@lovelaces-io/storyteller 0.3.0 → 0.4.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 +42 -1
- package/README.md +30 -0
- package/dist/index.cjs +454 -20
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +55 -515
- package/dist/index.d.ts +55 -515
- package/dist/index.js +440 -17
- package/dist/index.js.map +1 -1
- package/dist/store/file.cjs +513 -0
- package/dist/store/file.cjs.map +1 -0
- package/dist/store/file.d.cts +29 -0
- package/dist/store/file.d.ts +29 -0
- package/dist/store/file.js +478 -0
- package/dist/store/file.js.map +1 -0
- package/dist/stories-BgN4BSoZ.d.cts +744 -0
- package/dist/stories-BgN4BSoZ.d.ts +744 -0
- package/llms.txt +11 -1
- package/package.json +16 -9
package/AGENTS.md
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
Storyteller (`@lovelaces-io/storyteller`) is a lightweight TypeScript logging library with zero production dependencies. You report beats of work as they happen; it keeps them and emits them as one structured record when the work finishes. Records go to pluggable audiences.
|
|
6
6
|
|
|
7
|
-
Version: 0.3.
|
|
7
|
+
Version: 0.3.1 (pre-1.0, API may change). Dual output: ESM + CJS.
|
|
8
8
|
|
|
9
9
|
## Narrate your work
|
|
10
10
|
|
|
@@ -84,6 +84,10 @@ Values dropped for size are replaced with an explicit `{ "@truncated": { kind, o
|
|
|
84
84
|
|
|
85
85
|
The normalizer never throws. A hostile object cannot break the pipeline.
|
|
86
86
|
|
|
87
|
+
## Secrets
|
|
88
|
+
|
|
89
|
+
Redaction happens at capture and again at the storage boundary: values under secret-named keys (`password`, `apiKey`, `dbPassword`, `x-api-key`), and recognisable secret formats inside any string (Stripe/OpenAI/GitHub keys, JWTs, PEM blocks, `Bearer …`, passwords in URLs, `?token=`), become `[redacted]` — error messages and stacks included. It is defense in depth, not a guarantee: a secret that looks like a word passes. Do not report a secret and rely on redaction; do not turn `redact` off in code that persists. `auditRedaction(value)` shows what would be removed, so check a real corpus before trusting coverage. `redactValues: "strict"` trades some legitimate content for more coverage.
|
|
90
|
+
|
|
87
91
|
## Output a program can read
|
|
88
92
|
|
|
89
93
|
For machine consumption, use NDJSON — one JSON object per line, nothing else on the channel:
|
|
@@ -185,9 +189,42 @@ Built in: `consoleAudience()` (notes and stories, registered by default), `dbAud
|
|
|
185
189
|
|
|
186
190
|
When an audience throws, the failure is reported through `onAudienceError` rather than swallowed, and never propagates into your code. When an audience is too slow, emissions past `maxInFlight` are dropped and counted in `droppedEmissions` on the closing story — so the loss shows up in the record instead of vanishing.
|
|
187
191
|
|
|
192
|
+
## Keeping stories, and reading them back
|
|
193
|
+
|
|
194
|
+
A story is the unit of retrieval: complete, ordered, small enough for a context window. A `StoryStore` keeps them and answers structured questions; `storeAudience` is the one-line bridge from delivery.
|
|
195
|
+
|
|
196
|
+
```typescript
|
|
197
|
+
import { memoryStore, stories, storeAudience } from "@lovelaces-io/storyteller";
|
|
198
|
+
import { fileStore } from "@lovelaces-io/storyteller/store/file"; // Node only
|
|
199
|
+
|
|
200
|
+
const kept = fileStore("./stories.jsonl"); // or memoryStore() for a browser, a test, one run
|
|
201
|
+
story.audience.add(storeAudience(kept));
|
|
202
|
+
|
|
203
|
+
await stories(kept).about("checkout").failing().since("24h"); // reads like the question
|
|
204
|
+
await stories(kept).slowerThan("5s").since("7d").oldest().limit(10);
|
|
205
|
+
await stories(kept).under(storyId); // its chapters
|
|
206
|
+
await kept.prune(new Date(Date.now() - 30 * 86_400_000));
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
`stories(store)` is the vocabulary: `about`, `from`, `level`, `atLeast`, `failing`, `succeeding`, `slowerThan`, `since`, `until`, `under`, `newest`, `oldest`, `limit`, `skip`; then `all()`, `first()` or `count()`, or just `await` it. It compiles to a `StoryQuery` object, never a string. `about` searches title, note text, scalar context and error messages; `from` searches the origin. To write an adapter for a database, store `canonicalRow(story)` and make its query agree with `matchesQuery` — that agreement is the contract.
|
|
210
|
+
|
|
211
|
+
## Rendering stories for humans
|
|
212
|
+
|
|
213
|
+
Stories are JSON so programs can read them. When a person needs to read one — an admin log screen, a terminal, a chat transcript — use `@lovelaces-io/storyteller-view`, a separate zero-dependency package from the same repo:
|
|
214
|
+
|
|
215
|
+
```ts
|
|
216
|
+
import { renderStory, renderStoryText } from "@lovelaces-io/storyteller-view";
|
|
217
|
+
|
|
218
|
+
renderStory(event); // HTMLElement: timeline of notes, context trees, error with cause chain
|
|
219
|
+
renderStoryText(event, { colors: true }); // string: the same layout as indented text
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
Both accept a `StoryEvent`, a `NoteEmission`, or a stored record read back from a database or NDJSON. Every marker the normalizer leaves (`@type`, `@truncated`, `[Circular → path]`, `[redacted]`) is shown as what it means rather than as a string, and the DOM renderer inserts text nodes only — never `innerHTML` — because story content is user input. Do not hand-roll a story renderer in an app; reach for the view and theme it with the `--stv-*` custom properties.
|
|
223
|
+
|
|
188
224
|
## Architecture
|
|
189
225
|
|
|
190
226
|
```
|
|
227
|
+
packages/core/
|
|
191
228
|
src/
|
|
192
229
|
storyteller.ts — core class, types, event building, delivery
|
|
193
230
|
normalize.ts — turns any value into something storable
|
|
@@ -207,6 +244,10 @@ snippets/
|
|
|
207
244
|
agents-section.md — the guidance block consumers paste into their AGENTS.md
|
|
208
245
|
```
|
|
209
246
|
|
|
247
|
+
The repository is a workspace: this package is `packages/core`; add-on packages with
|
|
248
|
+
real dependencies (a SQLite store, an MCP server) live beside it under `packages/`
|
|
249
|
+
and are never imported by core.
|
|
250
|
+
|
|
210
251
|
`snippets/agents-section.md` is the single source for that block. It is embedded
|
|
211
252
|
verbatim in README.md and written by `storyteller init`, and `npm run check:snippet`
|
|
212
253
|
fails the build if the copies drift or it outgrows its 40-line budget. Edit the
|
package/README.md
CHANGED
|
@@ -248,6 +248,33 @@ story.audience.add({
|
|
|
248
248
|
|
|
249
249
|
When an audience throws, the failure is reported rather than swallowed, and never reaches your code. When one is too slow, emissions past `maxInFlight` are dropped and counted in `droppedEmissions` on the closing record — visible loss beats silent loss.
|
|
250
250
|
|
|
251
|
+
## Keep Them, Ask Them
|
|
252
|
+
|
|
253
|
+
A story is the unit of retrieval: complete, ordered, small enough for a context window. Keep them in a store and the question *why did last night's sync fail?* has somewhere to look.
|
|
254
|
+
|
|
255
|
+
```ts
|
|
256
|
+
import { stories, storeAudience } from "@lovelaces-io/storyteller";
|
|
257
|
+
import { fileStore } from "@lovelaces-io/storyteller/store/file";
|
|
258
|
+
|
|
259
|
+
const kept = fileStore("./stories.jsonl"); // or memoryStore() anywhere
|
|
260
|
+
story.audience.add(storeAudience(kept));
|
|
261
|
+
|
|
262
|
+
await stories(kept).failing().since("1h");
|
|
263
|
+
await stories(kept).about("checkout").from("payment-service").level("oops").since("24h");
|
|
264
|
+
await stories(kept).slowerThan("5s").since("7d").oldest().limit(10);
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
Ask in words; every store answers the same question. Then let an agent ask: **the Librarian** is a read-only MCP server over any store.
|
|
268
|
+
|
|
269
|
+
```jsonc
|
|
270
|
+
// .mcp.json
|
|
271
|
+
{ "mcpServers": { "storyteller": { "command": "npx", "args": ["-y", "@lovelaces-io/storyteller-mcp", "./stories.jsonl"] } } }
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
Redaction runs at capture and again at the storage boundary — secret-named keys and recognisable secret formats inside any string — so what is kept is what is safe to keep. Defense in depth, not a guarantee; [SECURITY.md](./SECURITY.md) says exactly what it does not promise.
|
|
275
|
+
|
|
276
|
+
For a person, `@lovelaces-io/storyteller-view` renders a story as a timeline, in a page or a terminal: [storyteller.lovelaces.io/docs/view](https://storyteller.lovelaces.io/docs/view).
|
|
277
|
+
|
|
251
278
|
## Configuration
|
|
252
279
|
|
|
253
280
|
Every option can also come from the environment, so you can change behavior without touching code:
|
|
@@ -300,6 +327,9 @@ const story = useStoryteller({ origin: { who: "worker" } });
|
|
|
300
327
|
- [How It Works](docs/HOW-IT-WORKS.md) — narrative guide
|
|
301
328
|
- [Changelog](CHANGELOG.md)
|
|
302
329
|
- [For AI Agents](AGENTS.md) — guidance for AI coding assistants
|
|
330
|
+
- [The Library](https://storyteller.lovelaces.io/docs/library) — keep stories, ask in words, let an agent read them back
|
|
331
|
+
- [Story view](https://storyteller.lovelaces.io/docs/view) — `@lovelaces-io/storyteller-view`, stories rendered for humans
|
|
332
|
+
- [The Librarian](packages/mcp/README.md) — `@lovelaces-io/storyteller-mcp`, the read-only MCP server
|
|
303
333
|
|
|
304
334
|
## License
|
|
305
335
|
|