diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 17d84fc..6762011 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -2,9 +2,9 @@ name: CI
on:
push:
- branches: [main]
+ branches: [main, feature/phase1-5]
pull_request:
- branches: [main]
+ branches: [main, feature/phase1-5]
jobs:
build-test:
@@ -22,6 +22,10 @@ jobs:
with:
node-version: 22
+ - name: Widget tests
+ run: npm ci && npm test
+ working-directory: npm/botwire-js
+
- name: Build (Release, warnings as errors)
run: dotnet build BotWire.slnx -c Release
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index 2004eb4..71f5a2b 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -65,3 +65,56 @@ jobs:
args+=(--notes-file "$notes")
fi
gh release create "$GITHUB_REF_NAME" artifacts/*.nupkg artifacts/*.snupkg "${args[@]}"
+
+ npm:
+ needs: release # only publish the JS SDK once the .NET release succeeded
+ runs-on: ubuntu-latest
+ permissions:
+ contents: read
+ id-token: write # npm provenance (public-repo supply-chain attestation)
+ defaults:
+ run:
+ working-directory: npm/botwire-js
+ steps:
+ - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
+
+ - name: Verify tag matches package.json version
+ run: |
+ VERSION="${GITHUB_REF_NAME#v}"
+ PKG=$(node -p "require('./package.json').version")
+ if [ "$VERSION" != "$PKG" ]; then
+ echo "Tag $GITHUB_REF_NAME does not match botwire-js version $PKG in package.json" >&2
+ exit 1
+ fi
+
+ - name: Setup Node
+ uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
+ with:
+ node-version: 22
+ registry-url: https://registry.npmjs.org
+
+ - name: Install
+ run: npm ci
+
+ - name: Build (SDK + widget)
+ run: npm run build
+
+ - name: Test
+ run: npm test
+
+ # Trusted Publishing (OIDC) needs a newer npm than Node 22 ships with.
+ - name: Upgrade npm
+ run: npm install -g npm@latest
+
+ # No token: authenticates via OIDC against the trusted publisher configured
+ # for botwire-js on npmjs.com. Provenance is attached automatically.
+ # Idempotent: npm has no --skip-duplicate, so skip if the version already
+ # exists (e.g. a manually bootstrapped first publish) to keep re-runs green.
+ - name: Publish to npm (Trusted Publishing, OIDC)
+ run: |
+ VERSION=$(node -p "require('./package.json').version")
+ if npm view "botwire-js@$VERSION" version >/dev/null 2>&1; then
+ echo "botwire-js@$VERSION already published; skipping."
+ else
+ npm publish --access public
+ fi
diff --git a/Directory.Build.props b/Directory.Build.props
index 4b139d4..a2f30a6 100644
--- a/Directory.Build.props
+++ b/Directory.Build.props
@@ -5,7 +5,7 @@
latest
- 0.1.0
+ 0.2.0
Object IT Limited
diff --git a/NUGET.md b/NUGET.md
new file mode 100644
index 0000000..a96254f
--- /dev/null
+++ b/NUGET.md
@@ -0,0 +1,48 @@
+# BotWire
+
+**A 24/7 AI customer-support bot for your .NET app — self-hosted, bring-your-own-key, no SaaS fees.**
+
+Drop one package into your ASP.NET Core site, point it at your FAQ, and ship a support assistant that answers customers instantly from *your* docs — and quietly opens a human ticket the moment one is actually needed. You supply an OpenAI-compatible API key, so your only running cost is model tokens (pennies per conversation on `gpt-4o-mini` or DeepSeek).
+
+## Two lines to wire it up
+
+```csharp
+builder.Services.AddBotWire(opts =>
+{
+ opts.TopicDescription = "Online store customer support";
+ opts.Documents = ["docs/faq.md"];
+ opts.ChatProvider = new OpenAIProviderOptions { ApiKey = "sk-...", Model = "gpt-4o-mini" };
+});
+
+app.MapBotWire();
+```
+
+Embed the zero-dependency widget on any page:
+
+```html
+
+
+```
+
+## What you get
+
+- **Grounded answers.** Replies come only from the Markdown knowledge base you supply — no hallucinated policies, prices, or promises.
+- **Knows when to get a human.** Collects contact details and raises a support ticket (emailed to your team) instead of guessing.
+- **Streaming chat widget.** ~12 KB Web Component, Shadow DOM, no framework, one `