@bridgeline-digital/hawk-app-cli 0.1.1-dev.20261021024847 → 0.1.1-dev.20261026155507
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +188 -24
- package/agents/AGENTS.md +32 -8
- package/claude-plugin/.claude-plugin/marketplace.json +14 -0
- package/claude-plugin/hawk-apps/.claude-plugin/plugin.json +8 -0
- package/claude-plugin/hawk-apps/.mcp.json +8 -0
- package/claude-plugin/hawk-apps/scripts/mcp.mjs +35 -0
- package/claude-plugin/hawk-apps/skills/add-function/SKILL.md +46 -0
- package/claude-plugin/hawk-apps/skills/add-query/SKILL.md +31 -0
- package/claude-plugin/hawk-apps/skills/new-screen/SKILL.md +36 -0
- package/claude-plugin/hawk-apps/skills/prepare-review/SKILL.md +36 -0
- package/docs/bridge/README.md +380 -24
- package/docs/showcase/hawk-app.json +8 -3
- package/docs/showcase/src/App.tsx +255 -1
- package/docs/ui/README.md +7 -0
- package/package.json +12 -1
- package/schema/hawk-app.schema.json +226 -2
- package/src/agents.mjs +38 -7
- package/src/claude-plugin.mjs +67 -0
- package/src/cli.mjs +56 -7
- package/src/deploy.mjs +16 -8
- package/src/dev.mjs +177 -4
- package/src/doctor.mjs +17 -3
- package/src/error-brief.mjs +128 -0
- package/src/function-test-runner.mjs +16 -0
- package/src/function-test.d.ts +51 -0
- package/src/function-test.mjs +303 -0
- package/src/functions.mjs +35 -21
- package/src/manifest.mjs +240 -1
- package/src/mcp-hawksearch.mjs +14 -4
- package/src/mcp.mjs +16 -0
- package/src/platform.mjs +64 -0
- package/src/project-checks.mjs +19 -5
- package/src/scope-check.mjs +9 -2
- package/src/sourcemaps.mjs +168 -0
- package/src/telemetry.mjs +38 -4
- package/src/template-history.json +8 -4
- package/src/test-command.mjs +88 -0
- package/templates/next/next.config.ts +3 -0
- package/templates/next/src/app/page.tsx +6 -0
- package/templates/next-mui/next.config.ts +3 -0
- package/templates/next-mui/src/app/page.tsx +3 -1
- package/templates/preact/src/App.tsx +6 -0
- package/templates/preact/vite.config.ts +3 -0
- package/templates/react/src/App.tsx +6 -0
- package/templates/react/vite.config.ts +3 -0
- package/templates/vanilla/public/app.js +2 -0
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: prepare-review
|
|
3
|
+
description: Get a Hawk App ready to submit for approval in HawkSearch - run the approvers' automated checks (bundle, dependencies, accessibility, outside origins, queries, functions, scopes), fix what fails and say what's left. Use before submitting, after a big change, or when the user asks whether the app will pass review.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Prepare for review
|
|
7
|
+
|
|
8
|
+
Approvers see automated checks in HawkSearch's approve dialog; a failed one forces them to override it. Get every check
|
|
9
|
+
green, or explain to the user why one can't be.
|
|
10
|
+
|
|
11
|
+
1. **Validate with the build.** Run the hawk-apps `validate` tool with `build: true`. Fix what it reports in the code;
|
|
12
|
+
ask the user before changing `scopes` or `externalOrigins` in `hawk-app.json`.
|
|
13
|
+
2. **The review checklist** (fix or explain each):
|
|
14
|
+
- **Bundle:** under 20 MB in total, no file over 5 MB, not 50% (and 1 MB) bigger than the released version.
|
|
15
|
+
- **Dependencies:** no high or critical findings in `npm run app:audit` (production dependencies).
|
|
16
|
+
- **Accessibility:** no critical axe issues (`validate --build` lists them): labels, `alt`, one `<main>`, headings
|
|
17
|
+
in order.
|
|
18
|
+
- **Outside origins:** `externalOrigins` lists each https origin the app calls or loads from; `["*"]` fails
|
|
19
|
+
(`"img:*"` is fine for images).
|
|
20
|
+
- **Scopes:** each one is used by the code, and every bridge call's scope is there (`validate` warns about missing
|
|
21
|
+
ones). Fewer is better: elevated scopes (`data:read`, `functions:invoke`, `hawksearch:write`, `ai:invoke`, …) are
|
|
22
|
+
never auto-approved.
|
|
23
|
+
- **Queries:** each has `{{partitions(...)}}`, names its columns and has a `LIMIT` on lists.
|
|
24
|
+
- **Functions and external Lambdas:** shown to the approver with their code and settings; Engineering must have
|
|
25
|
+
enabled functions. `npx hawk-app test` passes, if the app has function tests.
|
|
26
|
+
- **Client API endpoints (`api`):** each answers only what the client's own users could see in the app.
|
|
27
|
+
- **Actions (`actions`):** each target reads only what its view needs, with stable selectors (ids, `data-*`
|
|
28
|
+
attributes, element names); approvers see every target, and an app with actions is never auto-approved.
|
|
29
|
+
- **No secrets** anywhere in the app or the build.
|
|
30
|
+
3. **What approvers will see.** Run `status`: the review checks on the newest version, and `deploy` with
|
|
31
|
+
`dryRun: true`: what `hawk-app.json` would change. If the newest version is older than the code, say so: the user
|
|
32
|
+
decides when to deploy.
|
|
33
|
+
4. **Errors.** Run `errors` and fix what the deployed versions threw, the most frequent first: `error_brief` with an
|
|
34
|
+
error's id gives its stack mapped to the source, the failed platform calls and what changed since it last worked.
|
|
35
|
+
5. **Finish** with a short list: ready to submit (open the app with `npx hawk-app open` and click **Submit for
|
|
36
|
+
approval**), or what's left. Never deploy to prod or submit for the user.
|
package/docs/bridge/README.md
CHANGED
|
@@ -125,6 +125,82 @@ them in `hawk-app.json`:
|
|
|
125
125
|
`data:read`): hide or disable what writes, sends, stores or asks the assistant, and say it's a trial.
|
|
126
126
|
`navigate({ intent: 'app' })` opens the host's request dialog.
|
|
127
127
|
|
|
128
|
+
### Actions: start the app from HawkSearch's screens
|
|
129
|
+
|
|
130
|
+
**Actions** put the app where the work is: a button in HawkSearch's own pages (by a search term in a report, on an AI
|
|
131
|
+
Advisor insight…) that opens one of the app's views in a dialog, already on that thing. The app says where, with
|
|
132
|
+
**targets**: the page (a regular expression on its path), the things (a CSS selector: one button per match), where the
|
|
133
|
+
button goes, and how each of the view's params is read from the thing. Deploy, and once the version is approved the
|
|
134
|
+
buttons appear in HawkSearch: nothing changes in HawkSearch for it.
|
|
135
|
+
|
|
136
|
+
```jsonc
|
|
137
|
+
"views": {
|
|
138
|
+
"fix": { "path": "views/fix", "title": "Fix a search", "height": 420, "params": ["keyword", "category"] }
|
|
139
|
+
},
|
|
140
|
+
"actions": {
|
|
141
|
+
"fix-keyword": {
|
|
142
|
+
"label": "Fix with Zero Results Fixer",
|
|
143
|
+
"view": "fix",
|
|
144
|
+
"targets": {
|
|
145
|
+
"hawksearch": [
|
|
146
|
+
{
|
|
147
|
+
"path": "/admin/advanced-analytics/(top-queries|query-with-poor-results)/?$", // the page
|
|
148
|
+
"selector": "bl-table tbody.table__body > tr.table__row", // each thing: a table row
|
|
149
|
+
"anchor": "td.table__cell:first-child", // the button goes in it
|
|
150
|
+
"params": { "keyword": { "selector": "td.table__cell:first-child" } } // its text
|
|
151
|
+
},
|
|
152
|
+
{
|
|
153
|
+
"path": "/admin/recommendations/?$",
|
|
154
|
+
"selector": ".recommendations-list__item:not(.recommendations-list__item--empty)",
|
|
155
|
+
"anchor": ".recommendations-list__text",
|
|
156
|
+
"position": "after",
|
|
157
|
+
"params": {
|
|
158
|
+
"keyword": { "selector": ".recommendations-list__text", "pattern": "[\"“”]([^\"“”]{1,100})[\"“”]" },
|
|
159
|
+
"category": { "selector": ".recommendations-list__category-tag" }
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
]
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
- **A target:** `path` (a regular expression on the page's path), `selector` (each thing), `anchor` (a selector inside
|
|
169
|
+
the thing; default the thing) and `position` (`append`, the default, `prepend`, `before` or `after` it), and `params`:
|
|
170
|
+
for each of the view's params, `selector` (an element inside the thing; default the thing), `attribute` (default its
|
|
171
|
+
text) and `pattern` (a regular expression; its first group is the value). Values are text, at most 200 characters.
|
|
172
|
+
- **Data attributes (optional):** a param's `data` names a `data-*` attribute without its `data-` (`"keyword"` reads
|
|
173
|
+
`data-keyword`), on the thing or the first element inside it that has one. When it has a value, that value is used
|
|
174
|
+
as is (no pattern); when it's missing or empty, the param's `selector`, `attribute` and `pattern` read it as above.
|
|
175
|
+
So a target can be ready for an attribute HawkSearch adds later, e.g. `"keyword": { "data": "hawk-keyword",
|
|
176
|
+
"selector": "td.table__cell:first-child" }`. With `data` alone, nothing is read until the attribute is there.
|
|
177
|
+
- **Pick stable selectors:** ids, `data-*` attributes and element names over styling classes. HawkSearch doesn't know
|
|
178
|
+
about your selectors: when it changes its markup, your buttons stop appearing until you deploy new targets.
|
|
179
|
+
- **Approval:** `actions` is a setting that always needs an approver (never auto-approve): reviewers see each target and
|
|
180
|
+
what it reads from the page. Read only what the view needs. Settings and the Apps pages never show actions.
|
|
181
|
+
|
|
182
|
+
```tsx
|
|
183
|
+
// src/app/views/fix/page.tsx
|
|
184
|
+
const { app, context } = useHawk();
|
|
185
|
+
const keyword = context.view?.params.keyword?.trim(); // only the params the view declares arrive
|
|
186
|
+
// context.view.action: { name: 'fix-keyword' } (null when the view isn't opened by an action)
|
|
187
|
+
|
|
188
|
+
async function saved() {
|
|
189
|
+
await app.toast(`Synonym created for "${keyword}"`, 'success'); // shown by the host for an action's view
|
|
190
|
+
await app.action.done({ changed: true }); // the host closes the dialog (and may refresh)
|
|
191
|
+
}
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
- Each target's params must be ones the view declares (`hawk-app validate` checks it).
|
|
195
|
+
- `app.action.done({ changed })` closes the host's dialog. It does nothing outside an action, and on a host from
|
|
196
|
+
before actions.
|
|
197
|
+
- Params are read from the page, like a URL's query: check them, and handle them missing (the page may not show the
|
|
198
|
+
value). The platform's checks (scopes, client, engine) still apply to every call the view makes.
|
|
199
|
+
- At most 10 actions, 5 targets per host each. `hawk-app dev` has an **Action** menu that opens a view as HawkSearch
|
|
200
|
+
will (type the value in).
|
|
201
|
+
- A complete example, from `hawk-app.json` to release:
|
|
202
|
+
[Fix a search term from HawkSearch's reports](../../docs/examples.md#fix-a-search-term-from-hawksearchs-reports).
|
|
203
|
+
|
|
128
204
|
### Hosts: `<hawk-app-container>`
|
|
129
205
|
|
|
130
206
|
Configure once per page, then place containers anywhere:
|
|
@@ -153,7 +229,9 @@ configureHawkAppContainers({
|
|
|
153
229
|
`full-app` (the entire app instead of a view: `resolveView(app, null)`; its context has no `view`),
|
|
154
230
|
`min-height` (default 0), `max-height` (default 1200), `fallback` (text when the view can't load and the app has no description),
|
|
155
231
|
`trial` (a store app's trial view: the host's `resolveView`, `getToken` and `openApp` get `{ trial: true }`, and
|
|
156
|
-
the context `trial: true`)
|
|
232
|
+
the context `trial: true`), `action` (`{"name":"fix-keyword"}`: the view was opened from one of the app's
|
|
233
|
+
[actions](#actions-start-the-app-from-hawksearchs-screens); its toasts reach the page and `app.action.done()` fires
|
|
234
|
+
`hawk-action-done`).
|
|
157
235
|
- **Loading:** when scrolled near, at most `maxConcurrent` (default 3) at a time; a placeholder of the view's height
|
|
158
236
|
until it connects; the fallback after `loadTimeoutMs` (default 20 s).
|
|
159
237
|
- **Styling:** the `state` attribute (`idle`, `loading`, `ready`, `fallback`) and a `hawk-view-state` event; the
|
|
@@ -256,32 +334,48 @@ server's `analytics_reports` tool reads that spec.
|
|
|
256
334
|
### HawkSearch dashboard API: `hawksearch:read`
|
|
257
335
|
|
|
258
336
|
```ts
|
|
259
|
-
const synonyms = await app.dashboard.get('Synonym');
|
|
260
|
-
const fields = await app.dashboard.get('Field');
|
|
337
|
+
const synonyms = await app.dashboard.get('Synonym'); // HawkSearchModel<'Synonym'>[]
|
|
338
|
+
const fields = await app.dashboard.get('Field'); // HawkSearchModel<'FieldModel'>[]
|
|
261
339
|
// Query parameters: app.dashboard.get('LandingPage', { query: { ... } })
|
|
340
|
+
|
|
341
|
+
// Typed routes of a resource
|
|
342
|
+
const redirects = app.dashboard.resource('redirect');
|
|
343
|
+
const all = await redirects.list();
|
|
344
|
+
const one = await redirects.get(42);
|
|
262
345
|
```
|
|
263
346
|
|
|
264
347
|
Any resource of the [HawkSearch Dashboard API](https://developerdocs.hawksearch.com/reference/getting-started-with-your-api):
|
|
265
348
|
`Synonym`, `LandingPage`, `Field`, …. Leave the API version out; the platform picks it. Responses are HawkSearch's
|
|
266
|
-
body as is.
|
|
349
|
+
body as is.
|
|
350
|
+
|
|
351
|
+
**Typed from HawkSearch's API definition.** `app.dashboard.resource(name)` gives a resource's routes: `list()`,
|
|
352
|
+
`get(id)`, `create(body)`, `update(id, body)` and `delete(id)`, only those HawkSearch has for it (`bannerzone` is list
|
|
353
|
+
only, `LandingPage` has no list), with HawkSearch's models: a misspelled field or resource is a compile error.
|
|
354
|
+
`get(name)` of such a resource is typed too, and `HawkSearchModel<'Synonym'>` (or `'RedirectModel'`, `'LandingPage'`,
|
|
355
|
+
…) names a model in your own code. The resources, as HawkSearch spells them: `banner`, `bannerzone`, `boostbury`,
|
|
356
|
+
`campaign`, `Facet`, `Field`, `FieldInfo`, `LandingPage`, `landingPageLayout`, `paginationset`,
|
|
357
|
+
`recommendationvisibility`, `redirect`, `sortingset`, `spellcheck`, `Synonym`, `visibility`, `visitortarget` (paths
|
|
358
|
+
ignore case). Other paths (`LandingPage/urls`, `reports/…`) stay on `get<T>(path)`. The types come from HawkSearch's dev
|
|
359
|
+
API and are refreshed with each bridge release. To change data, see [Writing to HawkSearch](#writing-to-hawksearch-hawksearchwrite). For reporting, use
|
|
267
360
|
analytics. Your assistant can look up any operation's fields, its bridge call and scope with the `hawk-apps` MCP
|
|
268
|
-
server's `hawksearch_api` tool.
|
|
361
|
+
server's `hawksearch_api` tool. The same scope gives the preview page's tools (`app.hawksearch.preview`: visitor targets,
|
|
362
|
+
indexes, item details, keyword explain): see [Preview like HawkSearch's preview page](#preview-like-hawksearchs-preview-page).
|
|
269
363
|
|
|
270
364
|
### Writing to HawkSearch: `hawksearch:write`
|
|
271
365
|
|
|
272
366
|
```ts
|
|
273
367
|
// Create a synonym
|
|
274
|
-
const synonym = await app.dashboard.
|
|
368
|
+
const synonym = await app.dashboard.resource('Synonym').create({
|
|
275
369
|
SearchTerm: 'sneakers',
|
|
276
370
|
SynonymGroup: 'trainers,running shoes',
|
|
277
371
|
MultiWay: true,
|
|
278
372
|
});
|
|
279
373
|
|
|
280
374
|
// Update a redirect
|
|
281
|
-
await app.dashboard.
|
|
375
|
+
await app.dashboard.resource('redirect').update(42, { ...redirect, RedirectUrl: '/sale' });
|
|
282
376
|
|
|
283
|
-
// Delete a banner
|
|
284
|
-
await app.dashboard.
|
|
377
|
+
// Delete a banner (or any path: app.dashboard.delete('Banner/7'))
|
|
378
|
+
await app.dashboard.resource('banner').delete(7);
|
|
285
379
|
```
|
|
286
380
|
|
|
287
381
|
`app.dashboard.post(path, body?)`, `put(path, body?)` and `delete(path)` (also on `ctx.platform.dashboard` in
|
|
@@ -489,8 +583,8 @@ what `handler` returned (up to 6 MB). It waits up to `timeoutMs` (default 30 s)
|
|
|
489
583
|
| `ctx.app.slug`, `ctx.version` | Your app and the version the caller has open (the function code is that version's). |
|
|
490
584
|
| `ctx.secrets.NAME` | The secrets the function declares in `hawk-app.json` (see below). Nothing else. |
|
|
491
585
|
| `ctx.config` | The app's [config](#app-config) for the caller's client and this version, already resolved: `{ key: value }` for every declared key (null when nothing is set and there's no default). Frozen. |
|
|
492
|
-
| `ctx.platform` | The platform API **as the caller**: `analytics.report`, `dashboard.get/post/put/delete`, `data.query` (polled for you), [`email.send`](#email-emailsend), [`hawksearch.search/autocomplete`](#hawksearch-search-hawksearchsearch), [`hawksearch.systemParameters`](#hawksearch-system-parameters-hawksearchsysparamswrite), [`functions.start/job/wait`](#background-jobs), [`ai.generate/stream/info`](#ai-aiinvoke) and `api.get/post`, with your app's scopes. Analytics dates don't default on the server: pass them. |
|
|
493
|
-
| `ctx.job`, `ctx.trigger` | For a [background job](#background-jobs): `{ id, progress(percent?, message?) }` (null for a call), and what started the run (`call` or `
|
|
586
|
+
| `ctx.platform` | The platform API **as the caller**: `analytics.report`, `dashboard.get/post/put/delete`, `data.query` (polled for you), [`email.send`](#email-emailsend), [`hawksearch.search/autocomplete`](#hawksearch-search-hawksearchsearch), [`hawksearch.preview`](#preview-like-hawksearchs-preview-page), [`hawkai`](#hawkai-agents-and-smart-responses-hawkairead), [`hawksearch.systemParameters`](#hawksearch-system-parameters-hawksearchsysparamswrite), [`functions.start/job/wait`](#background-jobs), [`ai.generate/stream/info`](#ai-aiinvoke) and `api.get/post`, with your app's scopes. Analytics dates don't default on the server: pass them. |
|
|
587
|
+
| `ctx.job`, `ctx.trigger` | For a [background job](#background-jobs): `{ id, progress(percent?, message?) }` (null for a call), and what started the run (`call`, `schedule`, [`event`](#events) or [`api`](#client-api)). |
|
|
494
588
|
| `ctx.aws` | Only with `data:read`: short-lived credentials scoped to this client's analytics data, plus `region`, `database`, `workGroup`, `outputLocation`, for the AWS SDK's Athena client. Prefer `ctx.platform.data.query` when an app query does the job. |
|
|
495
589
|
|
|
496
590
|
The deploy wraps your `handler` with `toLambdaHandler` from `@bridgeline-digital/hawk-app-bridge/server` and
|
|
@@ -587,7 +681,8 @@ try {
|
|
|
587
681
|
pick the job up again after a reload.
|
|
588
682
|
- In the function, `ctx.job` is `{ id, progress(percent?, message?) }` (null for a call). `progress` sends at most
|
|
589
683
|
once a second (calls in between are merged) and never throws. `ctx.trigger` says what started the run:
|
|
590
|
-
`{ type: 'call' }
|
|
684
|
+
`{ type: 'call' }`, `{ type: 'schedule', schedule, enabledBy }`, `{ type: 'event', event, enabledBy? }` or
|
|
685
|
+
`{ type: 'api', endpoint }` (a [client API](#client-api) call).
|
|
591
686
|
- `ctx.platform.functions.start/job/wait` start other jobs from a function, e.g. to fan work out.
|
|
592
687
|
|
|
593
688
|
Limits: a job runs up to 900 s (`timeoutSeconds`, default 300); 3 jobs per app and client run at once (429
|
|
@@ -622,6 +717,118 @@ await app.schedules.enable('nightly'); // or disable('nightly'); both resolve
|
|
|
622
717
|
- Schedules are a setting reviewed like scopes. They stop when the app's release is withdrawn, functions are turned off for
|
|
623
718
|
it, or a version without the schedule is approved.
|
|
624
719
|
|
|
720
|
+
### Events
|
|
721
|
+
|
|
722
|
+
`events` in `hawk-app.json` name a job function the platform starts when something happens for a client, so the app
|
|
723
|
+
doesn't poll for it.
|
|
724
|
+
|
|
725
|
+
```jsonc
|
|
726
|
+
"functions": { "on-event": { "job": true } },
|
|
727
|
+
"events": {
|
|
728
|
+
"client.added": { "function": "on-event" },
|
|
729
|
+
"config.changed": { "function": "on-event" },
|
|
730
|
+
"version.released": { "function": "on-event" }
|
|
731
|
+
}
|
|
732
|
+
```
|
|
733
|
+
|
|
734
|
+
```ts
|
|
735
|
+
// functions/on-event.ts
|
|
736
|
+
import type { HawkAppEvent, HawkFunction } from '@bridgeline-digital/hawk-app-bridge/server';
|
|
737
|
+
|
|
738
|
+
export const handler: HawkFunction<HawkAppEvent> = async (input, ctx) => {
|
|
739
|
+
switch (input.event) {
|
|
740
|
+
case 'client.added': // { clientId, at }
|
|
741
|
+
await ctx.platform.storage.put('settings', { digest: 'weekly' });
|
|
742
|
+
break;
|
|
743
|
+
case 'config.changed': // { clientId, at, key, scope: 'client' | 'global' }; new values in ctx.config
|
|
744
|
+
break;
|
|
745
|
+
case 'version.released': // { clientId, at, version, previousVersion }; runs on the new version
|
|
746
|
+
break;
|
|
747
|
+
}
|
|
748
|
+
};
|
|
749
|
+
```
|
|
750
|
+
|
|
751
|
+
| Event | When |
|
|
752
|
+
|---|---|
|
|
753
|
+
| `client.added` | The client's users see the app now and didn't before: a store request approved, the client added to the app's list, the app released (for every client already on its list), or an alias approved for the client. Only for listed clients: an app with visibility `all` has no list of who got it. |
|
|
754
|
+
| `config.changed` | A config value was set or cleared for the client, or globally (then for every client without its own value for the key). |
|
|
755
|
+
| `version.released` | Another version reached a client that had the app: a release or rollback, or its alias moved. |
|
|
756
|
+
|
|
757
|
+
- For an app every client has (visibility `all`), `config.changed` (global) and `version.released` go to the clients
|
|
758
|
+
whose users opened it in the last 90 days.
|
|
759
|
+
- The run is a job (`ctx.trigger` `{ type: 'event', event }`) for that client, as the user `event:<name>` (no roles,
|
|
760
|
+
not internal), **without a site or HawkSearch connection**: no user's session is behind it, so HawkSearch calls
|
|
761
|
+
answer 403 `hawksearch_not_available`. Storage, data, email, config and secrets work.
|
|
762
|
+
- Delivered once; not retried when it can't start (3 jobs already running for the client). Events are a setting
|
|
763
|
+
reviewed like scopes; only approved handlers run, and only while functions are on for the app.
|
|
764
|
+
|
|
765
|
+
#### HawkSearch events
|
|
766
|
+
|
|
767
|
+
`hawksearch.changed` (something changed in HawkSearch's dashboard) and `index.rebuilt` (an index rebuild finished) come
|
|
768
|
+
from HawkSearch itself, read once a minute for the engines where the client's users opened the app. They run **with
|
|
769
|
+
that engine and the client's HawkSearch connection**, so the handler can read what changed:
|
|
770
|
+
|
|
771
|
+
```jsonc
|
|
772
|
+
"events": {
|
|
773
|
+
"hawksearch.changed": { "function": "sync-synonyms", "sections": ["synonym"] },
|
|
774
|
+
"index.rebuilt": { "function": "after-rebuild" }
|
|
775
|
+
}
|
|
776
|
+
```
|
|
777
|
+
|
|
778
|
+
```ts
|
|
779
|
+
import type { HawkFunction, HawkSearchChangedEvent } from '@bridgeline-digital/hawk-app-bridge/server';
|
|
780
|
+
|
|
781
|
+
export const handler: HawkFunction<HawkSearchChangedEvent> = async (input, ctx) => {
|
|
782
|
+
// { engineId: '3141', section: 'synonym', changes: [{ action: 'update', recordId: 12, apiAction: false, at }], count: 1, truncated: false }
|
|
783
|
+
const synonyms = await ctx.platform.dashboard.get('Synonym');
|
|
784
|
+
// …push them to the other system
|
|
785
|
+
};
|
|
786
|
+
```
|
|
787
|
+
|
|
788
|
+
- `sections` (optional) are HawkSearch's section codes: `synonym`, `redirect`, `landingpage`, `field`, `facet`,
|
|
789
|
+
`visibilityrule`, `boostburyrule`, `pinneditem`, `contentitem`, `bannergroup`, `systemparameters` and the rest of the
|
|
790
|
+
search settings (the JSON schema lists them all). One event per section per minute, with up to 100 changes.
|
|
791
|
+
- `index.rebuilt`: `{ engineId, notificationId, status: 'success' | 'error', finishedAt }`, however the rebuild started.
|
|
792
|
+
- Expect them 1–2 minutes after the change. Changes your app makes itself through the platform (dashboard writes,
|
|
793
|
+
pinning, system parameters) aren't sent back to it. Who made a change is never sent. A handler can see an event twice
|
|
794
|
+
after a platform hiccup: make it idempotent.
|
|
795
|
+
|
|
796
|
+
### Client API
|
|
797
|
+
|
|
798
|
+
`api` in `hawk-app.json` turns functions into endpoints the **client's own systems** call (a developer's script, a job
|
|
799
|
+
on their side), with their engine's HawkSearch site API key. No keys to manage: Bridgeline turns API access on per client
|
|
800
|
+
in HawkSearch (the app's kebab menu › API access), which also shows each endpoint's URL and a curl.
|
|
801
|
+
|
|
802
|
+
```jsonc
|
|
803
|
+
"functions": { "design-html": {} },
|
|
804
|
+
"api": {
|
|
805
|
+
"design-html": { "function": "design-html", "method": "GET", "description": "A saved design's email HTML: ?designId=<id>" }
|
|
806
|
+
}
|
|
807
|
+
```
|
|
808
|
+
|
|
809
|
+
```ts
|
|
810
|
+
// functions/design-html.ts
|
|
811
|
+
import { apiResponse, type HawkApiResponse, type HawkFunction } from '@bridgeline-digital/hawk-app-bridge/server';
|
|
812
|
+
|
|
813
|
+
export const handler: HawkFunction<{ designId?: string }, HawkApiResponse> = async (input, ctx) => {
|
|
814
|
+
const saved = await ctx.platform.storage.get<Design>(`design#${input.designId}`);
|
|
815
|
+
if (!saved) return apiResponse('No design with this id.', { status: 404 });
|
|
816
|
+
return apiResponse(await renderDesign(saved.value, ctx), { contentType: 'text/html; charset=utf-8' });
|
|
817
|
+
};
|
|
818
|
+
```
|
|
819
|
+
|
|
820
|
+
```
|
|
821
|
+
curl -H "X-HawkSearch-ApiKey: <site API key>" "https://<platform>/v1/clients/<clientId>/apps/<slug>/api/design-html?designId=abc"
|
|
822
|
+
```
|
|
823
|
+
|
|
824
|
+
- The input is the query string (GET, the default; values are strings) or the JSON body (POST).
|
|
825
|
+
- A function's answer is sent as JSON; `apiResponse(body, { contentType?, status? })` sends a body as is (HTML, CSV, …).
|
|
826
|
+
- The function isn't a job (the caller waits). `ctx.trigger` is `{ type: 'api', endpoint }`; it runs as the user
|
|
827
|
+
`api:<name>` (no roles, not internal) for the client, on the key's engine, with the key as its HawkSearch connection.
|
|
828
|
+
- At most 10 endpoints; 120 calls a minute per client (429). Endpoints are a setting reviewed like functions. Anyone
|
|
829
|
+
holding the client's site key can call them once access is on: answer only what the client's users could see in
|
|
830
|
+
the app.
|
|
831
|
+
|
|
625
832
|
## Email: `email:send`
|
|
626
833
|
|
|
627
834
|
Send an email from the app or from a [function](#functions-functionsinvoke): a report to the user, an alert, a
|
|
@@ -695,8 +902,9 @@ await app.hawksearch.autocomplete({ Keyword: 'boo', ProductCount: 5 });
|
|
|
695
902
|
```
|
|
696
903
|
|
|
697
904
|
- The request is HawkSearch's v2 search / autocomplete body, sent as is: any field works (`Keyword`, `PageNo`,
|
|
698
|
-
`MaxPerPage`, `SortBy`, `FacetSelections`, `CustomUrl`, `IndexName`, `SmartBar`, …).
|
|
699
|
-
|
|
905
|
+
`MaxPerPage`, `SortBy`, `FacetSelections`, `CustomUrl`, `IndexName`, `SmartBar`, …). Every field of HawkSearch's search
|
|
906
|
+
API definition is typed (`SortingSetCode`, `PageId`, `RequestType`, `BoostQueries`, …). The platform sets `ClientGuid`
|
|
907
|
+
to the current engine's; you never need it. The response has the common fields typed; the rest is HawkSearch's as is.
|
|
700
908
|
- `IsInPreview: true` runs on the engine's preview search API.
|
|
701
909
|
- At most 600 calls per app and client per minute: a `429 too_many_requests` carries `retryAfterMs` (not retried
|
|
702
910
|
for you). For bulk checks, run a few at a time.
|
|
@@ -713,6 +921,87 @@ await app.hawksearch.autocomplete({ Keyword: 'boo', ProductCount: 5 });
|
|
|
713
921
|
|
|
714
922
|
Other HawkSearch errors (e.g. `400` for an unknown `IndexName`) come back as HawkSearch sent them.
|
|
715
923
|
|
|
924
|
+
### Preview like HawkSearch's preview page
|
|
925
|
+
|
|
926
|
+
A preview search takes everything the preview page sets: `IsInPreview: true`, `SmartBar` (`PreviewDate` and switches
|
|
927
|
+
such as `BoostAndBury: false`, `ItemPin: false`), `ClientData: { PreviewBuckets: [ids] }` for visitor targets,
|
|
928
|
+
`IndexName`, `Is100CoverageTurnedOn`, and `ExplainDocId` to explain one result's score (`DocExplain` in the answer). The page's own pickers and explain panel are `app.hawksearch.preview` (scope **`hawksearch:read`**,
|
|
929
|
+
within the same per-minute limit):
|
|
930
|
+
|
|
931
|
+
```ts
|
|
932
|
+
const settings = await app.hawksearch.preview.settings(); // visitor targets, fields, layouts
|
|
933
|
+
const { ClientIndexes, CurrentIndex } = await app.hawksearch.preview.indexes();
|
|
934
|
+
const item = await app.hawksearch.preview.item('SKU-123'); // one item as indexed ({ indexName } optional)
|
|
935
|
+
const why = await app.hawksearch.preview.explain({ Keyword: 'jackets', IsInPreview: true });
|
|
936
|
+
why.AdjustedKeyword; why.AppliedKeywordReplacementRules; why.ElasticQuery;
|
|
937
|
+
```
|
|
938
|
+
|
|
939
|
+
`explain` needs the session's site API key (`403 preview_needs_site_key` with a group key).
|
|
940
|
+
|
|
941
|
+
**Pin items** (scope **`hawksearch:write`**): a search's response has `ItemPinId`, the Item Pin rule it applied (null
|
|
942
|
+
when none), and `IsPin` on the results that rule pins. Pin, unpin and reorder its items; HawkSearch saves the change as
|
|
943
|
+
the user, and it applies to live search too:
|
|
944
|
+
|
|
945
|
+
```ts
|
|
946
|
+
const results = await app.hawksearch.search({ Keyword: 'jacket', IsInPreview: true });
|
|
947
|
+
if (results.ItemPinId) {
|
|
948
|
+
await app.hawksearch.pinning.set(results.ItemPinId, 'SKU-123', true); // false unpins
|
|
949
|
+
await app.hawksearch.pinning.order(results.ItemPinId, ['SKU-9', 'SKU-123']); // the pinned order
|
|
950
|
+
}
|
|
951
|
+
```
|
|
952
|
+
|
|
953
|
+
Pass `{ previewDate }` (the search's `SmartBar.PreviewDate`) so the next preview search shows the change at once.
|
|
954
|
+
Site keys only, within the write limit, never retried. Errors: `pinning_needs_user` (403: the host didn't send the
|
|
955
|
+
user's HawkSearch admin), `hawksearch_write_needs_site_key` (403), `pinning_refused` (502: HawkSearch refused the
|
|
956
|
+
user), `pinning_not_configured` (503). A search without a rule has nothing to pin into: rules are created in HawkSearch.
|
|
957
|
+
|
|
958
|
+
## HawkAI agents and smart responses: `hawkai:read`
|
|
959
|
+
|
|
960
|
+
The client's HawkAI agents and smart responses for the user's current engine, as Spark lists them: names and settings,
|
|
961
|
+
never prompts. From the app or a function (`ctx.platform.hawkai`).
|
|
962
|
+
|
|
963
|
+
```ts
|
|
964
|
+
const agents = await app.hawkai.agents(); // by name
|
|
965
|
+
const responses = await app.hawkai.smartResponses(); // with maxItems and allowRequestOverride
|
|
966
|
+
const one = await app.hawkai.smartResponse(responses[0].id);
|
|
967
|
+
```
|
|
968
|
+
|
|
969
|
+
Each is `{ id, name, description, model, allEngines, maxItems, allowRequestOverride }` (`allEngines`: set up for every
|
|
970
|
+
engine of the client).
|
|
971
|
+
|
|
972
|
+
**Run a smart response** for a query and a search, live or in preview, as the storefront does. The platform adds the
|
|
973
|
+
client, the engine's ClientGuid and its search API; HawkAI runs the search and picks the items:
|
|
974
|
+
|
|
975
|
+
```ts
|
|
976
|
+
const answer = await app.hawkai.runSmartResponse(id, {
|
|
977
|
+
query: 'waterproof jacket',
|
|
978
|
+
search: { Keyword: 'jacket', IsInPreview: true, SmartBar: { PreviewDate: '2026-11-27T00:00:00' } },
|
|
979
|
+
});
|
|
980
|
+
answer.recommendation; // Markdown
|
|
981
|
+
answer.Results; // [{ DocId, Document, reason }]
|
|
982
|
+
```
|
|
983
|
+
|
|
984
|
+
Answered whole (not streamed), within the search limit. **An assistant** runs in HawkAI's widget
|
|
985
|
+
([`@bridgeline-digital/hawkai-assistant`](https://www.npmjs.com/package/@bridgeline-digital/hawkai-assistant)), which
|
|
986
|
+
talks to HawkAI from the browser: `app.hawkai.assistant(agentId)` gives it `{ apiUrl, accountId, agentId }`.
|
|
987
|
+
|
|
988
|
+
```ts
|
|
989
|
+
HawkAI.Assistant.initialize({ ...(await app.hawkai.assistant(agentId)), ui: { display: 'embedded', embedHostSelector: '#assistant' } });
|
|
990
|
+
```
|
|
991
|
+
|
|
992
|
+
List HawkAI's origin in `externalOrigins` (`https://assistant-dev.hawksearch.net` on dev, `https://assistant.hawksearch.net`
|
|
993
|
+
on prod). The widget initializes once per page: reload to switch agents.
|
|
994
|
+
|
|
995
|
+
| `error.code` | Status | Meaning |
|
|
996
|
+
|---|---|---|
|
|
997
|
+
| `scope_missing` | 403 | Add `hawkai:read` to `scopes`. |
|
|
998
|
+
| `engine_required` | 400 | The host session has no engine. |
|
|
999
|
+
| `hawkai_not_found` | 404 | No agent / smart response with that id for this client and engine. |
|
|
1000
|
+
| `hawkai_unavailable` | 502 | HawkAI's configuration couldn't be read, or HawkAI didn't answer in time: try again. |
|
|
1001
|
+
| `hawkai_error` | 502 | HawkAI answered without a smart response (its reason is in the message). |
|
|
1002
|
+
| `hawkai_limit_reached` | 429 | HawkAI's own limit for the client (tokens or rate). |
|
|
1003
|
+
| `too_many_requests` | 429 | Over the search limit (smart responses count as searches); wait `retryAfterMs`. |
|
|
1004
|
+
|
|
716
1005
|
## HawkSearch system parameters: `hawksearch:sysparams:write`
|
|
717
1006
|
|
|
718
1007
|
Let users change a few HawkSearch **system parameters** of the engine they are working on (Sites → System Parameters
|
|
@@ -851,8 +1140,10 @@ await app.hawksearch.index.setCurrent(name);
|
|
|
851
1140
|
- **Never the live index:** `deleteIndex` of the current index is 409 `index_is_current`. Engines may have only two
|
|
852
1141
|
indexes: delete the old one before `create`. `unset-current`, index templates and group indexing aren't available.
|
|
853
1142
|
- `call(operation, body?)` sends HawkSearch's own body
|
|
854
|
-
([Indexing API](https://developerdocs.hawksearch.com/reference/indexv2_get)) and resolves to its answer.
|
|
855
|
-
|
|
1143
|
+
([Indexing API](https://developerdocs.hawksearch.com/reference/indexv2_get)) and resolves to its answer. The body is
|
|
1144
|
+
typed from HawkSearch's Indexing API definition (`IndexingRequests`, fields in PascalCase: `IndexName`, `Items`,
|
|
1145
|
+
`Hierarchies`, …; HawkSearch reads any case). Engines without an Indexing API answer 503 `indexing_api_not_available`;
|
|
1146
|
+
anything outside the list is 404.
|
|
856
1147
|
|
|
857
1148
|
## HawkSearch recommendations: `hawksearch:recommendations`
|
|
858
1149
|
|
|
@@ -1144,6 +1435,69 @@ await app.navigate({ intent: 'app', slug: 'other-tool' }); // also: 'report', 's
|
|
|
1144
1435
|
await app.resize(); // when autoResize is off
|
|
1145
1436
|
```
|
|
1146
1437
|
|
|
1438
|
+
### Say what the page shows: "Explain this page"
|
|
1439
|
+
|
|
1440
|
+
Hosts show an **Explain this page** button by the app: a short plain-language summary of what the page shows, what
|
|
1441
|
+
stands out and what to do, written by the platform's AI from what the app says is on screen. Every app gets it, with no
|
|
1442
|
+
scope and no AI budget of its own; the button shows only while the app has said what its page shows:
|
|
1443
|
+
|
|
1444
|
+
```tsx
|
|
1445
|
+
// With the UI kit: sent shortly after it changes, cleared when the page unmounts; null while loading.
|
|
1446
|
+
usePageContext(rows ? { page: 'Zero-result searches', dateRange: context.dateRange, total, top10: rows.slice(0, 10) } : null);
|
|
1447
|
+
|
|
1448
|
+
// Without it:
|
|
1449
|
+
await app.assistant.setContext({ page: 'Zero-result searches', dateRange, total, top10 }); // null clears it
|
|
1450
|
+
```
|
|
1451
|
+
|
|
1452
|
+
- **What to put in it:** the figures on screen, in plain names and numbers, with what they mean (`{ keyword, searches,
|
|
1453
|
+
results }`, not `{ k, s, r }`), and the page's name. A JSON object of at most 50 KB: summarize long tables (the top
|
|
1454
|
+
rows and the totals).
|
|
1455
|
+
- **Keep it current:** set it again when the user changes a filter or a tab; the answer is about what you last set. The
|
|
1456
|
+
host clears it when the app closes.
|
|
1457
|
+
- **The same context** goes to the host's assistant for apps with the `assistant` scope (below).
|
|
1458
|
+
- **The explanation** is written by the platform (Claude, the fast tier) from the context, the app's name and
|
|
1459
|
+
description, the page's title (`app.setTitle`) and the user's language. It's told to use only what the page says;
|
|
1460
|
+
users can copy it and rate it. Explanations come from one daily budget per client, for all its apps.
|
|
1461
|
+
- `hawk-app validate` warns when the app never sets a context, and `hawk-app dev` has the button in its bar.
|
|
1462
|
+
|
|
1463
|
+
Hosts: `<hawk-app-explain for="<frame id>">` (registered with `<hawk-app-frame>`) is the button and its side panel;
|
|
1464
|
+
the frame's `pageContext`, `hawk-page-context` event and `explainPage()` are there for hosts that build their own. See
|
|
1465
|
+
[HOSTS.md](HOSTS.md).
|
|
1466
|
+
|
|
1467
|
+
### Export a page as a PDF
|
|
1468
|
+
|
|
1469
|
+
Pages worth keeping (a report, a summary for a meeting) can offer **Export as PDF**: while the page allows it, the
|
|
1470
|
+
host shows a PDF icon by the app, and a click downloads the app's whole page (its full height, not just what's on
|
|
1471
|
+
screen) as a PDF. Only the app's frame is in it, never the host's page. You choose the pages; no scope is needed.
|
|
1472
|
+
|
|
1473
|
+
```tsx
|
|
1474
|
+
// With the UI kit: on while the page is mounted (false while it loads), off when it unmounts.
|
|
1475
|
+
usePdfExport(rows ? { fileName: `Zero results ${dateRange.from}`, title: 'Zero-result searches' } : false);
|
|
1476
|
+
|
|
1477
|
+
// Without it:
|
|
1478
|
+
await app.setPdfExport({ fileName: 'Zero results' }); // the icon shows
|
|
1479
|
+
await app.setPdfExport(null); // and hides
|
|
1480
|
+
```
|
|
1481
|
+
|
|
1482
|
+
```tsx
|
|
1483
|
+
<Stack direction="row" data-hawk-pdf-ignore> {/* left out of the PDF: filters, buttons */}
|
|
1484
|
+
```
|
|
1485
|
+
|
|
1486
|
+
- **What's in it:** the page as the user sees it, rendered to images in the app (the bridge loads `html-to-image`
|
|
1487
|
+
only when someone exports) and cut into A4 pages, landscape for pages wider than 900 px. Text in the PDF is an
|
|
1488
|
+
image: it can't be selected or searched.
|
|
1489
|
+
- **Options:** `fileName` (without `.pdf`; default `<slug>-<yyyy-mm-dd>`), `title` (the PDF's properties; default the
|
|
1490
|
+
page's title).
|
|
1491
|
+
- **Leave out** what doesn't belong on paper with `data-hawk-pdf-ignore` (buttons, filters, the pager). Content inside
|
|
1492
|
+
a scrolling box (a table with its own scrollbar) is exported as far as it's scrolled into view: let it grow on pages
|
|
1493
|
+
you export.
|
|
1494
|
+
- Images from other sites that don't allow it (CORS) come out blank. A failed export shows the host's error toast and
|
|
1495
|
+
is reported like the page's other errors (`hawk-app errors`).
|
|
1496
|
+
- `hawk-app dev` has the icon in its toolbar. Errors (`HawkBridgeError`): `unsupported` (a host without PDF export).
|
|
1497
|
+
|
|
1498
|
+
Hosts: `<hawk-app-pdf for="<frame id>">` (registered with `<hawk-app-frame>`) is the icon; the frame's `pdfExport`,
|
|
1499
|
+
`hawk-pdf-export` event and `exportPdf()` are there for hosts that build their own. See [HOSTS.md](HOSTS.md).
|
|
1500
|
+
|
|
1147
1501
|
### Ask the host's assistant: `assistant`
|
|
1148
1502
|
|
|
1149
1503
|
With the `assistant` scope, an app asks the host's assistant (HawkSearch: Hawk Assistant) a question as the user. The
|
|
@@ -1159,10 +1513,9 @@ useEffect(() => app.onContextChange((c) => setReady(!!c.features?.assistant)), [
|
|
|
1159
1513
|
</Button>
|
|
1160
1514
|
```
|
|
1161
1515
|
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
questions themselves plain:
|
|
1516
|
+
The page context ([above](#say-what-the-page-shows-explain-this-page): `app.assistant.setContext`, or the UI kit's
|
|
1517
|
+
`usePageContext`) goes to the assistant too, for apps with the scope: with every question, the app's and the ones the
|
|
1518
|
+
user types, until the app replaces or clears it. Keep the questions themselves plain:
|
|
1166
1519
|
|
|
1167
1520
|
```ts
|
|
1168
1521
|
useEffect(() => void app.assistant.setContext({ page: 'Recommendation Dashboard', range, totals, widgets }), [range, totals, widgets]);
|
|
@@ -1170,7 +1523,7 @@ await app.assistant.ask('Which widgets are taking up space?');
|
|
|
1170
1523
|
```
|
|
1171
1524
|
|
|
1172
1525
|
A message is at most 2,000 characters. Errors (`HawkBridgeError`): `unsupported` (no assistant on the page), `scope_missing`,
|
|
1173
|
-
`invalid_request
|
|
1526
|
+
`invalid_request`; `setContext` needs no scope (without it, only "Explain this page" gets the context). Hosts: `<hawk-app-frame>` fires a cancelable `hawk-assistant` event (`detail.message`; `hawk-assistant-context` with `detail.context` for the context); a host that
|
|
1174
1527
|
answers it itself calls `preventDefault()` and sets the element's `assistant` attribute. Otherwise the element sends the
|
|
1175
1528
|
message to `window.HawkAI.Assistant.sendMessage` (the context to `setContext`, kept until the assistant loads) when the page has it, and tells the app (a `contextChanged` with
|
|
1176
1529
|
`features.assistant`) when it loads.
|
|
@@ -1264,9 +1617,12 @@ Which clients have the app is not in this file: manage them in the Hawk Analytic
|
|
|
1264
1617
|
| `hosting` | `static` (the default): built files on the apps CDN. `server` is reserved for server-rendered apps and not accepted yet. |
|
|
1265
1618
|
| `visibility` | `allowList` (only the clients given the app), `all` (every client except excluded ones), or `internal` (Bridgeline staff). The clients themselves are managed in the Hawk Analytics portal or with `npx hawk-app clients add`/`exclude`/`remove`; a `hawk-app.json` with `clients` or `excludeClients` is refused. |
|
|
1266
1619
|
| `listed` | `true` (the default) shows the app on the host's Apps page. `false` leaves it off that list, and the host hides its own title and back button for it, but it still opens by its direct URL (e.g. linked from another app or page). It doesn't change who can open it: `visibility` and the app's clients still apply. |
|
|
1267
|
-
| `scopes` | `reports:read` for analytics, `hawksearch:read` for the dashboard API, `hawksearch:write` to [write to it](#writing-to-hawksearch-hawksearchwrite), `data:read` for [custom queries](#custom-data-queries-dataread), `functions:invoke` for [functions](#functions-functionsinvoke), `email:send` for [email](#email-emailsend), `hawksearch:sysparams:write` for [system parameters](#hawksearch-system-parameters-hawksearchsysparamswrite), `hawksearch:search` for [search](#hawksearch-search-hawksearchsearch), `hawksearch:features:write` for [features](#hawksearch-features-hawksearchfeatureswrite), `hawksearch:index` for [index rebuilds](#hawksearch-index-rebuilds-hawksearchindex), `hawksearch:index:items` and `hawksearch:index:manage` for [the Indexing API](#the-indexing-api-hawksearchindex-indexitems-indexmanage), `hawksearch:recommendations` for [recommendations](#hawksearch-recommendations-hawksearchrecommendations), `storage` for [storage](#storage-storage), `lambdas:invoke` for [external Lambdas](#external-lambdas-lambdasinvoke), `ai:invoke` for [AI](#ai-aiinvoke), `assistant` to [ask the host's assistant](#ask-the-hosts-assistant-assistant). |
|
|
1620
|
+
| `scopes` | `reports:read` for analytics, `hawksearch:read` for the dashboard API, `hawksearch:write` to [write to it](#writing-to-hawksearch-hawksearchwrite), `data:read` for [custom queries](#custom-data-queries-dataread), `functions:invoke` for [functions](#functions-functionsinvoke), `email:send` for [email](#email-emailsend), `hawksearch:sysparams:write` for [system parameters](#hawksearch-system-parameters-hawksearchsysparamswrite), `hawksearch:search` for [search](#hawksearch-search-hawksearchsearch), `hawksearch:features:write` for [features](#hawksearch-features-hawksearchfeatureswrite), `hawksearch:index` for [index rebuilds](#hawksearch-index-rebuilds-hawksearchindex), `hawksearch:index:items` and `hawksearch:index:manage` for [the Indexing API](#the-indexing-api-hawksearchindex-indexitems-indexmanage), `hawksearch:recommendations` for [recommendations](#hawksearch-recommendations-hawksearchrecommendations), `hawkai:read` for [HawkAI](#hawkai-agents-and-smart-responses-hawkairead), `storage` for [storage](#storage-storage), `lambdas:invoke` for [external Lambdas](#external-lambdas-lambdasinvoke), `ai:invoke` for [AI](#ai-aiinvoke), `assistant` to [ask the host's assistant](#ask-the-hosts-assistant-assistant). |
|
|
1268
1621
|
| `functions` | Your [functions](#functions-functionsinvoke) and their settings: `{ "<name>": { "job", "timeoutSeconds", "memoryMb", "secrets" } }`. Recorded with each version. |
|
|
1269
1622
|
| `schedules` | [Schedules](#schedules) for job functions: `{ "<name>": { "function", "cron", "timezone" } }`, at most 5, at most hourly. Needs `functions:invoke`. Reviewed like scopes. |
|
|
1623
|
+
| `events` | [Events](#events): a job function per event (`client.added`, `config.changed`, `version.released`, `hawksearch.changed`, `index.rebuilt`), `{ "<event>": { "function" } }` (`hawksearch.changed` also takes `sections`). Needs `functions:invoke`. Reviewed like scopes. |
|
|
1624
|
+
| `actions` | [Actions](#actions-start-the-app-from-hawksearchs-screens): buttons in HawkSearch's pages that open a view in a dialog, `{ "<name>": { "label", "view", "targets": { "hawksearch": [{ "path", "selector", "anchor"?, "position"?, "params" }] } } }` (at most 10). Always reviewed by an approver. |
|
|
1625
|
+
| `api` | [Client API](#client-api): endpoints the client's systems call with their HawkSearch site API key, `{ "<name>": { "function", "method"?: "GET" \| "POST", "description"? } }`, each a function that isn't a job (at most 10). Needs `functions:invoke`. Reviewed like scopes. |
|
|
1270
1626
|
| `systemParameters` | The HawkSearch [system parameters](#hawksearch-system-parameters-hawksearchsysparamswrite) the app may read and set, e.g. `["Timezone"]`. Needs `hawksearch:sysparams:write`. Reviewed like scopes. |
|
|
1271
1627
|
| `config` | [App config](#app-config) keys: `{ "<key>": { "type", "label", "description", "default" } }`, at most 50. The values are set outside the file (portal or `npx hawk-app config`) and aren't secret. Reviewed like scopes. |
|
|
1272
1628
|
| `externalOrigins` | Outside origins for scripts, styles, fonts, images, frames and API calls. New apps start with `[]` (none; the vanilla template: `["https://cdn.jsdelivr.net"]`). `["https://api.example.com", "https://*.cdn.example.com"]` allows only those. `["*"]` (also what a missing field means) allows any https origin and fails the review check. https or wss, no paths. Entries starting with `img:` are for images only: `"img:*"` (any https origin, e.g. a client's product images) or `"img:https://cdn.example.com"`. |
|
|
@@ -1287,8 +1643,8 @@ HawkSearch. See [versions, statuses and aliases](../../docs/versions-and-aliases
|
|
|
1287
1643
|
- Client changes (Hawk Analytics portal or `npx hawk-app clients`) apply at once, without a deploy or approval, and
|
|
1288
1644
|
are recorded in the app's history.
|
|
1289
1645
|
- Approvers can set an app to **auto-approve**, so new versions and changes are released straight away.
|
|
1290
|
-
- Apps using queries, functions, system parameters, dashboard writes, AI, external Lambdas, HawkSearch features
|
|
1291
|
-
rebuilds are never auto-approved.
|
|
1646
|
+
- Apps using queries, functions, system parameters, dashboard writes, AI, external Lambdas, HawkSearch features, index
|
|
1647
|
+
rebuilds or [actions](#actions-start-the-app-from-hawksearchs-screens) are never auto-approved.
|
|
1292
1648
|
|
|
1293
1649
|
**Approval.** Members of the Hawk-App-Developers group submit a draft, new version or settings change for approval in HawkSearch; members of
|
|
1294
1650
|
the Hawk-App-Approvers group approve, reject (with a reason) or force approve it. A new deploy or settings change clears a
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"slug": "showcase",
|
|
4
4
|
"name": "Hawk Apps Showcase",
|
|
5
5
|
"icon": "icon.svg",
|
|
6
|
-
"description": "Every Hawk Apps feature in one app: context, analytics, dashboard API, search, queries, functions, jobs, system parameters, storage, app config, AI and host messages.",
|
|
6
|
+
"description": "Every Hawk Apps feature in one app: context, analytics, dashboard API, search, preview tools, HawkAI, queries, functions, jobs, system parameters, storage, app config, AI and host messages.",
|
|
7
7
|
"category": "Showcase",
|
|
8
8
|
"version": "0.1.0",
|
|
9
9
|
"visibility": "internal",
|
|
@@ -21,7 +21,8 @@
|
|
|
21
21
|
"hawksearch:index",
|
|
22
22
|
"hawksearch:index:items",
|
|
23
23
|
"ai:invoke",
|
|
24
|
-
"storage"
|
|
24
|
+
"storage",
|
|
25
|
+
"hawkai:read"
|
|
25
26
|
],
|
|
26
27
|
"systemParameters": [
|
|
27
28
|
"Timezone",
|
|
@@ -41,7 +42,11 @@
|
|
|
41
42
|
"label": "Notification recipients"
|
|
42
43
|
}
|
|
43
44
|
},
|
|
44
|
-
"externalOrigins": [
|
|
45
|
+
"externalOrigins": [
|
|
46
|
+
"https://cdn.jsdelivr.net",
|
|
47
|
+
"https://assistant-dev.hawksearch.net",
|
|
48
|
+
"https://assistant.hawksearch.net"
|
|
49
|
+
],
|
|
45
50
|
"functions": {
|
|
46
51
|
"echo": {
|
|
47
52
|
"timeoutSeconds": 10,
|