@xano-sdk/chatbot 1.0.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,437 @@
1
+ # AGENTS.md
2
+
3
+ Instructions for coding agents (and humans) working **on** this repository.
4
+ For agents *consuming* the published package, see [llms.txt](llms.txt) instead.
5
+
6
+ ## What this is
7
+
8
+ `@xano-sdk/chatbot` ships a working conversational AI assistant as typed
9
+ [Xano SDK](https://www.npmjs.com/package/@xano/sdk) defs: two tables, a Xano AI
10
+ agent, one shared reply function, and two endpoint families. It exports plain def
11
+ objects; there is **no runtime** — the consumer's `Xano` instance registers and
12
+ encodes them. Everything is verified at the compiled-output level.
13
+
14
+ ## Commands
15
+
16
+ ```bash
17
+ npm run build # tsup → dist/ (esm + d.ts)
18
+ npm run typecheck # tsc --noEmit
19
+ npm run lint # eslint .
20
+ npm test # tsc --noEmit && vitest run (type-level tests need the typecheck)
21
+ ```
22
+
23
+ Run `npm run typecheck && npm run lint && npm test` before committing.
24
+
25
+ ### Never widen a stack
26
+
27
+ A query's `stack` must be a literal tuple. Two things silently destroy it:
28
+
29
+ - a helper returning `Statement[]`, spread into the stack;
30
+ - a conditional spread — `...(cond ? [x] : [])` — even inline.
31
+
32
+ Either one collapses the tuple, so every `as`/`ref()` in that stack (including
33
+ ones declared *after* the spread) resolves to `unknown` and the query's response
34
+ infers as `StackTupleWidened`. **Nothing in this repo fails when that happens** —
35
+ the bundle stays byte-identical and every runtime test passes. It surfaces only in
36
+ a consumer's typecheck, as a response type that quietly became useless.
37
+
38
+ Use `statements(...)` from core for a helper, and two explicit `statements(...)`
39
+ branches for a conditional. `test/types.test.ts` → "no endpoint's response is
40
+ widened away" is the regression guard; it asserts the negative directly, because
41
+ the positive is invisible here. A conditional spread inside a statement *field*
42
+ (e.g. `db.add`'s `data`) is fine — no inference depends on it.
43
+
44
+ ## Layout
45
+
46
+ - `src/options.ts` — the public option surface and `resolveOptions`, the single
47
+ validation gate. Every check lives there, before any def is built.
48
+ - `src/tables/*.ts`, `src/agent/*.ts`, `src/functions/*.ts`, `src/api/*.ts` — def
49
+ **factories**, one kind per module (the two endpoint families are one module
50
+ each; see below).
51
+ - `src/register.ts` — `createChatbot(opts)` builds everything;
52
+ `registerChatbot(xano, opts)` builds and registers, returning the same def set
53
+ with the instance on `.xano`. It must keep returning the defs: they are
54
+ factories, so that handle is a consumer's only route to the registered defs and
55
+ the only way a frontend can derive types without a runtime def import.
56
+ - `src/api/client-types.ts` — the per-endpoint request/response types consumers
57
+ import. Derived from the query handles via `ReturnType<typeof …Queries>`, which
58
+ is purely type-level, so nothing is built and nothing reaches a bundle.
59
+ - `src/index.ts` — the public surface.
60
+ - `test/*.test.ts` — encode-level fidelity assertions.
61
+ - `test/bundle.test.ts` + `test/fixtures/golden-bundle.json` — the byte-exact
62
+ bundle contract.
63
+ - `test/published-docs.test.ts` — the tarball contract.
64
+ - `scripts/regen-golden.ts` — regenerates the fixture (`npm run fixture:regen`).
65
+
66
+ ## Facts verified against a live instance
67
+
68
+ These were established by deploying probes to an ephemeral, not read from
69
+ documentation. Several are load-bearing and **not obvious from the SDK's source**,
70
+ so re-verify before changing anything that depends on them.
71
+
72
+ ### Agent tools work on ephemeral environments and instance workspaces
73
+
74
+ _Not yet re-probed on `@xano/sdk` 1.0.0._
75
+
76
+ Verified end-to-end: the chat agent calls tools through the ordinary `send`
77
+ endpoint and returns their values across both live ephemeral deploys
78
+ (`xanosdk deploy`) and instance workspaces. `s.tool.call` works as well.
79
+
80
+ (Note: An earlier ephemeral environment issue where toolsets were reported
81
+ missing or disabled has been resolved in the engine).
82
+
83
+ ### A tool the model is not told about is never called
84
+
85
+ With tools attached but no mention of them in the system prompt, the model
86
+ answered "I do not know the secret word" and never called the tool — the default
87
+ prompt's "say so rather than guessing" steers it away. Asked by name ("call the
88
+ probe_secret_word tool") it called it immediately and returned the value.
89
+
90
+ Hence `TOOLS_SYSTEM_PROMPT`, appended by `resolveOptions` whenever `tools` are
91
+ configured, to the caller's own prompt as well as the default.
92
+
93
+ ### Concurrent sends to one thread interleave (verified 2026-08-17)
94
+
95
+ Five parallel sends to one conversation produced the role pattern `uuuuuaaaaa`,
96
+ not `uauauauaua`: every user turn was written before any assistant turn, so each
97
+ model call read a history containing the other in-flight messages and replied to
98
+ several at once. Nothing was lost (10 rows, `created_at` non-decreasing) and no
99
+ guard failed — the send path simply has no per-thread lock, and cannot cheaply
100
+ have one across a slow model call.
101
+
102
+ Row ids were also NOT monotonic with `created_at` under that load, which is worth
103
+ remembering before writing any id-ordering assumption into a test or a stack.
104
+
105
+ Treated as a documented client-side responsibility (serialize sends per thread),
106
+ not a server fix. Revisit only if a cheap per-conversation lock appears.
107
+
108
+ ### An empty string is rejected as a MISSING param (re-verified 2026-08-17)
109
+
110
+ `input.text({ required: true })` refuses `""` exactly as it refuses an absent
111
+ param — `400 ERROR_CODE_INPUT_ERROR`, `"Missing param: <name>"`, **before the
112
+ stack runs**. Checked on all three guest endpoints, query-string and JSON-body
113
+ forms alike.
114
+
115
+ This CONTRADICTS the earlier finding that `required: true` accepts an empty
116
+ string, which is why `capabilityGuard`'s first precondition exists. The
117
+ precondition is therefore currently unreachable. It is kept on purpose — see the
118
+ comment at `src/api/guest.ts`. Re-verify before acting on either result: this is
119
+ engine behaviour that has already changed once.
120
+
121
+ ### The engine really does decode the `messages` template into LLM roles
122
+
123
+ Two otherwise-identical agents, one `prompt` and one `messages`, same payload:
124
+
125
+ | payload | `prompt` | `messages` |
126
+ |---|---|---|
127
+ | valid `[{role,content}]` | works, 110 input tokens | works, **89** |
128
+ | plain text, not an array | works | `ERROR_FATAL` |
129
+ | malformed JSON | works, echoes syntax back | `ERROR_FATAL` |
130
+ | `[]` | works | `ERROR_FATAL` |
131
+ | role outside the provider's set | works | `ERROR_FATAL` |
132
+ | `system` role in the array | — | works, and is honored |
133
+ | extra keys (`id`, `created_at`) | — | ignored |
134
+
135
+ The refusals are the proof — a literal string cannot be malformed — and the token
136
+ gap is the quantitative version: JSON punctuation and the `"role"`/`"content"`
137
+ keys never reach the model. **Do not "simplify" the agent to `prompt`.**
138
+
139
+ ### An empty `content` does not error — it confabulates
140
+
141
+ A blank user turn produced a reply inventing an entire fictional prior exchange
142
+ about correcting the word "mispelled". This is why `content` carries `min:1` at
143
+ the column and a precondition in the reply function. A confabulated turn written
144
+ into the transcript poisons every later turn's context, and nothing surfaces it.
145
+
146
+ ### `auth` on a query is binary, and `auth()` in a public stack raises
147
+
148
+ | endpoint | request | result |
149
+ |---|---|---|
150
+ | `auth` set | no token | `401 Unauthorized` |
151
+ | public | no token | `auth()` → `ACCESS_DENIED` |
152
+ | public | **valid** token | `auth()` → `ACCESS_DENIED` |
153
+
154
+ Older SDK docs said `auth()` is `null` on a public endpoint; it is not — it raises
155
+ (the SDK now documents this).
156
+ This is the entire reason there are two endpoint families. **Do not merge them.**
157
+
158
+ ### A tool with no per-tool `auth` is a PUBLIC stack, and fails in silence
159
+
160
+ The most expensive defect this package has shipped (live build log,
161
+ 2026-08-31). Eight tools were passed the way every other SDK collection takes
162
+ handles — `tools: [saveNote]` — and everything downstream looked right: the
163
+ bundle carried all eight against the agent with resolved guids and
164
+ `enabled: true`, and the deploy succeeded. Then every tool threw
165
+ `ERROR_CODE_ACCESS_DENIED` on its first statement, the agent swallowed the
166
+ throw, and the model answered *"I saved a note that the office door code is
167
+ 4821"* while nothing was written.
168
+
169
+ The cause is the row above: a bare handle encodes `auth: false`, a bare handle
170
+ is therefore a PUBLIC tool stack, and `auth("id")` in a public stack raises
171
+ rather than resolving to null.
172
+
173
+ What made it expensive is that the failure is invisible from every surface an
174
+ agent can reach — no 500, no error in the reply, no build or export warning, and
175
+ the model's confident sentence is itself misleading evidence. Finding the error
176
+ string took a temporary `debug_event` table, a probe tool wrapping `auth("id")`
177
+ in `s.try_catch`, a public read endpoint, and three deploy cycles.
178
+
179
+ `resolveOptions` now gives every tool entry that names no `auth` the configured
180
+ `authTable` (`src/options.ts` → `applyDefaultToolAuth`), so the bare form works.
181
+ `{ tool, auth: false }` is the explicit opt-out and the only spelling that still
182
+ produces a public tool. Enabling both endpoint families with scoped tools warns:
183
+ one agent serves both and an entry carries one `auth`, so a scoped tool has no
184
+ identity to bind on a public guest send.
185
+
186
+ **Verified on ephemeral `emed-ivuz-9b4e`, 2026-09-01** — `local-files/probe/`
187
+ reproduces the log's shape exactly (bare handles, `auth("id")` as each tool's
188
+ first statement). Through the ordinary `send` endpoint: "Save a note that the
189
+ office door code is 4821" now WRITES the row, with `user_id` bound to the
190
+ caller. A second user asking the same agent to list their notes gets `[]` and
191
+ the row of the first user is invisible to them, so the scoping is real and not
192
+ merely present in the bundle.
193
+
194
+ ### Tool calls live in `steps[].content[]` — the top-level `toolCalls` is always empty
195
+
196
+ From the same log. `ChatReply.tool_calls` was null on every send, which reads as proof
197
+ that no tool ran — the worst possible answer during the debugging session above,
198
+ where every tool HAD run and thrown.
199
+
200
+ The response bound `ref("run.tool_calls")`, a key the agent-run envelope does not
201
+ carry. Core types the envelope as `AgentRunResult` — `result`, `finishReason`,
202
+ `steps`, `toolCalls`, `usage` — so `toolCalls` looked like the answer. It is not.
203
+ **Probed live (ephemeral `emed-ivuz-9b4e`, 2026-09-01): the top-level `toolCalls` is
204
+ present and always `[]`, even on a run whose tool wrote a row.** The calls are one
205
+ level down:
206
+
207
+ ```jsonc
208
+ steps[0].content[0] = { "type": "tool-call", "toolCallId": "…", "toolName": "save_note",
209
+ "input": { "body": "…" } }
210
+ steps[0].content[1] = { "type": "tool-result", "toolCallId": "…", "toolName": "save_note",
211
+ "input": { … }, "output": { "id": 2, "user_id": 2, … } }
212
+ steps[1].content[0] = { "type": "text", "text": "OK. I've saved that." }
213
+ ```
214
+
215
+ So the reply function reads `run|get:"steps",[]` and loops: per step, `index_by("type")`
216
+ then `get("tool-call", [])` — both entry types carry `toolName`, so without the split
217
+ every call is reported twice — then maps to the name and `array_merge`s onto an
218
+ accumulator. Names only: a tool-result carries the tool's whole return value, which is
219
+ data no client asked for.
220
+
221
+ **`fl.map` with a JS lambda does the same job in one filter, and was verified to work.**
222
+ It is not used: core is explicit that a lambda is an escape hatch drawing on a bounded
223
+ workspace-wide worker pool, and every send in every install would pay for work four
224
+ ordinary statements express. `fl.flatten()` is not an option either — it flattens all
225
+ the way to scalar VALUES, so a list of step-content objects comes back as 15 strings.
226
+
227
+ Verified end to end through the real `send` endpoint: two `save_note` calls and a
228
+ `list_notes` in one turn returned `["save_note","save_note","list_notes"]` — order kept,
229
+ duplicates kept — and a turn the model answered directly returned `[]`.
230
+
231
+ ### Route names are not verb pairs
232
+
233
+ The SDK composes a query's identity from `(api group, verb, name)`, and
234
+ `xanosdk routes --emit` keys its manifest on `"<VERB> <name>"`, so `GET foo` and
235
+ `POST foo` do not collide. The route names stay distinct and `routePrefix` stays
236
+ anyway: a query's identity includes its name, so a rename moves every endpoint's
237
+ identity and every consumer's `xano.lock` with it.
238
+
239
+ ⚠ **The dev pin matters.** The golden fixture is generated against
240
+ `devDependencies`, and query guids derive from the SDK's identity rules. Bumping
241
+ the pin is a reviewed act with a real fixture diff behind it — not drift to
242
+ regenerate past.
243
+
244
+ ### Reproducing any of this
245
+
246
+ ```bash
247
+ xanosdk deploy ./xano/index.ts --name probe --expires-hours 2
248
+ ```
249
+
250
+ If `deploy` reports `must be owner of table mvpw1_N` it is refusing to re-import
251
+ over an existing ephemeral; run `xanosdk ephemeral delete probe` (or delete
252
+ `.xano/ephemeral.json`) and deploy again to get a fresh one.
253
+
254
+ ## Rules that bite
255
+
256
+ - **The defs are FACTORIES, not module singletons.** `f.tableRef` resolves its
257
+ target's guid eagerly at column-construction time, so a module-level
258
+ `conversation` def would bake in one auth table forever. This is the one
259
+ structural divergence from `@xano-sdk/auth`, and everything else follows from it —
260
+ including the *absence* of auth's ~100 lines of canonical/history conflict
261
+ guards, which exist only because its group is a process-wide singleton. Do not
262
+ "restore symmetry" with auth by hoisting a def to module scope.
263
+ - **`registerChatbot` still needs its WeakSet.** For a different reason than
264
+ auth's: core's duplicate-def guard compares def *identity*, and two
265
+ `createChatbot` calls produce distinct objects sharing names, so the mistake
266
+ slips past core and lands at `export()` as `Duplicate object guid … shared by
267
+ "dbo/conversation" and "dbo/conversation"` — naming neither call.
268
+ - **Nothing may import `@xano-sdk/auth`.** Not as a dependency, not as a
269
+ peer dependency, not in an example that would make it load-bearing. `authTable`
270
+ is the whole integration surface. A test that needs an auth table declares one.
271
+ - **References use def handles, never bare names**, inside the package. The
272
+ *consumer* may pass a bare name for `authTable`; everything this package
273
+ references itself (the conversation table from the message table, the reply
274
+ function from the send endpoints) uses the handle.
275
+ - **Pin no guids, and no canonical by default.** Identity belongs to the
276
+ consumer's `xano.lock` (fallback: `md5("<kind>:<name>")`; queries seed on
277
+ group + verb + name). The one exception is
278
+ the opt-in `{ canonical }`, which the consumer supplies.
279
+ - **Security defaults are not preferences.** Request history off; `session_token`
280
+ `internal` and uniquely indexed; the blank-token precondition; `notfound` rather
281
+ than `accessdenied` on someone else's thread; the unclaimed check on both the
282
+ guest guard and the claim endpoint. Each has a comment naming the failure it
283
+ prevents. Changing one needs a stated reason, not a tidier-looking stack.
284
+ - **The authenticated create mints a `session_token` when guests are on.** Not
285
+ redundant — an omitted column takes its type default (`""`) on `db.add`, so the
286
+ *second* authenticated create used to die on the unique index, and every
287
+ authenticated row sharing `""` made an empty guest token match a logged-in
288
+ user's thread. Caught live; do not remove it as dead code.
289
+ - **`DEFAULT_SYSTEM_PROMPT` asks for light markdown on purpose.** It is not
290
+ filler: the README tells frontends to render `reply` as markdown, and that only
291
+ works if the model is told to produce it — the two are halves of one decision, so
292
+ do not drop the clause from one side alone. "Light" is also deliberate; a model
293
+ told plainly to "use markdown" reaches for headings and tables in a two-line
294
+ answer, which reads worse in a chat bubble than prose. The `ChatReply.reply`
295
+ doc and README both carry the matching warning that a rendered reply is
296
+ model output shaped by user input, so the renderer must keep raw HTML off.
297
+ - **`llm.prompt` / `llm.messages` are refused**, in the type and at runtime. The
298
+ package owns the run prompt because that is how the transcript is delivered, and
299
+ Xano stores one prompt behind a `prompt_type` discriminator, so a supplied one
300
+ would replace the history rather than add to it.
301
+ - **Values stay explicit `c.*`, never bare literals**, matching `@xano-sdk/auth`.
302
+ Core coerces raw literals inside some maps; keeping the tag is load-bearing
303
+ where a constant is a magic string the engine interprets (`c.text("now")`).
304
+ - **The two endpoint families live one module each**, unlike auth's
305
+ one-def-per-module layout. Five separate modules here would be five copies of an
306
+ identical five-parameter factory signature; the shared authorization idiom
307
+ (`ownershipGuard` / `capabilityGuard`) is what justifies the grouping.
308
+
309
+ ## Auto-wiring
310
+
311
+ `package.json` carries a `"xanosdk"` block (`register: registerChatbot`, `returns: handle`,
312
+ `options.authTable` → `@xano-sdk/auth`'s `userTable`) that `xanosdk init --marketplace`
313
+ reads to write the registration into `xano/index.ts`. The SDK offers it only when
314
+ `@xano-sdk/auth` is also installed; otherwise it prints the call and leaves it unwired.
315
+ `test/manifest.test.ts` pins the block against the real export.
316
+
317
+ ## The peer range
318
+
319
+ `@xano/sdk` is a `peerDependency` with the window `>=<floor> <2.0.0` — the floor is
320
+ currently `1.0.0` and the ceiling is the next major. `devDependencies` carries the
321
+ version actually tested, pinned **exactly** (no caret) because it is the single
322
+ version the golden fixture was generated against.
323
+
324
+ Verify the floor by installing it and running the suite, rather than copying the
325
+ number forward:
326
+
327
+ - `security.create_uuid` is the uuid helper; `create_guid` was removed and must
328
+ not come back.
329
+ - The reply function's `responseShape: {} as ChatReply` is **load-bearing**.
330
+ Without it, the `function.run` brand in each send endpoint carries the reply
331
+ function's whole derived response type, which is too deep for tsup's dts emit
332
+ (a location-less TS2589 that fails `npm run build`) and for a consumer's
333
+ `bot.queries.map((q) => …)`. Plain `tsc --noEmit` does not catch the build
334
+ half — run `npm run build` on every bump.
335
+
336
+ Do not raise the floor without a reason: needlessly raising it forces consumers
337
+ into an upgrade that buys them nothing. `test/helpers.ts` deliberately computes
338
+ `md5("<kind>:<name>")` itself rather than importing `deriveGuid` from
339
+ `@xano/sdk/internal` — importing it would raise the floor for every consumer to
340
+ satisfy a test.
341
+
342
+ ## Versions
343
+
344
+ Versions start at 1.0.0 under `@xano-sdk/chatbot` and only 1.0.x increments for
345
+ now, regardless of the change. Do not bump unless told to.
346
+
347
+ ## The golden-bundle contract
348
+
349
+ `test/bundle.test.ts` registers everything on a fresh `Xano`, calls `export()`,
350
+ and deep-equals the result against `test/fixtures/golden-bundle.json` (raw, no
351
+ normalizer). This is the peer-drift tripwire.
352
+
353
+ The golden config turns **everything** on — both families, a pinned canonical, a
354
+ keyed provider — because a tripwire only guards what it encodes. `bundle.test.ts`
355
+ has a second block asserting that coverage, so the config cannot quietly narrow.
356
+
357
+ Regenerating is a deliberate, reviewed act — never a way to make a red test pass.
358
+ A failure means the encoded bundle moved; find out *why* first.
359
+
360
+ ```bash
361
+ npm run fixture:regen && git diff test/fixtures/golden-bundle.json
362
+ ```
363
+
364
+ Review that diff line by line (watch guids, auth flags, stack order, output lists,
365
+ `prompt_type`) before committing.
366
+
367
+ ## Release
368
+
369
+ Lockstep with the peer. For each SDK bump:
370
+
371
+ 1. Read the SDK's `CHANGELOG.md` entries between the two versions, then its
372
+ `llms.txt` diff, before touching anything — a green suite proves no encoding drift, not that the
373
+ package still follows current guidance.
374
+ 2. Move the `devDependencies` pin. Move the `peerDependencies` floor **only** if a
375
+ new core behaviour or type became load-bearing here, and *verify* it by
376
+ installing that version and running the suite.
377
+ 3. Run `npm run typecheck && npm run lint && npm test`. Regenerate the fixture
378
+ only if the bundle legitimately changed. An unchanged fixture is the expected
379
+ outcome of most bumps, not a reason to look harder.
380
+ 4. Re-run the live probes if anything touched the agent, the reply function, or an
381
+ authorization guard. The encode-level suite cannot catch a change in what the
382
+ *engine* does with the bytes.
383
+ 5. Update the install notes in `README.md` **and** `llms.txt` with both numbers.
384
+ 6. Ship it. Only 1.0.x increments for now, and only when told to bump.
385
+
386
+ ```bash
387
+ npm run release:beta # prerelease: bumps, tags, publishes under `beta`
388
+ ```
389
+
390
+ The stable path does **not** bump for you — `npm run release` only publishes.
391
+ Bump in the PR that carries the change, so the version reviewers approve is
392
+ the version that ships:
393
+
394
+ ```bash
395
+ npm version patch --no-git-tag-version # in the PR
396
+ ```
397
+
398
+ Then, after the PR merges, from a green tree on the default branch:
399
+
400
+ ```bash
401
+ git tag "v$(node -p 'require("./package.json").version')"
402
+ npm run release
403
+ git push --tags
404
+ ```
405
+
406
+ `npm pack --dry-run` should show exactly 7 files (`dist/` ×2 — no source map,
407
+ `README.md`, `AGENTS.md`, `llms.txt`, `LICENSE`, `package.json`).
408
+ `test/published-docs.test.ts` pins that list and checks every relative link in
409
+ a shipped doc resolves inside the tarball.
410
+
411
+ ### Release notes
412
+
413
+ Start from [.github/RELEASE_TEMPLATE.md](https://github.com/xanots/chatbot/blob/main/.github/RELEASE_TEMPLATE.md) — it carries
414
+ the shape and the constraints the Slack announcement imposes, and its guidance
415
+ lives in HTML comments stripped before Slack sees them.
416
+
417
+ - The GitHub release **name** (not the tag) becomes the Slack header verbatim:
418
+ `vX.Y.Z — Three-to-five word theme`.
419
+ - Everything before the first `##` is the summary block. No story — a brief
420
+ paragraph and the install snippet.
421
+ - After the summary, one itemized title and short description per change. Anything
422
+ a consumer must act on (a breaking type, a peer-range move, a migration) gets
423
+ called out there too, not left for the reader to infer.
424
+ - **Each change gets its own `##` heading**, because those become the itemized
425
+ Slack bullets (first 8 shown). Write each as a claim that survives with no body
426
+ text under it. Purely structural headings (Notes, Compatibility, …) are dropped
427
+ from the bullets, so use them freely — just never hide a change under one.
428
+
429
+ Publishing a release fires `.github/workflows/release-slack.yml`, which runs
430
+ `.github/scripts/test_slack_release_message.py` in the same job that posts, so a
431
+ malformed payload fails the workflow rather than reaching Slack. Both the builder
432
+ and that test are kept identical to `xanots/sdk`'s and `xanots/auth`'s modulo the
433
+ repo and package names; port fixes between them rather than letting them diverge.
434
+
435
+ ```bash
436
+ cd .github/scripts && python3 test_slack_release_message.py
437
+ ```
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Xano, Inc.
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.