bkper 5.6.0 → 5.7.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.
@@ -37,7 +37,7 @@ Confidence is high when the model's probability concentrates on one answer. For
37
37
 
38
38
  2. **Let code gather the facts.**
39
39
 
40
- Code fetches the item and the context a person would need, such as nearby Transactions, how an Account is usually described, or Book and Account properties. Pass them as named fields, such as `bank_line` and `existing_transaction`. Decision models read text only, so convert files and images to text first.
40
+ Code fetches the item and the context a person would need, such as nearby Transactions, how an Account is usually described, or Book and Account properties. Pass them as named fields, such as `bank_line` and `existing_transaction`. Facts can also be images, such as a receipt photo. Convert PDFs and other files to text or images first.
41
41
 
42
42
  3. **Ask narrow questions.**
43
43
 
@@ -141,7 +141,8 @@ For more designs, see TypeSafe's [Patterns](https://docs.typesafe.ai/patterns) a
141
141
  [Jev](https://docs.typesafe.ai/introduction) is TypeSafe's flagship model and the first System One model. Its Bkper model ID is `jev`.
142
142
 
143
143
  - **Versions.** `jev-latest` and versioned IDs are accepted but always select the current Jev; you cannot pin a version. Log the revision each answer reports, and re-check tuned thresholds when it changes.
144
- - **Input.** Text only. English is Jev's primary training language; other languages work with lower accuracy.
144
+ - **Input.** Text and images. English is Jev's primary training language; other languages work with lower accuracy.
145
+ - **Images.** An experimental TypeSafe feature, so accuracy and limits can change. Send PNG or JPEG images between 280 and 1568 pixels per side as base64 `assets` in a JSON request; TypeSafe's SDKs upload images in a form Bkper AI does not accept. Image tokens count as input. See TypeSafe's [Images](https://docs.typesafe.ai/images) guide.
145
146
  - **Known weak spots.** Literal reading, arithmetic, date comparison, and large states full of irrelevant detail. See [Jev 1.13 jaggedness](https://docs.typesafe.ai/model-jaggedness/jev-1.13).
146
147
 
147
148
  Requests count against your Bkper AI allowance at Jev's [usage rates](https://bkper.com/docs/ai/models.md#usage-rates).
@@ -262,6 +262,7 @@ Decision models come from TypeSafe's [System One](https://docs.typesafe.ai/conce
262
262
  - **SDKs.** TypeSafe SDKs work with base URL `https://ai.bkper.app`, without `/v1`, and a Bkper access token. See [Add Bkper AI to an App](https://bkper.com/docs/platform/apps/ai#ask-a-decision-model).
263
263
  - **Errors.** Bkper returns `400` where TypeSafe returns `422`, and `503` with `provider_overloaded` where TypeSafe returns `529`. A `429` can also mean the Bkper AI allowance is exhausted.
264
264
  - **Usage.** Requests count against the Bkper AI allowance, not a TypeSafe account.
265
+ - **Images.** Send images as base64 `assets` in a JSON request, as in TypeSafe's [Images](https://docs.typesafe.ai/images) guide. TypeSafe SDKs upload images as multipart, which Bkper does not accept, so send image requests over HTTP. Bkper turns on TypeSafe's experimental image input for you. Box questions are not supported. Image tokens count as input tokens.
265
266
  - **Coding agents.** TypeSafe's [agent skill](https://docs.typesafe.ai/agent-skill) teaches questions and patterns. For a Bkper integration, also point the agent to this guide, `https://bkper.com/docs/api/ai-gateway.md`, for the endpoint, authentication, model IDs, and errors.
266
267
 
267
268
  ## Stream a response
@@ -7,14 +7,14 @@ The Managed Agent API lets you build intelligent apps on Bkper's agent: a privat
7
7
  Build, for example:
8
8
 
9
9
  - a Bkper platform app with its own chat, in a Book or on its own page;
10
- - a chat bot for Slack, Teams, or Discord, with one session per thread;
10
+ - a chat bot for Slack, Teams, or Discord, with one session per thread and each message's sender in its `attributes`;
11
11
  - a scheduled script that asks for a monthly report and saves the file.
12
12
 
13
- Sessions belong to the user, not the app: work started in one app continues in any other, including [Bkper Agent](https://bkper.com/docs/ai/bkper-agent), the agent in Bkper's Books. Bkper Agent is built only on this API and is its reference implementation. Usage counts against the user's Bkper AI allowance; see [Models and Usage](https://bkper.com/docs/ai/models). To call models directly, without an agent or workspace, use the [AI Gateway API](https://bkper.com/docs/api/ai-gateway). Building a Bkper app? Start with [Add Bkper AI to an App](https://bkper.com/docs/platform/apps/ai).
13
+ Each app has its own workspace for each user: its sessions, files, and sandbox stay apart from that user's other apps and from [Bkper Agent](https://bkper.com/docs/ai/bkper-agent), the agent in Bkper's Books. Calls with a plain Bkper token, such as from a script, work in the user's own workspace, which Bkper Agent shows. Bkper Agent is built only on this API and is its reference implementation. Usage counts against the user's Bkper AI allowance; see [Models and Usage](https://bkper.com/docs/ai/models). To call models directly, without an agent or workspace, use the [AI Gateway API](https://bkper.com/docs/api/ai-gateway). Building a Bkper app? Start with [Add Bkper AI to an App](https://bkper.com/docs/platform/apps/ai).
14
14
 
15
15
  ## Requirements
16
16
 
17
- - A Bkper account with an email address. Accounts not admitted to the beta get 403 `agent_beta_required`.
17
+ - A Bkper account with an email address.
18
18
  - A Bkper OAuth access token for the user the agent acts as. [Bkper platform apps](https://bkper.com/docs/platform/apps/overview) need none: call the API from the app's server, and the platform adds the user's credentials, as it does for Bkper AI.
19
19
  - A server, bot, or script to call the API. Browsers on other websites are refused: the API sends no CORS headers and rejects cross-origin writes with 403 `origin_forbidden`.
20
20
 
@@ -80,7 +80,7 @@ The agent receives, in order:
80
80
 
81
81
  1. **Bkper's base**: the workspace, Bkper's accounting model, and rules no interface can relax.
82
82
  2. **Your `instructions`.**
83
- 3. **Each message**, with its optional `bookContext` as labeled information, never as instructions or permission.
83
+ 3. **Each message**, with its optional `bookContext` and `attributes` as labeled information, never as instructions or permission.
84
84
  4. **Each message's attached files**, copied into the workspace and listed with their paths as labeled information.
85
85
 
86
86
  There is no default. Without `instructions`, the agent follows only Bkper's base, which does not ask anyone before acting. Bkper Agent sends its own instructions, so it asks questions and makes only the changes it is asked for; your interface decides its own.
@@ -109,7 +109,17 @@ the totals you checked.
109
109
 
110
110
  ## Who the agent acts as
111
111
 
112
- Every request acts as the user whose token you send. That user's Book permissions, sessions, files, and Bkper AI allowance apply. Their sessions appear in their Bkper Agent too.
112
+ Every request acts as the user whose token you send. That user's Book permissions and Bkper AI allowance apply.
113
+
114
+ The workspace (sessions, files, and sandbox) depends on who calls. Bkper sets the app from the platform side, never from the request:
115
+
116
+ | Caller | Workspace |
117
+ | --- | --- |
118
+ | Bkper Agent, or a script or CLI with a plain Bkper token | The user's own |
119
+ | A [Bkper platform app](https://bkper.com/docs/platform/apps/overview) calling from its server | The user's, for that app |
120
+ | A Bkper channel bot, such as a Slack bot | The bot's account, for that bot |
121
+
122
+ Book changes made in an app's workspace are attributed to that app, with its name and logo.
113
123
 
114
124
  - **Personal tools**: use each person's own token, so each person sees only their Books.
115
125
  - **Shared channel bots**: everyone in the channel acts with the bot's account. Use a dedicated Bkper account, shared only on the Books the channel needs, with the lowest permission that does the job.
@@ -145,6 +155,23 @@ Every create, input, and upload request needs an `Idempotency-Key` of 1–128 le
145
155
 
146
156
  Send `bookContext: { "bookId": "…", "query": "…" }` with an input when the message is about a Book. Bkper checks the user's access when the agent opens the Book.
147
157
 
158
+ ### Message attributes
159
+
160
+ Send `attributes` with an input for what the agent should know about the message but the person did not type: who sent it, from which channel, or the thread messages before it. They are strings by name:
161
+
162
+ ```json
163
+ {
164
+ "content": [{ "type": "text", "text": "suggest an answer here" }],
165
+ "attributes": {
166
+ "sender": "Alice (U0123)",
167
+ "channel": "#finance",
168
+ "thread": "Bob: can we pay the March invoice from Acme?"
169
+ }
170
+ }
171
+ ```
172
+
173
+ The agent receives them after the message as labeled information, never as instructions or permission: identity comes only from the token. The input and the user's entry keep the text as sent, with `attributes` beside it. Send per-message context here rather than in `instructions`, which stay the same for every message.
174
+
148
175
  ### Forks
149
176
 
150
177
  Create a session with `parent` to continue from a point in another session:
@@ -263,10 +290,14 @@ async function agent(path: string, body?: unknown, key?: string) {
263
290
  return result;
264
291
  }
265
292
 
266
- type SlackEvent = { event_id: string; thread_ts: string; text: string };
293
+ type SlackEvent = { event_id: string; thread_ts: string; text: string; user: string };
267
294
 
268
295
  export async function reply(event: SlackEvent): Promise<string> {
269
- const input = { content: [{ type: 'text', text: event.text }] };
296
+ const input = {
297
+ content: [{ type: 'text', text: event.text }],
298
+ // Who wrote it, as information for the agent: everyone acts as the bot.
299
+ attributes: { sender: event.user },
300
+ };
270
301
  // The event ID is the key: a redelivered event arrives once.
271
302
  const key = event.event_id;
272
303
  let id = sessions.get(event.thread_ts);
@@ -313,6 +344,7 @@ Acknowledge each Slack event at once and post the reply when `reply` returns: Sl
313
344
  | File retention | 30 days after creation |
314
345
  | `instructions` | 16,000 characters |
315
346
  | `bookContext.query` | 4,000 characters |
347
+ | `attributes` | 16 names, 16000 characters in total |
316
348
  | List page | 50 sessions, entries, or files |
317
349
 
318
350
  ## Errors
@@ -323,7 +355,7 @@ Errors are `{ "error": { "code", "type", "message" } }`. Branch on `code`.
323
355
  | --- | --- | --- |
324
356
  | 400 | `invalid_request`, `unknown_model`, `unknown_thinking_level` | A field is invalid or unknown |
325
357
  | 401 | `unauthorized`, `authentication_failed` | The token is missing, invalid, or expired |
326
- | 403 | `agent_beta_required` | The account is not admitted to the beta |
358
+ | 403 | `agent_beta_required` | The account cannot use the agent, such as one without an email address |
327
359
  | 403 | `origin_forbidden` | A browser on another website sent a write |
328
360
  | 404 | `not_found`, `parent_not_found`, `file_not_found` | No such resource for this user |
329
361
  | 405 | `method_not_allowed` | The path does not support the method |
@@ -183,6 +183,7 @@ Map these errors to your route's typed error response. Keep the code and message
183
183
 
184
184
  - **Passing your own `fetch`.** The TypeSafe SDK calls `fetch` as its own method, which the Workers runtime rejects with `Illegal invocation`. Wrap it: `fetch: (input, init) => myFetch(input, init)`. Without the option, the SDK's default works.
185
185
  - **No `null` values.** The TypeSafe SDK's types accept `null` for state, instructions, and some criteria. Bkper AI rejects them with `400`.
186
+ - **No SDK image uploads.** The TypeSafe SDK uploads images as multipart, which Bkper AI rejects with `400`. Send image requests with `fetch` as JSON, with base64 `assets`. See [Decision Models](https://bkper.com/docs/ai/decision-models.md#jev-by-typesafe).
186
187
  - **No `client.models.list()`.** It expects TypeSafe's catalog shape. Use the [`GET /v1/models`](https://ai.bkper.app/v1/models) catalog.
187
188
  - **Other System One clients** take `https://ai.bkper.app/v1` as their base URL.
188
189
  - **Flexible JSON schemas.** Open Responses always requests `strict: true`. For a schema that needs `strict: false`, such as a typed dynamic map, use `@ai-sdk/openai` with base URL `https://ai.bkper.app/v1`, its `.responses()` model, and `strictJsonSchema: false`.
@@ -92,15 +92,12 @@ window.addEventListener('message', event => {
92
92
  return;
93
93
  }
94
94
 
95
- // Keep the iframe URL in sync without reloading it.
96
- window.history.replaceState(window.history.state, '', nextUrl);
97
-
98
95
  // Apply the validated context update.
99
96
  handleAppUrlChange(nextUrl);
100
97
  });
101
98
  ```
102
99
 
103
- `handleAppUrlChange` is App logic. The App can update internal state, notify components, refresh data, change its UI, or ignore the message. Bkper only communicates the new URL; it does not reload the iframe or apply the context inside the App.
100
+ `handleAppUrlChange` is App logic, best in the module that [owns the URL](https://bkper.com/docs/platform/apps/quality.md#client-state): it takes the Book's context, keeps the App's own state, such as its selection, and replaces the URL without reloading. The App can update internal state, notify components, refresh data, change its UI, or ignore the message. Bkper only communicates the new URL; it does not reload the iframe or apply the context inside the App.
104
101
 
105
102
  Apps opened with `NEW_TAB` do not receive this message. Their context is set only by the URL used to open the tab.
106
103
 
@@ -61,10 +61,10 @@ Split a module when unrelated behavior changes for different reasons or when mix
61
61
  Start every client with this structure, so it grows by adding pieces instead of restructuring:
62
62
 
63
63
  - **Stores hold shared state.** Anything another component or the URL could care about, such as loaded data, the selection, or errors, lives in a store under `client/src/state/`, one per concern. A store holds its data and the actions that change it, announces changes, and never touches the DOM.
64
- - **Components render and report intent.** A component watches the stores it shows and changes them only through their actions. Only its own presentation, such as an open menu or hover, stays in the component. Each component owns its CSS.
64
+ - **Components render and report intent.** A component watches only the stores it shows and changes them only through their actions, in response to the person, never while rendering. Only its own presentation, such as an open menu or hover, stays in the component. Each component owns its CSS.
65
65
  - **Stores ask the interface through events.** When a store needs the interface to act, such as focusing an input or scrolling to new content, it fires an event the component listens to. Stores never call components.
66
- - **One module wires the stores.** It creates them and gives each the stores it depends on. Dependencies point one way. A store changes another store only through that store's actions, and reactions that span stores live in this module.
67
- - **One module owns the URL.** Only it reads or writes the URL's path, query, and `history`, mapping the URL to state. Put in the URL what a person would reload or share, such as the selected item. Standalone, add a history entry per selection so Back works. Embedded in a Book, replace the URL so Back stays the Book's. Pass it the window so it can be tested.
66
+ - **One module wires the stores.** It creates them and gives each the stores it depends on. Dependencies point one way. A store changes another store only through that store's actions, and reactions that span stores live in this module. Components get only the stores and actions they use; services stay inside this module.
67
+ - **One module owns the URL.** Only it reads or writes the URL's path, query, and `history`, mapping the URL to state. Put in the URL what a person would reload or share, such as the selected item. Standalone, add a history entry for each place the person goes, so Back works; when a place becomes another, such as a new item once saved, replace its entry. Embedded in a Book, replace the URL so Back stays the Book's. Pass it the window so it can be tested.
68
68
  - **Split by concern, not by screen region.** Split a store or component when its parts change for different reasons.
69
69
 
70
70
  ## Security
@@ -264,9 +264,6 @@ bkper transaction list -b abc123 -q 'on:2025' --limit 100
264
264
  # Fetch the next transaction page
265
265
  bkper transaction list -b abc123 -q 'on:2025' --limit 100 --cursor cursor_123
266
266
 
267
- # Count transactions without listing them
268
- bkper transaction count -b abc123 -q "account:'Capital' on:2025"
269
-
270
267
  # Update a transaction
271
268
  bkper transaction update tx_456 -b abc123 --amount 120.00 --description "Printer paper (corrected)"
272
269
 
@@ -289,6 +286,19 @@ bkper transaction untrash tx_456 -b abc123
289
286
  bkper transaction merge tx_123 tx_456 -b abc123
290
287
  ```
291
288
 
289
+ ### Counting transactions
290
+
291
+ To answer how many transactions match, use `transaction count` (one call per account, group, or period), never `transaction list`. Queries include drafts: add `is:posted` for posted only. Trashed transactions are excluded unless `is:trashed`.
292
+
293
+ ```bash
294
+ bkper transaction count -b abc123 -q "account:'Capital' on:2025 is:posted" # {"count":N}
295
+ bkper transaction count -b abc123 -q "group:'Equity' on:2025 is:posted"
296
+
297
+ # Accounts directly in a group, to count per account
298
+ GROUP_ID=$(bkper group get 'Equity' -b abc123 | jq -r '.id')
299
+ bkper account list -b abc123 | jq -r --arg g "$GROUP_ID" '.items[] | select(any(.groups[]?; .id == $g)) | .name'
300
+ ```
301
+
292
302
  <details>
293
303
  <summary>Command reference</summary>
294
304
 
@@ -296,7 +306,7 @@ bkper transaction merge tx_123 tx_456 -b abc123
296
306
  - `transaction list -b <bookId> -q <query>` - List transactions matching a query (auto-paginates through all results unless `--limit` or `--cursor` is provided)
297
307
  - `--limit <number>` - Fetch one page with up to this many transactions
298
308
  - `--cursor <cursor>` - Cursor for fetching the next page
299
- - `transaction count -b <bookId> [-q <query>]` - Count transactions matching a query, returning `{"count":N}`; counts all transactions when `-q` is omitted. Use this instead of `transaction list` whenever only the number of transactions is needed — one call per account/group/period is fast, while listing fetches every transaction
309
+ - `transaction count -b <bookId> [-q <query>]` - Count transactions matching a query, returning `{"count":N}`; counts all transactions when `-q` is omitted
300
310
  - `transaction create -b <bookId>` - Create a transaction
301
311
  - `--date <date>` - Transaction date
302
312
  - `--amount <amount>` - Transaction amount
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bkper",
3
- "version": "5.6.0",
3
+ "version": "5.7.0",
4
4
  "description": "Official Bkper CLI for accounting data, automation, apps, and AI agents",
5
5
  "bin": {
6
6
  "bkper": "lib/cli.js"
@@ -64,13 +64,13 @@
64
64
  "upgrade:api": "bun update @bkper/bkper-api-types --latest && bun update bkper-js --latest"
65
65
  },
66
66
  "dependencies": {
67
- "@earendil-works/pi-agent-core": "1.2.0",
68
- "@earendil-works/pi-ai": "1.2.0",
69
- "@earendil-works/pi-codemode": "1.2.0",
70
- "@earendil-works/pi-coding-agent": "1.2.0",
71
- "@earendil-works/pi-mcp": "1.2.0",
72
- "@earendil-works/pi-telemetry": "1.2.0",
73
- "@earendil-works/pi-tui": "1.2.0",
67
+ "@earendil-works/pi-agent-core": "1.4.0",
68
+ "@earendil-works/pi-ai": "1.4.0",
69
+ "@earendil-works/pi-codemode": "1.4.0",
70
+ "@earendil-works/pi-coding-agent": "1.4.0",
71
+ "@earendil-works/pi-mcp": "1.4.0",
72
+ "@earendil-works/pi-telemetry": "1.4.0",
73
+ "@earendil-works/pi-tui": "1.4.0",
74
74
  "@silvia-odwyer/photon-node": "0.3.4",
75
75
  "bkper-js": "^2.45.1",
76
76
  "commander": "^13.1.0",