@bootstrapware/answers 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/AGENTS.md ADDED
@@ -0,0 +1,245 @@
1
+ # Integrating @bootstrapware/answers
2
+
3
+ Use this file as agent instructions. The React widget and the Hosted Answers service are separate. This file is the install contract: config, keys, MCP, the widget, the script embed, and the Hosted ask route that already exists.
4
+
5
+ ## What this package does
6
+
7
+ Visitors ask a question about the page. The widget shows one complete answer with citations, or an explicit fallback when the result is `unanswered`.
8
+
9
+ **Free:** local sample and test keys. No card. Pass `createLocalAdapter()`, or omit `adapter` when you also omit `publishableKey`. The sample answers hours, returns, and shipping for Northwind Outfitters. It does not call the network.
10
+
11
+ **BYO ($9.99):** live app config. Knowledge, retrieval, the model, and visitor logs stay on the customer backend. Pass an `AnswersAdapter`, or `createByoAdapter(impl)`.
12
+
13
+ **Hosted ($19.99):** live app config, and Hosted stores approved sources, the derived index, visitor questions, optional conversations, unanswered items, and citations. Hosted includes 2,500 answers per workspace each month, 1 GiB of knowledge and index, 2,000 sources, 4,000 indexed chunks per app, 20 MiB per file, and 30 days of conversation history. Hitting a ceiling stops that axis.
14
+
15
+ Stack discount is company-wide: 25% off the second paid product's list, 35% off the third and later. Env names only: `STRIPE_PRICE_ANSWERS_BYO` and `STRIPE_PRICE_ANSWERS_HOSTED`. Do not invent Stripe price ids.
16
+
17
+ `@bootstrapware/answers` is published to npm: `pnpm add @bootstrapware/answers`. Non-React pages can still use the Hosted script embed. The Cursor Marketplace listing is separate and is not implied by this file. Do not invent a version or a tarball. The imports in this file are the package contract.
18
+
19
+ Publishable env: `NEXT_PUBLIC_BSW_ANSWERS_PUBLISHABLE_KEY`.
20
+
21
+ The React package does not call Hosted ask by itself. Passing `publishableKey` without `adapter` shows "This page is not ready to answer yet." and does not fetch.
22
+
23
+ ## Install algorithm (do this in order)
24
+
25
+ 1. Read this file. Call `list_answers_capabilities` on Answers MCP before inventing tools.
26
+ 2. If the user only needs a working UI in this app: Path A. Do not invent keys.
27
+ 3. If they need live Hosted ask: Path B.
28
+ 4. If they need their own backend: Path C.
29
+ 5. Never mint live or secret API keys via MCP or in the browser. For a test publishable key, call `ensure_answers_test_publishable`.
30
+
31
+ ### Path A: local, zero keys
32
+
33
+ ```tsx
34
+ import { Answers, createLocalAdapter } from "@bootstrapware/answers";
35
+ import "@bootstrapware/answers/styles.css";
36
+
37
+ <Answers adapter={createLocalAdapter()} />
38
+ ```
39
+
40
+ Try these questions, in order, on one session:
41
+
42
+ 1. `What is your return window?` Answered. Citation id `ane_returns`.
43
+ 2. `How long do I have?` Follow-up on that same session. A new session returns unanswered.
44
+ 3. `What are your hours?` Answered. Citation id `ane_hours`.
45
+ 4. `Is shipping free?` Answered. Citation id `ane_shipping`.
46
+ 5. `Do you price match?` Unanswered, with the sample fallback.
47
+
48
+ `onFallback` runs for unanswered results. The widget does not render an answer body for that outcome.
49
+
50
+ ### Path B: Hosted
51
+
52
+ 1. Confirm Cursor MCP `bootstrapware-answers` at `https://answers.bootstrapware.co/mcp` (OAuth URL-only). If disconnected, tell the human to click **Add to Cursor (OAuth)** on https://app.bootstrapware.co/answers/keys
53
+ 2. `list_answers_capabilities` → `create_answers_app` → `update_answers_draft` (toggles + `allowedOrigins` for localhost and production; `name` is optional) → `publish_answers_app`
54
+ 3. `ensure_answers_test_publishable`. Put `envLine` in `.env.local`. The full test publishable is returned every time. The env name is `NEXT_PUBLIC_BSW_ANSWERS_PUBLISHABLE_KEY`.
55
+ 4. `get_answers_install_snippet`. `appId` is real. Use `process.env.NEXT_PUBLIC_BSW_ANSWERS_PUBLISHABLE_KEY`. Never invent a key and never put a secret in the snippet.
56
+ 5. Keep the adapter in the snippet. It posts to `POST /api/v1/ask?appId=`. Omitting `adapter` does not call that route.
57
+ 6. `add_answers_url_source` for each dedicated public page whose body already states the fact (`/pricing`, hours, returns). Do not stop at the homepage.
58
+ 7. After the index job succeeds, tell the human to retest in the dashboard playground: a product question, a follow-up that names no product, and a question the pages do not answer.
59
+
60
+ `create_answers_app` returns these `nextSteps`, in order:
61
+
62
+ 1. Call `update_answers_draft` with `allowedOrigins` for localhost and production. Name is optional.
63
+ 2. Call `publish_answers_app`.
64
+ 3. Call `ensure_answers_test_publishable` to get or create a test publishable key.
65
+ 4. Call `get_answers_install_snippet`. The React package does not call Hosted ask unless you pass an adapter.
66
+ 5. Call `add_answers_url_source` with a dedicated public page whose body already states the fact. A homepage of product blurbs is a weak first source.
67
+ 6. After the index job succeeds, retest in the dashboard playground: a product question, a follow-up that names no product, and a question the pages do not answer.
68
+
69
+ `publish_answers_app` returns:
70
+
71
+ 1. Call `ensure_answers_test_publishable` to get or create a test publishable key.
72
+ 2. Call `get_answers_install_snippet` and point the adapter at `POST /api/v1/ask`.
73
+ 3. Call `add_answers_url_source` with a dedicated public page whose body already states the fact. A homepage of product blurbs is a weak first source.
74
+ 4. After the index job succeeds, retest in the dashboard playground: a product question, a follow-up that names no product, and a question the pages do not answer.
75
+
76
+ ```tsx
77
+ import { Answers, createByoAdapter } from "@bootstrapware/answers";
78
+ import "@bootstrapware/answers/styles.css";
79
+
80
+ <Answers
81
+ appId="ana_demo"
82
+ publishableKey={process.env.NEXT_PUBLIC_BSW_ANSWERS_PUBLISHABLE_KEY}
83
+ adapter={createByoAdapter({
84
+ ask: async (input) => {
85
+ const response = await fetch("https://answers.bootstrapware.co/api/v1/ask?appId=ana_demo", {
86
+ method: "POST",
87
+ headers: {
88
+ Authorization: `Bearer ${process.env.NEXT_PUBLIC_BSW_ANSWERS_PUBLISHABLE_KEY}`,
89
+ "Content-Type": "application/json",
90
+ },
91
+ body: JSON.stringify(input),
92
+ });
93
+ const json = await response.json();
94
+ if (!response.ok) throw new Error(json?.error?.message ?? "Could not ask.");
95
+ return json.data;
96
+ },
97
+ })}
98
+ />
99
+ ```
100
+
101
+ `get_answers_install_snippet` substitutes the published `ana_` id. Optional API host in the snippet defaults to `https://answers.bootstrapware.co`.
102
+
103
+ The ask body is `{ sessionId, question, pageContext }`. `pageContext` is `{ url, title }` only. The widget does not send the DOM. Success is `{ data }` with `outcome` `answered` or `unanswered`. An answered result includes `answer` and citations whose ids came from retrieval. An unanswered result includes `fallback` and no answer text. There is no public confidence percentage. The response is complete. It does not stream.
104
+
105
+ Live publishable config and live Hosted writes need a paid plan. Test keys work unpaid. Live Hosted writes need Hosted ($19.99). Live config needs BYO ($9.99) or Hosted ($19.99).
106
+
107
+ Public URL sources are `add_answers_url_source` with `kind` `website`, `sitemap`, or `url`. `reindex_answers_app` takes that same URL, or omits `url` to reindex the app. Neither tool accepts file bytes or a question.
108
+
109
+ ### Path C: BYO
110
+
111
+ Same MCP app config as Path B. Implement `AnswersAdapter` on the customer backend. `createByoAdapter(impl)` returns that adapter. Knowledge, retrieval, the model, and visitor logs never go to Bootstrapware.
112
+
113
+ `ask` receives `{ sessionId, question, pageContext }` and returns `{ sessionId, answerId, outcome, answer?, citations?, fallback? }`. `outcome` is `answered` or `unanswered`. Optional `feedback({ sessionId, answerId, helpful })`.
114
+
115
+ ```tsx
116
+ <Answers
117
+ appId="ana_demo"
118
+ adapter={createByoAdapter({
119
+ ask: async (input) => {
120
+ const response = await fetch("/api/answers/ask", {
121
+ method: "POST",
122
+ headers: { "Content-Type": "application/json" },
123
+ body: JSON.stringify(input),
124
+ });
125
+ if (!response.ok) throw new Error("Could not ask.");
126
+ return response.json();
127
+ },
128
+ })}
129
+ />
130
+ ```
131
+
132
+ The reference backend is `examples/answers-nextjs`. It reads markdown knowledge on the server, ranks chunks, and grounds the answer on retrieved ids. `OPENAI_API_KEY` stays in server env. Without that key, the route quotes a retrieved sentence and does not call a model.
133
+
134
+ Citations must be ids you retrieved. The widget links a URL only when that URL is on the returned citation. Other URLs stay text. HTML in the answer stays text.
135
+
136
+ ## Hosted MCP (Cursor)
137
+
138
+ HTTP MCP endpoint: `https://answers.bootstrapware.co/mcp`
139
+
140
+ **Preferred:** OAuth Connect, URL-only. Scope `answers:mcp`. **Add to Cursor (OAuth)** on https://app.bootstrapware.co/answers/keys
141
+
142
+ **Fallback:** test secret as `Authorization: Bearer`. Live and secret keys stay on the dashboard. Cursor connects through the Answers MCP endpoint. A Cursor marketplace listing is optional and may not be listed yet.
143
+
144
+ Tools:
145
+
146
+ - `list_answers_apps`
147
+ - `get_answers_app`
148
+ - `create_answers_app`
149
+ - `update_answers_draft` (does not publish; `name` optional)
150
+ - `publish_answers_app`
151
+ - `get_answers_published_config`
152
+ - `get_answers_install_snippet`
153
+ - `ensure_answers_test_publishable`
154
+ - `list_answers_capabilities`
155
+ - `add_answers_url_source` (public `http` or `https` URL only)
156
+ - `reindex_answers_app` (that URL, or the app when `url` is omitted)
157
+
158
+ **Dashboard-only (do not invent MCP tools):** live and secret key mint, webhooks, app delete, billing, file upload, manual sources, conversations, unanswered text, provider secrets, export, purge. Call `list_answers_capabilities`.
159
+
160
+ Publishing stores hosted app config. There is no `GET /api/v1/config/:appId` route. `POST /api/v1/ask` reads the published revision.
161
+
162
+ ## Config
163
+
164
+ Draft defaults: `assistantLabel` "Ask", `greeting` "Ask a question this site already answers.", `theme` "light", `presentation` "launcher", `fallbackMessage` "We could not find an answer in the published sources.", `allowedOrigins` `["*"]`, `retainHistory` true, `publicAnsweringEnabled` true, `allowCodeExcerpts` false.
165
+
166
+ `["*"]` is the CORS default for the ask route. It is not content authorization. `update_answers_draft` accepts `assistantLabel`, `greeting`, `theme`, `presentation`, `fallbackMessage`, `allowedOrigins`, `retainHistory`, `publicAnsweringEnabled`, and `allowCodeExcerpts`. It rejects questions, answers, file bytes, and provider keys.
167
+
168
+ `allowCodeExcerpts` defaults to false. How-to, install, and embed answers stay short sentences with a docs link. Set it true only when the app should quote code from a source.
169
+
170
+ `publicAnsweringEnabled: false` pauses new asks. Sources and history stay.
171
+
172
+ ## Props
173
+
174
+ - `appId`: published app id (`ana_`).
175
+ - `publishableKey`: browser publishable key. Unused for fetch unless your adapter uses it.
176
+ - `adapter`: `ask`, and optional `feedback`.
177
+ - `presentation`: `launcher` (default), `section`, `modal`, or `window`.
178
+ - `defaultOpen`: modal, window, and launcher start closed unless this is set.
179
+ - `theme`: `light` or `dark`. Omit it and the shell stays the light cream default: no `data-theme`, and no `prefers-color-scheme`.
180
+ - `pageContext`: `{ url, title }`. Omit it and the widget sends `location.href` and `document.title`.
181
+ - `onFallback`: called with the sanitized unanswered result.
182
+ - `className`: layout only.
183
+
184
+ Escape closes modal, window, and launcher. The backdrop closes modal and launcher. Nudge the corner with `--bsw-an-frame-launcher-right` and `--bsw-an-frame-launcher-bottom`.
185
+
186
+ Hosts may set `--bsw-an-bg`, `--bsw-an-panel`, `--bsw-an-fg`, `--bsw-an-muted`, `--bsw-an-line`, `--bsw-an-accent`, `--bsw-an-accent-fg`, and `--bsw-an-radius` on `.bsw-an` (with `theme="dark"`, `.bsw-an[data-theme="dark"]`).
187
+
188
+ ## Session
189
+
190
+ The widget stores `ans_` plus 24 hex in `sessionStorage` under `bsw_an_session:<appId>` (`local` when `appId` is omitted). It does not write a cookie. The adapter's returned `sessionId` does not replace that value. Citation sources start collapsed. The visitor opens the source count to read titles and excerpts.
191
+
192
+ Questions are limited to 1,000 Unicode code points. A longer draft stays in the composer and is not sent.
193
+
194
+ ## Script mount
195
+
196
+ `GET https://answers.bootstrapware.co/embed/v1.js` serves the versioned IIFE. A local build writes `dist/answers.iife.js` (gitignored).
197
+
198
+ ```html
199
+ <div id="answers"></div>
200
+ <script src="https://answers.bootstrapware.co/embed/v1.js"></script>
201
+ <script>
202
+ BootstrapwareAnswers.mount(document.getElementById("answers"), { presentation: "launcher" });
203
+ </script>
204
+ ```
205
+
206
+ `mount` returns an unmount function. Calling `mount` again on the same element updates props. `BootstrapwareAnswers.unmount(el)` removes it. The script is the same widget. Without an adapter, it does not call Hosted ask. Pass an adapter function from the page script when you want Path B or Path C.
207
+
208
+ ## Publish pages that can be quoted
209
+
210
+ Hosted quotes the body after it drops navigation chrome. It does not invent a fact that is only implied by a homepage card. `list_answers_capabilities` returns `sourceAdvice` with the same rules.
211
+
212
+ 1. Add a dedicated public URL per fact you want quoted (`/pricing`, hours, returns), not only the site root.
213
+ 2. Put the product name and the price in the same sentence. Example: `Answers Hosted is $19.99 per workspace each month.`
214
+ 3. Put each product's comparison on that product's page. Do not leave Chat vs Comments only on the homepage.
215
+ 4. After `add_answers_url_source`, wait for the index job, then retest in the dashboard playground: a product question, a follow-up that names no product (`What about BYO?`), and a question the pages do not answer.
216
+ 5. A citation excerpt that starts with `Skip to content` means the page had no usable body. Add a better URL, or reindex after the body is public.
217
+ 6. Unanswered is the correct outcome when the pages do not have the fact. Add a URL or a dashboard manual FAQ. Do not send the question through MCP.
218
+
219
+ ## Knowledge, grounding, and provider
220
+
221
+ Hosted knowledge kinds are website root, sitemap, single public URL, PDF, DOCX, TXT, Markdown, and manual FAQ. MCP can add a public URL and reindex it. Files, manual text, conversations, and unanswered question text stay on the dashboard.
222
+
223
+ Every Hosted source is visitor-readable. Do not upload private runbooks or secrets. Crawl is public `http` and `https` only. Private hosts are rejected. `robots.txt` is honored. A failed crawl leaves the last good index in place. A page that is only navigation chrome is rejected and does not become a citation.
224
+
225
+ Citations are ids from the retrieved revision. The model does not invent URLs. Unsupported and plausible-false questions return `unanswered` and the configured fallback. There is no model-memory fallback.
226
+
227
+ Unanswered observations store normalized question text even when conversation history is off, so the dashboard can group them. MCP does not return that text.
228
+
229
+ Hosted AI is managed OpenAI, or one Hosted BYOK OpenAI key. The secret route is `GET`, `PUT`, and `DELETE /api/v1/apps/:id/provider` with a secret key. `PUT` accepts only `{ "apiKey" }` and returns the key once. Later reads return a hint. MCP, the browser package, logs, and publishable config do not receive the key. Anthropic and Gemini are not in this version.
230
+
231
+ ## Pricing and retention
232
+
233
+ - Free: local mode and test keys
234
+ - BYO $9.99: live config; you store knowledge and visitor logs
235
+ - Hosted $19.99: Hosted stores approved sources, the derived index, visitor questions, optional conversations, unanswered items, and citations
236
+ - Cancel Hosted: writes freeze immediately. Dashboard JSON export for 30 days, then Hosted sources, index, conversations, and unanswered items are deleted
237
+ - Hosted to BYO keeps the rows and does not start the 30-day clock
238
+
239
+ ## Security
240
+
241
+ - Do not put `bsw_live_sec_` or `bsw_test_sec_` in client code, `NEXT_PUBLIC_*`, logs, or snippets.
242
+ - Do not send visitor questions, file bytes, or provider keys to MCP or webhooks.
243
+ - Publishable keys and `allowedOrigins` identify the workspace and apply CORS. They do not stop a script that replays the publishable key. Hosted ask still checks the key, the body size, the burst limits, and the monthly allowance before retrieval or a model call.
244
+ - Visitor question text and indexed documents are untrusted. Render the safe markdown subset only.
245
+ - Local and BYO question text stays in the browser or on the customer backend.
package/CHANGELOG.md ADDED
@@ -0,0 +1,14 @@
1
+ # Changelog
2
+
3
+ ## 0.1.0
4
+
5
+ - `<Answers>` with `appId`, `publishableKey`, `adapter`, `presentation`, `defaultOpen`, `theme`, `pageContext`, and `onFallback`.
6
+ - Presentations: `launcher` (default), `section`, `modal`, and `window`. Launcher and window use the corner host. Launcher and modal dim the page. Escape closes modal, window, and launcher.
7
+ - `createLocalAdapter` answers a Northwind Outfitters sample: a supported fact, a follow-up, and an unanswered fallback. No network.
8
+ - `createByoAdapter` returns the caller adapter.
9
+ - Safe answer markdown (bold, italic, code, lists). Citation links only. HTML stays text.
10
+ - Visitor `sessionId` in `sessionStorage` (`ans_` plus 24 hex). No cookie.
11
+ - IIFE entry `window.BootstrapwareAnswers.mount` from `dist/answers.iife.js` after `pnpm build`.
12
+ - Passing a publishable key without an adapter shows a not-ready state and does not fetch. Hosted ask is not in this version.
13
+ - Hosted included amounts are documented: 2,500 answers per workspace each month, 1 GiB of knowledge and index, 2,000 sources, 4,000 indexed chunks per app, 20 MiB per file, and 30 days of conversation history.
14
+ - Feedback may include an optional `reason`. Webhook payloads omit that text.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Bootstrapware
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,60 @@
1
+ # @bootstrapware/answers
2
+
3
+ Visitors ask a question. The widget shows a cited answer, or an explicit fallback when nothing matches.
4
+
5
+ **Local mode is free.** **BYO is $9.99 / month** (you store knowledge and answers). **Hosted is $19.99 / month.** Hosted stores approved sources, the derived index, visitor questions, optional conversations, unanswered items, and citations. Hosted includes 2,500 answers per workspace each month, 1 GiB of knowledge and index, 2,000 sources, 4,000 indexed chunks per app, 20 MiB per file, and 30 days of conversation history. Test keys are free. The React package does not call Hosted ask by itself. Pass `createLocalAdapter()` or your own adapter.
6
+
7
+ ```bash
8
+ pnpm add @bootstrapware/answers
9
+ ```
10
+
11
+ Non-React pages can use the Hosted script embed instead (see Script below).
12
+
13
+ Peer dependencies: `react` and `react-dom` (>= 18).
14
+
15
+ ## Local
16
+
17
+ ```tsx
18
+ import { Answers, createLocalAdapter } from "@bootstrapware/answers";
19
+ import "@bootstrapware/answers/styles.css";
20
+
21
+ <Answers adapter={createLocalAdapter()} />
22
+ ```
23
+
24
+ The default presentation is a closed corner launcher. `presentation` can be `launcher`, `section`, `modal`, or `window`. Modal, window, and launcher start closed unless `defaultOpen`.
25
+
26
+ The local adapter is a deterministic sample for Northwind Outfitters (hours, returns, shipping). It does not call the network.
27
+
28
+ ## Script
29
+
30
+ `pnpm build` writes `dist/answers.iife.js`. A non-React page can mount the same component:
31
+
32
+ ```html
33
+ <div id="answers"></div>
34
+ <script src="./dist/answers.iife.js"></script>
35
+ <script>
36
+ BootstrapwareAnswers.mount(document.getElementById("answers"), {});
37
+ </script>
38
+ ```
39
+
40
+ The hosted script is `GET https://answers.bootstrapware.co/embed/v1.js`.
41
+
42
+ ## BYO
43
+
44
+ ```tsx
45
+ <Answers
46
+ appId="ana_..."
47
+ adapter={createByoAdapter({
48
+ ask: async ({ sessionId, question, pageContext }) => {
49
+ const response = await fetch("/api/answers", {
50
+ method: "POST",
51
+ headers: { "Content-Type": "application/json" },
52
+ body: JSON.stringify({ sessionId, question, pageContext }),
53
+ });
54
+ return response.json();
55
+ },
56
+ })}
57
+ />
58
+ ```
59
+
60
+ Question text stays on your backend. `pageContext` is the page URL and title only.