@positronic/template-new-project 0.0.63 → 0.0.64

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/index.js CHANGED
@@ -53,10 +53,10 @@ module.exports = {
53
53
  ],
54
54
  setup: async ctx => {
55
55
  const devRootPath = process.env.POSITRONIC_LOCAL_PATH;
56
- let coreVersion = '^0.0.63';
57
- let cloudflareVersion = '^0.0.63';
58
- let clientVercelVersion = '^0.0.63';
59
- let genUIComponentsVersion = '^0.0.63';
56
+ let coreVersion = '^0.0.64';
57
+ let cloudflareVersion = '^0.0.64';
58
+ let clientVercelVersion = '^0.0.64';
59
+ let genUIComponentsVersion = '^0.0.64';
60
60
 
61
61
  // Map backend selection to package names
62
62
  const backendPackageMap = {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@positronic/template-new-project",
3
- "version": "0.0.63",
3
+ "version": "0.0.64",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
@@ -119,6 +119,16 @@ export default brain('approval-workflow')
119
119
  }));
120
120
  ```
121
121
 
122
+ ### CSRF Tokens for Pages with Forms
123
+
124
+ If your brain generates a custom HTML page with a form that submits to a webhook, you must include a CSRF token. Without a token, the server will reject the submission.
125
+
126
+ 1. Generate a token with `generateFormToken()` from `@positronic/core`
127
+ 2. Add `<input type="hidden" name="__positronic_token" value="${token}">` to the form
128
+ 3. Pass the token when creating the webhook registration: `myWebhook(identifier, token)`
129
+
130
+ The `.ui()` step handles this automatically. See `/docs/brain-dsl-guide.md` for full examples.
131
+
122
132
  ### How Auto-Discovery Works
123
133
 
124
134
  - Place webhook files in `/webhooks` directory
@@ -5,6 +5,7 @@ node_modules/
5
5
 
6
6
  # Local development server directory
7
7
  .positronic/
8
+ .positronic-auth.json
8
9
  .env
9
10
 
10
11
  # Cloudflare Wrangler state and temp files
@@ -867,8 +867,6 @@ brain('Batch Processor')
867
867
  }, {
868
868
  over: (state) => state.items, // Array to iterate over
869
869
  concurrency: 10, // Parallel requests (default: 10)
870
- stagger: 100, // Delay between requests in ms
871
- maxRetries: 3,
872
870
  error: (item, error) => ({ summary: 'Failed to summarize' }) // Fallback on error
873
871
  })
874
872
  .step('Process Results', ({ state }) => ({
@@ -884,9 +882,7 @@ brain('Batch Processor')
884
882
  ### Batch Options
885
883
 
886
884
  - `over: (state) => T[]` - Function returning the array to iterate over
887
- - `concurrency: number` - Maximum parallel requests (default: 10)
888
- - `stagger: number` - Milliseconds to wait between starting requests
889
- - `maxRetries: number` - Maximum number of retries for failed requests (passed to the AI client SDK)
885
+ - `concurrency: number` - Maximum number of items processed in parallel (default: 10)
890
886
  - `error: (item, error) => Response` - Fallback function when a request fails
891
887
 
892
888
  ### Result Format
@@ -1103,6 +1099,87 @@ The created page object contains:
1103
1099
  - `url: string` - Public URL to access the page
1104
1100
  - `webhook: WebhookConfig` - Webhook configuration for handling form submissions
1105
1101
 
1102
+ ### Custom Pages with Forms (CSRF Token)
1103
+
1104
+ When building custom HTML pages with forms, you must include a CSRF token to prevent unauthorized submissions. The `.ui()` step handles this automatically, but custom pages require manual setup. This applies whether you submit to the built-in `ui-form` endpoint or to a custom webhook.
1105
+
1106
+ #### Using a Custom Webhook
1107
+
1108
+ If your page submits to a custom webhook (e.g., `/webhooks/archive`), pass the token as the second argument when creating the webhook registration:
1109
+
1110
+ ```typescript
1111
+ import { generateFormToken } from '@positronic/core';
1112
+ import archiveWebhook from '../webhooks/archive.js';
1113
+
1114
+ brain('Archive Workflow')
1115
+ .step('Create Page', async ({ state, pages, env }) => {
1116
+ const formToken = generateFormToken();
1117
+
1118
+ const html = `<html>
1119
+ <body>
1120
+ <form method="POST" action="<%= '${env.origin}' %>/webhooks/archive">
1121
+ <input type="hidden" name="__positronic_token" value="<%= '${formToken}' %>">
1122
+ <input type="text" name="name" placeholder="Your name">
1123
+ <button type="submit">Submit</button>
1124
+ </form>
1125
+ </body>
1126
+ </html>`;
1127
+
1128
+ await pages.create('my-page', html);
1129
+ return { ...state, formToken };
1130
+ })
1131
+ .wait('Wait for submission', ({ state }) => archiveWebhook(state.sessionId, state.formToken))
1132
+ .step('Process', ({ state, response }) => ({
1133
+ ...state,
1134
+ name: response.name,
1135
+ }));
1136
+ ```
1137
+
1138
+ #### Using the System `ui-form` Endpoint
1139
+
1140
+ If your page submits to the built-in `ui-form` endpoint, include the token in the webhook registration object:
1141
+
1142
+ ```typescript
1143
+ import { generateFormToken } from '@positronic/core';
1144
+
1145
+ brain('Custom Form')
1146
+ .step('Create Form Page', async ({ state, pages, env }) => {
1147
+ const formToken = generateFormToken();
1148
+ const webhookIdentifier = `custom-form-<%= '${Date.now()}' %>`;
1149
+ const formAction = `<%= '${env.origin}' %>/webhooks/system/ui-form?identifier=<%= '${encodeURIComponent(webhookIdentifier)}' %>`;
1150
+
1151
+ const page = await pages.create('my-form', `<html>
1152
+ <body>
1153
+ <form method="POST" action="<%= '${formAction}' %>">
1154
+ <input type="hidden" name="__positronic_token" value="<%= '${formToken}' %>">
1155
+ <input type="text" name="name" placeholder="Your name">
1156
+ <button type="submit">Submit</button>
1157
+ </form>
1158
+ </body>
1159
+ </html>`);
1160
+
1161
+ return {
1162
+ ...state,
1163
+ pageUrl: page.url,
1164
+ webhook: { slug: 'ui-form', identifier: webhookIdentifier, token: formToken },
1165
+ };
1166
+ })
1167
+ .wait('Wait for form', ({ state }) => state.webhook)
1168
+ .step('Process', ({ state, response }) => ({
1169
+ ...state,
1170
+ name: response.name,
1171
+ }));
1172
+ ```
1173
+
1174
+ #### Summary
1175
+
1176
+ The three required pieces for any custom page with a form:
1177
+ 1. Call `generateFormToken()` to get a token
1178
+ 2. Add `<input type="hidden" name="__positronic_token" value="...">` to your form
1179
+ 3. Include the `token` in your webhook registration — either as the second argument to a custom webhook function (e.g., `myWebhook(identifier, token)`) or in the registration object for `ui-form`
1180
+
1181
+ Without a token, the server will reject the form submission.
1182
+
1106
1183
  ## UI Steps
1107
1184
 
1108
1185
  UI steps allow brains to generate dynamic user interfaces using AI. The `.ui()` step generates a page and provides a `page` object to the next step. You then notify users and use `.wait()` to pause until the form is submitted.