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.
Files changed (70) hide show
  1. package/README.md +23 -31
  2. package/lib/agent/extensions/builtins.d.ts +1 -0
  3. package/lib/agent/extensions/builtins.d.ts.map +1 -1
  4. package/lib/agent/extensions/builtins.js.map +1 -1
  5. package/lib/agent/extensions/handoff-goal-editor.d.ts +27 -0
  6. package/lib/agent/extensions/handoff-goal-editor.d.ts.map +1 -0
  7. package/lib/agent/extensions/handoff-goal-editor.js +113 -0
  8. package/lib/agent/extensions/handoff-goal-editor.js.map +1 -0
  9. package/lib/agent/extensions/handoff.d.ts.map +1 -1
  10. package/lib/agent/extensions/handoff.js +2 -1
  11. package/lib/agent/extensions/handoff.js.map +1 -1
  12. package/lib/agent/extensions/startup.d.ts +3 -1
  13. package/lib/agent/extensions/startup.d.ts.map +1 -1
  14. package/lib/agent/extensions/startup.js +21 -7
  15. package/lib/agent/extensions/startup.js.map +1 -1
  16. package/lib/agent/interactive/interactive-mode.d.ts.map +1 -1
  17. package/lib/agent/interactive/interactive-mode.js +9 -2
  18. package/lib/agent/interactive/interactive-mode.js.map +1 -1
  19. package/lib/agent/interactive/run-agent-mode.d.ts.map +1 -1
  20. package/lib/agent/interactive/run-agent-mode.js +7 -2
  21. package/lib/agent/interactive/run-agent-mode.js.map +1 -1
  22. package/lib/agent/interactive/settings.d.ts +18 -1
  23. package/lib/agent/interactive/settings.d.ts.map +1 -1
  24. package/lib/agent/interactive/settings.js +50 -0
  25. package/lib/agent/interactive/settings.js.map +1 -1
  26. package/lib/agent/system-prompt.d.ts +1 -1
  27. package/lib/agent/system-prompt.d.ts.map +1 -1
  28. package/lib/agent/system-prompt.js +24 -10
  29. package/lib/agent/system-prompt.js.map +1 -1
  30. package/lib/commands/books/copy.d.ts +16 -0
  31. package/lib/commands/books/copy.d.ts.map +1 -0
  32. package/lib/commands/books/copy.js +48 -0
  33. package/lib/commands/books/copy.js.map +1 -0
  34. package/lib/commands/books/index.d.ts +1 -0
  35. package/lib/commands/books/index.d.ts.map +1 -1
  36. package/lib/commands/books/index.js +1 -0
  37. package/lib/commands/books/index.js.map +1 -1
  38. package/lib/commands/books/register.d.ts.map +1 -1
  39. package/lib/commands/books/register.js +16 -1
  40. package/lib/commands/books/register.js.map +1 -1
  41. package/lib/commands/transactions/get.d.ts +11 -0
  42. package/lib/commands/transactions/get.d.ts.map +1 -0
  43. package/lib/commands/transactions/get.js +30 -0
  44. package/lib/commands/transactions/get.js.map +1 -0
  45. package/lib/commands/transactions/index.d.ts +3 -0
  46. package/lib/commands/transactions/index.d.ts.map +1 -1
  47. package/lib/commands/transactions/index.js +3 -0
  48. package/lib/commands/transactions/index.js.map +1 -1
  49. package/lib/commands/transactions/register.d.ts.map +1 -1
  50. package/lib/commands/transactions/register.js +28 -1
  51. package/lib/commands/transactions/register.js.map +1 -1
  52. package/lib/commands/transactions/uncheck.d.ts +10 -0
  53. package/lib/commands/transactions/uncheck.d.ts.map +1 -0
  54. package/lib/commands/transactions/uncheck.js +29 -0
  55. package/lib/commands/transactions/uncheck.js.map +1 -0
  56. package/lib/commands/transactions/untrash.d.ts +10 -0
  57. package/lib/commands/transactions/untrash.d.ts.map +1 -0
  58. package/lib/commands/transactions/untrash.js +29 -0
  59. package/lib/commands/transactions/untrash.js.map +1 -0
  60. package/lib/docs/apps/architecture.md +40 -7
  61. package/lib/docs/apps/configuration.md +3 -1
  62. package/lib/docs/apps/context-menu.md +47 -0
  63. package/lib/docs/apps/development.md +4 -4
  64. package/lib/docs/apps/event-handlers.md +1 -1
  65. package/lib/docs/apps/security.md +73 -0
  66. package/lib/docs/apps/self-hosted.md +1 -1
  67. package/lib/docs/cli/data-management.md +31 -0
  68. package/lib/docs/index.md +1 -0
  69. package/lib/docs/sdk/bkper-js.md +2 -6
  70. 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-specific behavior.
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/books"
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/books');
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
- See [Context Menu](https://bkper.com/docs/build/apps/context-menu.md) for details on building menu integrations.
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:8787/openapi.json` |
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`. The app OpenAPI spec is served by 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.
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 Alternative
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.
@@ -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(transaction1: string | bkper.Transaction | Transaction, transaction2: string | bkper.Transaction | Transaction)` → `Promise<Transaction>` — Merge two `Transactions` into a single new canonical transaction.
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.26.1",
4
- "description": "Command line client for Bkper",
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.2",
56
- "@earendil-works/pi-tui": "0.84.2",
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",