bkper 4.26.1 → 4.28.1
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/README.md +23 -31
- package/lib/agent/extensions/builtins.d.ts +1 -0
- package/lib/agent/extensions/builtins.d.ts.map +1 -1
- package/lib/agent/extensions/builtins.js.map +1 -1
- package/lib/agent/extensions/handoff-goal-editor.d.ts +27 -0
- package/lib/agent/extensions/handoff-goal-editor.d.ts.map +1 -0
- package/lib/agent/extensions/handoff-goal-editor.js +113 -0
- package/lib/agent/extensions/handoff-goal-editor.js.map +1 -0
- package/lib/agent/extensions/handoff.d.ts.map +1 -1
- package/lib/agent/extensions/handoff.js +2 -1
- package/lib/agent/extensions/handoff.js.map +1 -1
- package/lib/agent/extensions/startup.d.ts +3 -1
- package/lib/agent/extensions/startup.d.ts.map +1 -1
- package/lib/agent/extensions/startup.js +21 -7
- package/lib/agent/extensions/startup.js.map +1 -1
- package/lib/agent/interactive/interactive-mode.d.ts.map +1 -1
- package/lib/agent/interactive/interactive-mode.js +9 -2
- package/lib/agent/interactive/interactive-mode.js.map +1 -1
- package/lib/agent/interactive/run-agent-mode.d.ts.map +1 -1
- package/lib/agent/interactive/run-agent-mode.js +7 -2
- package/lib/agent/interactive/run-agent-mode.js.map +1 -1
- package/lib/agent/interactive/settings.d.ts +18 -1
- package/lib/agent/interactive/settings.d.ts.map +1 -1
- package/lib/agent/interactive/settings.js +50 -0
- package/lib/agent/interactive/settings.js.map +1 -1
- package/lib/agent/system-prompt.d.ts +1 -1
- package/lib/agent/system-prompt.d.ts.map +1 -1
- package/lib/agent/system-prompt.js +24 -10
- package/lib/agent/system-prompt.js.map +1 -1
- package/lib/commands/books/copy.d.ts +16 -0
- package/lib/commands/books/copy.d.ts.map +1 -0
- package/lib/commands/books/copy.js +48 -0
- package/lib/commands/books/copy.js.map +1 -0
- package/lib/commands/books/index.d.ts +1 -0
- package/lib/commands/books/index.d.ts.map +1 -1
- package/lib/commands/books/index.js +1 -0
- package/lib/commands/books/index.js.map +1 -1
- package/lib/commands/books/register.d.ts.map +1 -1
- package/lib/commands/books/register.js +16 -1
- package/lib/commands/books/register.js.map +1 -1
- package/lib/commands/transactions/get.d.ts +11 -0
- package/lib/commands/transactions/get.d.ts.map +1 -0
- package/lib/commands/transactions/get.js +30 -0
- package/lib/commands/transactions/get.js.map +1 -0
- package/lib/commands/transactions/index.d.ts +3 -0
- package/lib/commands/transactions/index.d.ts.map +1 -1
- package/lib/commands/transactions/index.js +3 -0
- package/lib/commands/transactions/index.js.map +1 -1
- package/lib/commands/transactions/register.d.ts.map +1 -1
- package/lib/commands/transactions/register.js +28 -1
- package/lib/commands/transactions/register.js.map +1 -1
- package/lib/commands/transactions/uncheck.d.ts +10 -0
- package/lib/commands/transactions/uncheck.d.ts.map +1 -0
- package/lib/commands/transactions/uncheck.js +29 -0
- package/lib/commands/transactions/uncheck.js.map +1 -0
- package/lib/commands/transactions/untrash.d.ts +10 -0
- package/lib/commands/transactions/untrash.d.ts.map +1 -0
- package/lib/commands/transactions/untrash.js +29 -0
- package/lib/commands/transactions/untrash.js.map +1 -0
- package/lib/docs/apps/architecture.md +40 -7
- package/lib/docs/apps/configuration.md +3 -1
- package/lib/docs/apps/context-menu.md +47 -0
- package/lib/docs/apps/development.md +4 -4
- package/lib/docs/apps/event-handlers.md +1 -1
- package/lib/docs/apps/security.md +73 -0
- package/lib/docs/apps/self-hosted.md +1 -1
- package/lib/docs/cli/data-management.md +31 -0
- package/lib/docs/index.md +1 -0
- package/lib/docs/sdk/bkper-js.md +2 -6
- package/package.json +14 -5
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
2
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
3
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
4
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
5
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
6
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
7
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
8
|
+
});
|
|
9
|
+
};
|
|
10
|
+
import { getBkperInstance } from '../../bkper-factory.js';
|
|
11
|
+
/**
|
|
12
|
+
* Marks a checked transaction as unchecked (editable).
|
|
13
|
+
*
|
|
14
|
+
* @param bookId - The book ID containing the transaction
|
|
15
|
+
* @param transactionId - The ID of the transaction to uncheck
|
|
16
|
+
* @returns The unchecked transaction
|
|
17
|
+
*/
|
|
18
|
+
export function uncheckTransaction(bookId, transactionId) {
|
|
19
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
20
|
+
const bkper = getBkperInstance();
|
|
21
|
+
const book = yield bkper.getBook(bookId);
|
|
22
|
+
const transaction = yield book.getTransaction(transactionId);
|
|
23
|
+
if (!transaction) {
|
|
24
|
+
throw new Error(`Transaction not found: ${transactionId}`);
|
|
25
|
+
}
|
|
26
|
+
return transaction.uncheck();
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
//# sourceMappingURL=uncheck.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"uncheck.js","sourceRoot":"","sources":["../../../src/commands/transactions/uncheck.ts"],"names":[],"mappings":";;;;;;;;;AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAG1D;;;;;;GAMG;AACH,MAAM,UAAgB,kBAAkB,CACpC,MAAc,EACd,aAAqB;;QAErB,MAAM,KAAK,GAAG,gBAAgB,EAAE,CAAC;QACjC,MAAM,IAAI,GAAG,MAAM,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;QACzC,MAAM,WAAW,GAAG,MAAM,IAAI,CAAC,cAAc,CAAC,aAAa,CAAC,CAAC;QAC7D,IAAI,CAAC,WAAW,EAAE,CAAC;YACf,MAAM,IAAI,KAAK,CAAC,0BAA0B,aAAa,EAAE,CAAC,CAAC;QAC/D,CAAC;QACD,OAAO,WAAW,CAAC,OAAO,EAAE,CAAC;IACjC,CAAC;CAAA"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { Transaction } from 'bkper-js';
|
|
2
|
+
/**
|
|
3
|
+
* Restores a transaction from the trash.
|
|
4
|
+
*
|
|
5
|
+
* @param bookId - The book ID containing the transaction
|
|
6
|
+
* @param transactionId - The ID of the transaction to restore
|
|
7
|
+
* @returns The restored transaction
|
|
8
|
+
*/
|
|
9
|
+
export declare function untrashTransaction(bookId: string, transactionId: string): Promise<Transaction>;
|
|
10
|
+
//# sourceMappingURL=untrash.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"untrash.d.ts","sourceRoot":"","sources":["../../../src/commands/transactions/untrash.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AAEvC;;;;;;GAMG;AACH,wBAAsB,kBAAkB,CACpC,MAAM,EAAE,MAAM,EACd,aAAa,EAAE,MAAM,GACtB,OAAO,CAAC,WAAW,CAAC,CAQtB"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
2
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
3
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
4
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
5
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
6
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
7
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
8
|
+
});
|
|
9
|
+
};
|
|
10
|
+
import { getBkperInstance } from '../../bkper-factory.js';
|
|
11
|
+
/**
|
|
12
|
+
* Restores a transaction from the trash.
|
|
13
|
+
*
|
|
14
|
+
* @param bookId - The book ID containing the transaction
|
|
15
|
+
* @param transactionId - The ID of the transaction to restore
|
|
16
|
+
* @returns The restored transaction
|
|
17
|
+
*/
|
|
18
|
+
export function untrashTransaction(bookId, transactionId) {
|
|
19
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
20
|
+
const bkper = getBkperInstance();
|
|
21
|
+
const book = yield bkper.getBook(bookId);
|
|
22
|
+
const transaction = yield book.getTransaction(transactionId);
|
|
23
|
+
if (!transaction) {
|
|
24
|
+
throw new Error(`Transaction not found: ${transactionId}`);
|
|
25
|
+
}
|
|
26
|
+
return transaction.untrash();
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
//# sourceMappingURL=untrash.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"untrash.js","sourceRoot":"","sources":["../../../src/commands/transactions/untrash.ts"],"names":[],"mappings":";;;;;;;;;AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAG1D;;;;;;GAMG;AACH,MAAM,UAAgB,kBAAkB,CACpC,MAAc,EACd,aAAqB;;QAErB,MAAM,KAAK,GAAG,gBAAgB,EAAE,CAAC;QACjC,MAAM,IAAI,GAAG,MAAM,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;QACzC,MAAM,WAAW,GAAG,MAAM,IAAI,CAAC,cAAc,CAAC,aAAa,CAAC,CAAC;QAC7D,IAAI,CAAC,WAAW,EAAE,CAAC;YACf,MAAM,IAAI,KAAK,CAAC,0BAA0B,aAAa,EAAE,CAAC,CAAC;QAC/D,CAAC;QACD,OAAO,WAAW,CAAC,OAAO,EAAE,CAAC;IACjC,CAAC;CAAA"}
|
|
@@ -14,7 +14,6 @@ my-app/
|
|
|
14
14
|
│ ├── vite.config.ts
|
|
15
15
|
│ └── src/
|
|
16
16
|
│ ├── api/
|
|
17
|
-
│ ├── app/
|
|
18
17
|
│ ├── auth/
|
|
19
18
|
│ ├── components/
|
|
20
19
|
│ └── services/
|
|
@@ -44,10 +43,14 @@ The client uses:
|
|
|
44
43
|
- [`@bkper/web-design`](https://www.npmjs.com/package/@bkper/web-design) for Bkper design tokens.
|
|
45
44
|
- [Vite](https://vitejs.dev/) for development and production builds, configured in `client/vite.config.ts`.
|
|
46
45
|
|
|
47
|
-
Client code has two data paths:
|
|
46
|
+
Client code has two data paths. Choose based on who owns the behavior:
|
|
48
47
|
|
|
49
|
-
- **Direct Bkper calls** use `bkper-js` for browser
|
|
50
|
-
- **App API calls** use the generated typed client in `client/src/api/` with `auth.authenticatedFetch()
|
|
48
|
+
- **Direct Bkper calls** use `bkper-js` for generic Bkper data needed only by the browser UI.
|
|
49
|
+
- **App API calls** use the generated typed client in `client/src/api/` with `auth.authenticatedFetch()` for app-owned behavior, especially when it needs server-only capabilities or more than one caller.
|
|
50
|
+
|
|
51
|
+
Keep app-owned behavior in one place. Do not implement the same behavior separately in the UI and the app API.
|
|
52
|
+
|
|
53
|
+
For stateful feature components, co-locate view, controller, and CSS files in one folder under `components/`. Simple presentational components can remain in one file.
|
|
51
54
|
|
|
52
55
|
### Client authentication
|
|
53
56
|
|
|
@@ -115,7 +118,7 @@ The default template publishes versioned routes under `/api/v1/*` and exposes th
|
|
|
115
118
|
| Business behavior | `server/src/services/` |
|
|
116
119
|
| Generated client types | `client/src/api/generated/types.d.ts` |
|
|
117
120
|
| Typed client wrapper | `client/src/api/app-api.ts` |
|
|
118
|
-
| Contract snapshot | `server/test/openapi.snapshot.json`
|
|
121
|
+
| Contract snapshot | `server/test/api/openapi.snapshot.json` |
|
|
119
122
|
|
|
120
123
|
When changing the API:
|
|
121
124
|
|
|
@@ -126,6 +129,30 @@ When changing the API:
|
|
|
126
129
|
|
|
127
130
|
Keep existing `/api/v1/*` contracts backward compatible. Additive fields and routes can remain in `v1`; breaking changes belong in a new namespace such as `/api/v2/*`.
|
|
128
131
|
|
|
132
|
+
### Reuse Bkper API types
|
|
133
|
+
|
|
134
|
+
When an app API returns payloads from the Bkper REST API, reference the canonical types from `@bkper/bkper-api-types` instead of recreating their fields in the app. The template's balances endpoint demonstrates this with `bkper.Book`:
|
|
135
|
+
|
|
136
|
+
```ts
|
|
137
|
+
export const BookSchema = z
|
|
138
|
+
.custom<bkper.Book>(value => value !== undefined)
|
|
139
|
+
.openapi('Book', {
|
|
140
|
+
type: 'object',
|
|
141
|
+
additionalProperties: true,
|
|
142
|
+
'x-typescript-type': 'bkper.Book',
|
|
143
|
+
});
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
The template's API generator recognizes `x-typescript-type`, imports `@bkper/bkper-api-types`, and emits the canonical reference in `client/src/api/generated/types.d.ts`:
|
|
147
|
+
|
|
148
|
+
```ts
|
|
149
|
+
Book: bkper.Book;
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
Both the server and client packages include `@bkper/bkper-api-types` for local typechecking. Run `npm run api` after adding or changing these schemas.
|
|
153
|
+
|
|
154
|
+
This bridge provides compile-time types but does not validate payload fields at runtime. Use it directly for trusted Bkper-owned responses. Request bodies, especially those used to create or modify Book resources, still require concrete Zod validation.
|
|
155
|
+
|
|
129
156
|
### URLs
|
|
130
157
|
|
|
131
158
|
```txt
|
|
@@ -145,7 +172,7 @@ TOKEN="$(bkper auth token)"
|
|
|
145
172
|
|
|
146
173
|
curl \
|
|
147
174
|
-H "Authorization: Bearer ${TOKEN}" \
|
|
148
|
-
"https://my-app.bkper.app/api/v1/
|
|
175
|
+
"https://my-app.bkper.app/api/v1/ping"
|
|
149
176
|
```
|
|
150
177
|
|
|
151
178
|
Replace `my-app` with the app id from `bkper.yaml`.
|
|
@@ -155,7 +182,7 @@ Replace `my-app` with the app id from `bkper.yaml`.
|
|
|
155
182
|
Deployed `/api/*` routes require a Bkper OAuth bearer token. The template client uses `authenticatedFetch()` so token attachment and refresh stay inside `@bkper/web-auth`:
|
|
156
183
|
|
|
157
184
|
```ts
|
|
158
|
-
const response = await auth.authenticatedFetch('/api/v1/
|
|
185
|
+
const response = await auth.authenticatedFetch('/api/v1/ping');
|
|
159
186
|
```
|
|
160
187
|
|
|
161
188
|
Dispatch validates the incoming bearer token and strips the `Authorization` header before the Worker runs. Server code should not read or forward the token.
|
|
@@ -171,6 +198,12 @@ const books = await bkper.getBooks();
|
|
|
171
198
|
|
|
172
199
|
Platform outbound authentication injects the validated user's OAuth token on Bkper API requests.
|
|
173
200
|
|
|
201
|
+
### Authorize app operations
|
|
202
|
+
|
|
203
|
+
Authentication identifies the Bkper user, but each app must authorize sensitive data and actions server-side. Client-side checks are not an authorization boundary.
|
|
204
|
+
|
|
205
|
+
See [App Security](https://bkper.com/docs/build/apps/security.md) for domain restrictions, Book permissions, and app installation checks.
|
|
206
|
+
|
|
174
207
|
## Event handlers
|
|
175
208
|
|
|
176
209
|
Platform event deliveries reach `/events` on the same Worker. Event adapters live in `server/src/events/`, while reusable business behavior belongs in `server/src/services/`.
|
|
@@ -95,7 +95,9 @@ deployment:
|
|
|
95
95
|
| `menuText` | Custom menu text (defaults to app name). |
|
|
96
96
|
| `menuOpenMode` | How the app menu opens: `SIDEBAR` (default), `EXPANDED`, or `NEW_TAB`. |
|
|
97
97
|
|
|
98
|
-
|
|
98
|
+
`SIDEBAR` and `EXPANDED` Apps can receive live context updates while their iframe stays loaded. `NEW_TAB` Apps receive context only in the URL used to open the tab.
|
|
99
|
+
|
|
100
|
+
See [Context Menu](https://bkper.com/docs/build/apps/context-menu.md#live-context-updates) for menu URL configuration and live context updates.
|
|
99
101
|
|
|
100
102
|
### Menu URL variables
|
|
101
103
|
|
|
@@ -53,6 +53,53 @@ menuOpenMode: SIDEBAR
|
|
|
53
53
|
| `EXPANDED` | Opens in a wider panel with more room for complex UIs. |
|
|
54
54
|
| `NEW_TAB` | Opens the menu URL in a new browser tab instead of an embedded panel. |
|
|
55
55
|
|
|
56
|
+
### Live context updates
|
|
57
|
+
|
|
58
|
+
Bkper keeps embedded Apps informed of context changes without reloading the iframe, allowing them to preserve their current state. For Apps opened in `SIDEBAR` or `EXPANDED`, Bkper communicates those changes by sending the updated App URL to the iframe when its origin remains the same:
|
|
59
|
+
|
|
60
|
+
```js
|
|
61
|
+
{
|
|
62
|
+
type: 'bkper:app-url-changed',
|
|
63
|
+
url: 'https://my-app.bkper.app?bookId=abc123&query=account:Sales',
|
|
64
|
+
}
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
Listen for the message in the App:
|
|
68
|
+
|
|
69
|
+
```js
|
|
70
|
+
const BKPER_ORIGIN = 'https://bkper.app';
|
|
71
|
+
|
|
72
|
+
window.addEventListener('message', event => {
|
|
73
|
+
// Verify that the trusted Bkper parent sent the message.
|
|
74
|
+
if (event.source !== window.parent || event.origin !== BKPER_ORIGIN) return;
|
|
75
|
+
|
|
76
|
+
// Verify that this is a valid App URL update.
|
|
77
|
+
const message = event.data;
|
|
78
|
+
if (message?.type !== 'bkper:app-url-changed' || typeof message.url !== 'string') return;
|
|
79
|
+
|
|
80
|
+
// Parse the updated URL, ignoring malformed URL strings.
|
|
81
|
+
let nextUrl;
|
|
82
|
+
try {
|
|
83
|
+
nextUrl = new URL(message.url);
|
|
84
|
+
|
|
85
|
+
// Accept only URLs belonging to this App.
|
|
86
|
+
if (nextUrl.origin !== window.location.origin) return;
|
|
87
|
+
} catch {
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Keep the iframe URL in sync without reloading it.
|
|
92
|
+
window.history.replaceState(window.history.state, '', nextUrl);
|
|
93
|
+
|
|
94
|
+
// Apply the validated context update.
|
|
95
|
+
handleAppUrlChange(nextUrl);
|
|
96
|
+
});
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
`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
|
+
|
|
101
|
+
Apps opened with `NEW_TAB` do not receive this message. Their context is set only by the URL used to open the tab.
|
|
102
|
+
|
|
56
103
|
### Available expressions
|
|
57
104
|
|
|
58
105
|
The menu URL supports these dynamic expressions:
|
|
@@ -25,10 +25,10 @@ You can also run them independently: `npm run dev:client` for just the UI, or `n
|
|
|
25
25
|
| Client (Vite dev server) | `http://localhost:5173` |
|
|
26
26
|
| Server Worker (Miniflare) | `http://localhost:8787` |
|
|
27
27
|
| App API routes | `http://localhost:8787/api/*` |
|
|
28
|
-
| App OpenAPI spec | `http://localhost:
|
|
28
|
+
| App OpenAPI spec | `http://localhost:5173/openapi.json` |
|
|
29
29
|
| Events (via tunnel to the same Worker) | `https://<random>.trycloudflare.com/events` |
|
|
30
30
|
|
|
31
|
-
The Vite dev server proxies `/api` requests to `http://localhost:8787` through `client/vite.config.ts
|
|
31
|
+
The Vite dev server proxies `/api` and `/openapi.json` requests to `http://localhost:8787` through `client/vite.config.ts`, so the client and OpenAPI spec share the same local origin just as they do in production. The spec also remains available directly from the Worker at `http://localhost:8787/openapi.json`. The tunnel URL is automatically registered as `webhookUrlDev`, so development-mode events are routed to your local machine.
|
|
32
32
|
|
|
33
33
|
## Configuration flags
|
|
34
34
|
|
|
@@ -40,13 +40,13 @@ bkper app dev --sp 8787
|
|
|
40
40
|
|
|
41
41
|
## Client configuration
|
|
42
42
|
|
|
43
|
-
The client dev server is configured in `client/vite.config.ts`. This standard Vite configuration registers local auth middleware and proxies `/api` requests to the Worker.
|
|
43
|
+
The client dev server is configured in `client/vite.config.ts`. This standard Vite configuration registers local auth middleware and proxies `/api` and `/openapi.json` requests to the Worker.
|
|
44
44
|
|
|
45
45
|
### Local development authentication
|
|
46
46
|
|
|
47
47
|
During local development, the Vite dev server runs `createBkperAuthMiddleware()` from `bkper/dev`. It serves the local `/auth/refresh` endpoint used by `@bkper/web-auth`, obtaining OAuth tokens from your CLI credentials.
|
|
48
48
|
|
|
49
|
-
The separate Vite proxy configuration forwards `/api` requests to the Miniflare Worker.
|
|
49
|
+
The separate Vite proxy configuration forwards `/api` and `/openapi.json` requests to the Miniflare Worker.
|
|
50
50
|
|
|
51
51
|
Before starting development, run:
|
|
52
52
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# Event Handlers
|
|
1
|
+
# Bkper Webhooks and Event Handlers
|
|
2
2
|
|
|
3
3
|
Event handlers are the code that reacts to events in your Bkper Books. When a transaction is checked, an account is created, or any other event occurs, your handler receives it and can take action — calculate taxes, sync data between books, post to external services, and more.
|
|
4
4
|
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
# App Security
|
|
2
|
+
|
|
3
|
+
Bkper and each platform app have separate security responsibilities. This guide explains the platform authentication boundary and common authorization checks an app must enforce.
|
|
4
|
+
|
|
5
|
+
## Authentication and authorization
|
|
6
|
+
|
|
7
|
+
Authentication identifies the Bkper user making a request. The platform handles this flow for deployed apps.
|
|
8
|
+
|
|
9
|
+
Authorization determines whether that user may perform a specific operation.
|
|
10
|
+
|
|
11
|
+
See [App Architecture](https://bkper.com/docs/build/apps/architecture.md) for the client and server authentication flow.
|
|
12
|
+
|
|
13
|
+
## Authorize app operations
|
|
14
|
+
|
|
15
|
+
Platform authentication identifies the Bkper user and provides outbound authentication for server-side Bkper requests. Your app must still decide which authenticated users may perform each operation. Protect sensitive data and actions in the server API; client-side checks may improve the UI, but they are not an authorization boundary.
|
|
16
|
+
|
|
17
|
+
### Restrict an internal app by user domain
|
|
18
|
+
|
|
19
|
+
For an app intended only for people in one organization, authorize the authenticated user's hosted domain:
|
|
20
|
+
|
|
21
|
+
```ts
|
|
22
|
+
const ALLOWED_DOMAIN = 'example.com';
|
|
23
|
+
|
|
24
|
+
const user = await context.bkper.getUser();
|
|
25
|
+
const domain = user.getHostedDomain()?.toLowerCase();
|
|
26
|
+
|
|
27
|
+
if (domain !== ALLOWED_DOMAIN) {
|
|
28
|
+
return c.json(buildApiError('FORBIDDEN', 'This app is restricted to your organization'), 403);
|
|
29
|
+
}
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
### Authorize a Book-backed operation
|
|
33
|
+
|
|
34
|
+
When an operation acts on a Book, use an explicit permission allowlist appropriate to that operation. For an operation that requires edit access:
|
|
35
|
+
|
|
36
|
+
```ts
|
|
37
|
+
import { Permission } from 'bkper-js';
|
|
38
|
+
|
|
39
|
+
const EDIT_PERMISSIONS: readonly Permission[] = [Permission.EDITOR, Permission.OWNER];
|
|
40
|
+
|
|
41
|
+
const book = await context.bkper.getBook(bookId);
|
|
42
|
+
|
|
43
|
+
if (!EDIT_PERMISSIONS.includes(book.getPermission())) {
|
|
44
|
+
return c.json(
|
|
45
|
+
buildApiError('FORBIDDEN', 'Editor or owner permission required for this operation'),
|
|
46
|
+
403
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
Read, posting, and other operations may require different policies. Choose the minimum authorization appropriate to the behavior instead of treating every authenticated user as authorized.
|
|
52
|
+
|
|
53
|
+
### Require app installation
|
|
54
|
+
|
|
55
|
+
Having permission to access a Book does not mean the app is installed in that Book. If an app is only supposed to be used with Books where it is installed, verify installation:
|
|
56
|
+
|
|
57
|
+
```ts
|
|
58
|
+
const APP_ID = 'my-app';
|
|
59
|
+
|
|
60
|
+
const book = await context.bkper.getBook(bookId);
|
|
61
|
+
const installedApps = await book.getApps();
|
|
62
|
+
const isInstalled = installedApps.some(app => app.getId() === APP_ID);
|
|
63
|
+
|
|
64
|
+
if (!isInstalled) {
|
|
65
|
+
return c.json(buildApiError('FORBIDDEN', 'This app is not installed in this Book'), 403);
|
|
66
|
+
}
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
## Next Steps
|
|
70
|
+
|
|
71
|
+
- [App Architecture](https://bkper.com/docs/build/apps/architecture.md) — Understand client and server authentication flows.
|
|
72
|
+
- [Building & Deploying](https://bkper.com/docs/build/apps/deploying.md#setting-secrets) — Store production and preview secrets.
|
|
73
|
+
- [Event Handlers](https://bkper.com/docs/build/apps/event-handlers.md#authentication) — Understand authentication for platform and self-hosted events.
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# Self-Hosted
|
|
1
|
+
# Bkper Self-Hosted Webhooks
|
|
2
2
|
|
|
3
3
|
The [Bkper Platform](https://bkper.com/docs/build/apps/overview.md) handles hosting, authentication, and deployment for you. However, you can host event handlers on your own infrastructure if you have specific requirements — existing cloud setup, compliance constraints, or legacy apps.
|
|
4
4
|
|
|
@@ -40,6 +40,12 @@ bkper book create --name "My Company" --fraction-digits 2 \
|
|
|
40
40
|
# Create a book with custom properties
|
|
41
41
|
bkper book create --name "Project X" -p "code=PX001" -p "department=Engineering"
|
|
42
42
|
|
|
43
|
+
# Copy a book's structure without transactions
|
|
44
|
+
bkper book copy abc123 --name "My Company Copy"
|
|
45
|
+
|
|
46
|
+
# Copy a book with transactions from a specific date (source book owner only)
|
|
47
|
+
bkper book copy abc123 --name "My Company 2025" --transactions --from-date 2025-01-01
|
|
48
|
+
|
|
43
49
|
# Update a book
|
|
44
50
|
bkper book update abc123 --lock-date 2024-12-31
|
|
45
51
|
```
|
|
@@ -58,6 +64,10 @@ bkper book update abc123 --lock-date 2024-12-31
|
|
|
58
64
|
- `--time-zone <timezone>` - IANA time zone (e.g. `America/New_York`, `UTC`)
|
|
59
65
|
- `--period <period>` - Period (`MONTH`, `QUARTER`, or `YEAR`)
|
|
60
66
|
- `-p, --property <key=value>` - Set a property (repeatable)
|
|
67
|
+
- `book copy <bookId>` - Copy a book
|
|
68
|
+
- `--name <name>` - Name for the copied book (required)
|
|
69
|
+
- `--transactions` - Include transactions (source book owner only)
|
|
70
|
+
- `--from-date <YYYY-MM-DD>` - Include transactions from this date; requires `--transactions`
|
|
61
71
|
- `book update <bookId>` - Update a book
|
|
62
72
|
- `--name <name>` - Book name
|
|
63
73
|
- `--fraction-digits <digits>` - Number of decimal places (`0`-`8`)
|
|
@@ -228,6 +238,10 @@ bkper transaction create -b abc123 --description "Office supplies"
|
|
|
228
238
|
bkper transaction create -b abc123 --date 2025-01-15 --amount 100.50 \
|
|
229
239
|
--from "Bank Account" --to "Office Supplies" --description "Printer paper"
|
|
230
240
|
|
|
241
|
+
# Keep a complete AI-derived transaction as a draft
|
|
242
|
+
printf '%s\n' '[{"date":"2025-01-15","amount":"100.50","creditAccount":{"name":"Bank Account"},"debitAccount":{"name":"Office Supplies"},"description":"Extracted printer paper","draft":true}]' | \
|
|
243
|
+
bkper transaction create -b abc123
|
|
244
|
+
|
|
231
245
|
# Create a transaction with one local attachment
|
|
232
246
|
bkper transaction create -b abc123 --date 2025-01-15 --amount 23.90 \
|
|
233
247
|
--from "Cash" --to "Meals" --description "Team lunch" --file ./receipt.pdf
|
|
@@ -235,6 +249,9 @@ bkper transaction create -b abc123 --date 2025-01-15 --amount 23.90 \
|
|
|
235
249
|
# Create a file-only draft for receipt capture
|
|
236
250
|
bkper transaction create -b abc123 --file ./invoice.pdf
|
|
237
251
|
|
|
252
|
+
# Get a transaction by id
|
|
253
|
+
bkper transaction get tx_456 -b abc123
|
|
254
|
+
|
|
238
255
|
# List transactions for a full year (on:YYYY)
|
|
239
256
|
bkper transaction list -b abc123 -q 'on:2025'
|
|
240
257
|
|
|
@@ -259,9 +276,15 @@ bkper transaction post tx_456 -b abc123
|
|
|
259
276
|
# Check (reconcile) a transaction
|
|
260
277
|
bkper transaction check tx_456 -b abc123
|
|
261
278
|
|
|
279
|
+
# Uncheck a transaction
|
|
280
|
+
bkper transaction uncheck tx_456 -b abc123
|
|
281
|
+
|
|
262
282
|
# Trash a transaction
|
|
263
283
|
bkper transaction trash tx_456 -b abc123
|
|
264
284
|
|
|
285
|
+
# Restore a transaction from the trash
|
|
286
|
+
bkper transaction untrash tx_456 -b abc123
|
|
287
|
+
|
|
265
288
|
# Merge two duplicate transactions
|
|
266
289
|
bkper transaction merge tx_123 tx_456 -b abc123
|
|
267
290
|
```
|
|
@@ -269,6 +292,7 @@ bkper transaction merge tx_123 tx_456 -b abc123
|
|
|
269
292
|
<details>
|
|
270
293
|
<summary>Command reference</summary>
|
|
271
294
|
|
|
295
|
+
- `transaction get <id> -b <bookId>` - Get a transaction by ID
|
|
272
296
|
- `transaction list -b <bookId> -q <query>` - List transactions matching a query (auto-paginates through all results unless `--limit` or `--cursor` is provided)
|
|
273
297
|
- `--limit <number>` - Fetch one page with up to this many transactions
|
|
274
298
|
- `--cursor <cursor>` - Cursor for fetching the next page
|
|
@@ -294,7 +318,9 @@ bkper transaction merge tx_123 tx_456 -b abc123
|
|
|
294
318
|
- `-p, --property <key=value>` - Set a property (repeatable, empty value deletes)
|
|
295
319
|
- `transaction post <id> -b <bookId>` - Post a draft transaction
|
|
296
320
|
- `transaction check <id> -b <bookId>` - Check a transaction
|
|
321
|
+
- `transaction uncheck <id> -b <bookId>` - Uncheck a transaction
|
|
297
322
|
- `transaction trash <id> -b <bookId>` - Trash a transaction
|
|
323
|
+
- `transaction untrash <id> -b <bookId>` - Restore a transaction from the trash
|
|
298
324
|
- `transaction merge <id1> <id2> -b <bookId>` - Merge two transactions
|
|
299
325
|
|
|
300
326
|
</details>
|
|
@@ -518,6 +544,10 @@ python export_bank.py | bkper transaction create -b abc123
|
|
|
518
544
|
|
|
519
545
|
The input follows the exact `bkper.Transaction` or `bkper.Account` type from the [Bkper API Types](https://raw.githubusercontent.com/bkper/bkper-api-types/refs/heads/master/index.d.ts). Custom properties go inside the `properties` object.
|
|
520
546
|
|
|
547
|
+
### AI-derived transaction safety
|
|
548
|
+
|
|
549
|
+
Always set `"draft": true` for AI-derived transactions, even when complete. It bypasses Book auto-posting, so the transaction stays out of balances until explicitly posted. Do not rely on missing fields; parsing or Account discovery may complete them.
|
|
550
|
+
|
|
521
551
|
Groups are created explicitly with `bkper group create --name` and optional `--parent` so hierarchy stays deterministic during setup.
|
|
522
552
|
|
|
523
553
|
The `--property` CLI flag can override or delete properties from the stdin payload:
|
|
@@ -582,6 +612,7 @@ Only the fields below are meaningful when creating or updating resources via std
|
|
|
582
612
|
| `creditAccount` | `{"name":"..."}` or `{"id":"..."}` | Reference to an existing account |
|
|
583
613
|
| `debitAccount` | `{"name":"..."}` or `{"id":"..."}` | Reference to an existing account |
|
|
584
614
|
| `description` | `string` | Free-text description |
|
|
615
|
+
| `draft` | `boolean` | `true` forces a draft and bypasses Book auto-posting |
|
|
585
616
|
| `urls` | `string[]` | Attached URLs (e.g. receipts) |
|
|
586
617
|
| `remoteIds` | `string[]` | External IDs to prevent duplicates |
|
|
587
618
|
| `properties` | `{"key": "value", ...}` | Custom key/value properties |
|
package/lib/docs/index.md
CHANGED
|
@@ -11,6 +11,7 @@ For Bkper data, accounting, reporting, tax, or financial-flow tasks, read `core/
|
|
|
11
11
|
- `apps/ai.md` — Bkper AI integration for Platform apps: authenticated `/api/*` request flow, outbound authorization and app attribution, live model discovery, strict structured output, response validation, error preservation, data minimization, and unit-test boundaries.
|
|
12
12
|
- `apps/first-app.md` — First-app walkthrough: scaffold, install, run locally, trigger an event, customize the listing, establish shared source, check, and deploy.
|
|
13
13
|
- `apps/architecture.md` — App and template architecture: npm workspace structure, Lit/Vite client, Hono Worker, typed `/api/*` contracts, authentication, `/events`, static assets, and supported app shapes.
|
|
14
|
+
- `apps/security.md` — App security responsibilities and server-side authorization: platform authentication boundaries, user-domain restrictions, Book permission allowlists, and app installation checks.
|
|
14
15
|
- `apps/configuration.md` — Complete `bkper.yaml` reference: identity, branding, ownership, access, context menus, event subscriptions, property schemas, and single-Worker deployment settings.
|
|
15
16
|
- `apps/development.md` — Local development: Vite and Worker processes, ports, API proxy, local authentication, secrets, KV, generated environment types, development loop, and debugging.
|
|
16
17
|
- `apps/event-handlers.md` — Event handler behavior: `/events` routing, responses, replay, loop prevention, platform and self-hosted authentication, event payloads, and event types.
|
package/lib/docs/sdk/bkper-js.md
CHANGED
|
@@ -636,7 +636,8 @@ It contains all `Accounts` where `Transactions` are recorded/posted;
|
|
|
636
636
|
- `listEvents(options: ListEventsOptions)` → `Promise<EventList>` — Lists events in the Book based on the provided options.
|
|
637
637
|
- `listFiles(limit?: number, cursor?: string)` → `Promise<FileList>` — Lists files in the Book, for pagination.
|
|
638
638
|
- `listTransactions(query?: string, limit?: number, cursor?: string)` → `Promise<TransactionList>` — Lists transactions in the Book based on the provided query, limit, and cursor, for pagination.
|
|
639
|
-
- `mergeTransactions(
|
|
639
|
+
- `mergeTransactions(primary: string | bkper.Transaction | Transaction, secondary: string | bkper.Transaction | Transaction)` → `Promise<Transaction>` — Merge a primary and secondary `Transaction`. Submitted primary fields
|
|
640
|
+
have highest precedence; an id string sends no field overrides.
|
|
640
641
|
- `parseDate(date: string)` → `Date` — Parse a date string according to date pattern and timezone of the Book. Also parse ISO yyyy-mm-dd format.
|
|
641
642
|
- `parseValue(value: string)` → `Amount | undefined` — Parse a value string according to `DecimalSeparator` and fraction digits of the Book.
|
|
642
643
|
- `remove()` → `Promise<Book>` — Warning!
|
|
@@ -736,11 +737,6 @@ const groups2 = await bookWithGroups.getGroups(); // Already cached
|
|
|
736
737
|
|
|
737
738
|
Requests are sent sequentially in batches of up to 200 IDs.
|
|
738
739
|
|
|
739
|
-
**mergeTransactions**
|
|
740
|
-
|
|
741
|
-
The merged transaction is created synchronously. Cleanup of the two
|
|
742
|
-
originals is scheduled asynchronously by the backend.
|
|
743
|
-
|
|
744
740
|
**remove**
|
|
745
741
|
|
|
746
742
|
Deletes this Book and all its data (transactions, accounts, groups). Book owner only.
|
package/package.json
CHANGED
|
@@ -1,14 +1,23 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "bkper",
|
|
3
|
-
"version": "4.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "4.28.1",
|
|
4
|
+
"description": "Official Bkper CLI for accounting data, automation, apps, and AI agents",
|
|
5
5
|
"bin": {
|
|
6
6
|
"bkper": "./lib/cli.js"
|
|
7
7
|
},
|
|
8
8
|
"repository": "https://github.com/bkper/bkper-cli.git",
|
|
9
|
-
"homepage": "https://bkper.com/docs",
|
|
9
|
+
"homepage": "https://bkper.com/docs/build/tools/cli",
|
|
10
10
|
"author": "mael <mael@bkper.com>",
|
|
11
11
|
"license": "Apache-2.0",
|
|
12
|
+
"keywords": [
|
|
13
|
+
"bkper",
|
|
14
|
+
"cli",
|
|
15
|
+
"accounting",
|
|
16
|
+
"bookkeeping",
|
|
17
|
+
"finance",
|
|
18
|
+
"automation",
|
|
19
|
+
"ai-agent"
|
|
20
|
+
],
|
|
12
21
|
"private": false,
|
|
13
22
|
"main": "./lib/index.js",
|
|
14
23
|
"module": "./lib/index.js",
|
|
@@ -52,8 +61,8 @@
|
|
|
52
61
|
"upgrade:api": "bun update @bkper/bkper-api-types --latest && bun update bkper-js --latest"
|
|
53
62
|
},
|
|
54
63
|
"dependencies": {
|
|
55
|
-
"@earendil-works/pi-coding-agent": "0.84.
|
|
56
|
-
"@earendil-works/pi-tui": "0.84.
|
|
64
|
+
"@earendil-works/pi-coding-agent": "0.84.3",
|
|
65
|
+
"@earendil-works/pi-tui": "0.84.3",
|
|
57
66
|
"bkper-js": "^2.42.0",
|
|
58
67
|
"commander": "^13.1.0",
|
|
59
68
|
"dotenv": "^8.2.0",
|