@positronic/template-new-project 0.0.2
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/.claude/settings.local.json +8 -0
- package/CLAUDE.md +215 -0
- package/index.js +180 -0
- package/package.json +18 -0
- package/template/.positronic/package.json +17 -0
- package/template/.positronic/src/index.ts +35 -0
- package/template/.positronic/tsconfig.json +19 -0
- package/template/.positronic/wrangler.jsonc +53 -0
- package/template/CLAUDE.md +120 -0
- package/template/_env +32 -0
- package/template/_gitignore +24 -0
- package/template/brain.ts +59 -0
- package/template/brains/example.ts +13 -0
- package/template/docs/brain-dsl-guide.md +601 -0
- package/template/docs/brain-testing-guide.md +307 -0
- package/template/docs/positronic-guide.md +236 -0
- package/template/docs/tips-for-agents.md +425 -0
- package/template/jest.config.js +24 -0
- package/template/package.json +29 -0
- package/template/positronic.config.json +8 -0
- package/template/resources/example.md +0 -0
- package/template/runner.ts +9 -0
- package/template/tests/example.test.ts +20 -0
- package/template/tsconfig.json +16 -0
package/CLAUDE.md
ADDED
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
# Maintainer's Guide to the Positronic Project Template & Caz
|
|
2
|
+
|
|
3
|
+
This document serves as the complete guide for maintainers of the `@positronic/template-new-project`. It provides a deep dive into the structure of this template, its configuration, and the underlying scaffolding engine, `caz`, that powers it.
|
|
4
|
+
|
|
5
|
+
This guide assumes no prior knowledge of the `caz` codebase.
|
|
6
|
+
|
|
7
|
+
## Part 1: The `@positronic/template-new-project`
|
|
8
|
+
|
|
9
|
+
This project is a `caz` template. Its sole purpose is to generate new Positronic projects by running a command like `npx @positronic/template-new-project <new-project-name>`.
|
|
10
|
+
|
|
11
|
+
### The Anatomy of the Template
|
|
12
|
+
|
|
13
|
+
The template is controlled by its `index.js` file, which defines how `caz` should behave. The actual files to be generated live inside the `template/` directory.
|
|
14
|
+
|
|
15
|
+
#### Key Files
|
|
16
|
+
|
|
17
|
+
* **`index.js`**: The brain of the template. This is a configuration file that tells `caz` what questions to ask, what logic to run before generating files, and how to prepare the files.
|
|
18
|
+
* **`package.json`**: This defines the template itself, including its dependencies like `caz`. Note that the dependencies here are for the *template*, not the *generated project*.
|
|
19
|
+
* **`template/`**: This directory contains the skeleton of the project that will be created. Files and filenames here can contain template variables.
|
|
20
|
+
|
|
21
|
+
#### The Generation Process at a Glance
|
|
22
|
+
|
|
23
|
+
When a user runs this template, `caz` performs the following high-level steps:
|
|
24
|
+
|
|
25
|
+
1. **Prompts**: Asks the user a series of questions defined in `index.js`.
|
|
26
|
+
2. **Setup**: Runs custom logic to process the user's answers and prepare dynamic variables.
|
|
27
|
+
3. **Prepare**: Prepares the list of files to be generated, renaming special files like `_gitignore`.
|
|
28
|
+
4. **Render & Emit**: Copies files from the `template/` directory, injecting variables and processing logic.
|
|
29
|
+
5. **Install & Initialize**: (Optionally) installs dependencies and initializes a Git repository in the new project.
|
|
30
|
+
|
|
31
|
+
### In-Depth Configuration (`index.js`)
|
|
32
|
+
|
|
33
|
+
The `index.js` file exports a configuration object that `caz` uses to drive the entire process. Let's break down its key sections.
|
|
34
|
+
|
|
35
|
+
#### **1. Prompts (`prompts` array)**
|
|
36
|
+
|
|
37
|
+
This array defines the interactive questions `caz` will ask the user.
|
|
38
|
+
|
|
39
|
+
* **`name`**: The name of the new project. Defaults to `my-positronic-project`.
|
|
40
|
+
* **`backend`**: A selection list for the deployment backend. This is critical as it drives which dependencies and configurations are included. Currently, only 'Cloudflare' is enabled.
|
|
41
|
+
* **`install`**: A confirmation to ask whether dependencies should be installed automatically.
|
|
42
|
+
* **`pm`**: A selection list for the package manager (npm, pnpm, yarn), which only appears if the user opts to install dependencies.
|
|
43
|
+
|
|
44
|
+
#### **2. Pre-Generation Logic (`setup` hook)**
|
|
45
|
+
|
|
46
|
+
This asynchronous function runs after the user has answered the prompts but before any files are generated. It's used for complex, dynamic configuration.
|
|
47
|
+
|
|
48
|
+
* **Local Development with `POSITRONIC_LOCAL_PATH`**: This is a crucial feature for maintainers. If the `POSITRONIC_LOCAL_PATH` environment variable is set (pointing to the root of a local monorepo), the template will map the `@positronic/*` dependencies to local `file:` paths instead of fetching them from `npm`. This allows for simultaneous development and testing of the core libraries and the template.
|
|
49
|
+
* **Backend Package Mapping**: It maps the user's `backend` choice (e.g., 'cloudflare') to a specific npm package name (e.g., `@positronic/cloudflare`).
|
|
50
|
+
* **Dynamic Versioning**: It sets the versions for `@positronic/core`, `@positronic/cloudflare`, and `@positronic/client-vercel` to either `'latest'` or a local `file:` path.
|
|
51
|
+
* **Installation Control**: It sets `ctx.config.install` to the chosen package manager or `false`, telling `caz` whether to run an installation step later.
|
|
52
|
+
* **Context Augmentation**: It adds new properties to `ctx.answers` (like `projectName`, `positronicCoreVersion`, etc.) which can then be used as variables in the template files.
|
|
53
|
+
|
|
54
|
+
#### **3. File Preparation (`prepare` hook)**
|
|
55
|
+
|
|
56
|
+
This function runs after `caz` has gathered all the template files but before they are rendered and written to disk. It allows for final manipulation of the file list.
|
|
57
|
+
|
|
58
|
+
* **Renaming System Files**: It renames `_gitignore` to `.gitignore` and `_env` to `.env`. This is a common pattern to prevent the template's own `.gitignore` or `.env` from affecting the template repository itself.
|
|
59
|
+
* **Marking Files as Binary**: It iterates through the files and marks any markdown files in the `docs/` directory as binary. This tells `caz`'s rendering engine to skip them, preventing it from trying to inject template variables into the documentation.
|
|
60
|
+
|
|
61
|
+
### The Template Files (`template/` directory)
|
|
62
|
+
|
|
63
|
+
Files within this directory are the blueprint for the generated project. They utilize Lodash template syntax.
|
|
64
|
+
|
|
65
|
+
* **Variable Injection**: Simple variables are injected using `<%=...%>`.
|
|
66
|
+
* Example from `template/positronic.config.json`: `"projectName": "<%= name %>"`
|
|
67
|
+
* **Conditional Logic**: JavaScript logic can be embedded with `<%...%>`. This is used powerfully in `template/package.json` to conditionally include dependencies based on the backend choice.
|
|
68
|
+
```json
|
|
69
|
+
"dependencies": {
|
|
70
|
+
...
|
|
71
|
+
"@positronic/core": "<%= positronicCoreVersion %>"<% if (backend === 'cloudflare') { %>,
|
|
72
|
+
"@positronic/cloudflare": "<%= positronicCloudflareVersion %>"<% } %>
|
|
73
|
+
}
|
|
74
|
+
```
|
|
75
|
+
* **File Renaming**: `caz` also supports renaming files that contain `{variable}` syntax in their names, though this template does not currently use that feature.
|
|
76
|
+
|
|
77
|
+
***
|
|
78
|
+
|
|
79
|
+
## Part 2: `caz` - The Scaffolding Engine
|
|
80
|
+
|
|
81
|
+
`caz` is the engine that interprets and executes our template. Understanding its workflow is key to understanding why the template is built the way it is.
|
|
82
|
+
|
|
83
|
+
### The Core `caz` Workflow (Middleware Chain)
|
|
84
|
+
|
|
85
|
+
`caz` processes a project in a strict, sequential order of middleware.
|
|
86
|
+
|
|
87
|
+
1. **`confirm`**: Checks if the target project directory exists. If it's not empty, it prompts the user to either merge, overwrite, or cancel. The `--force` flag bypasses this.
|
|
88
|
+
2. **`resolve`**: Locates the template. It can be a local path (`./my-template`) or a remote repository (`zce/nm`). Remote templates are downloaded and cached in `~/.cache/caz` for offline use and speed.
|
|
89
|
+
3. **`load`**: Loads the template's configuration (`index.js`). **Crucially, it also runs `npm install --production` inside the template's directory**, installing any `dependencies` listed in the template's `package.json`.
|
|
90
|
+
4. **`inquire`**: Presents the interactive prompts from the `prompts` array to the user. `caz` automatically provides smart defaults and validation for common fields like `name`, `version`, `author`, `email`, and `url` by reading system `.npmrc` and `.gitconfig` files.
|
|
91
|
+
5. **`setup`**: Executes the `setup` hook from the template config, as described above.
|
|
92
|
+
6. **`prepare`**: Reads all files from the `source` directory (`template/` by default). It applies any `filters` defined in the config to conditionally exclude files. After this, it runs the `prepare` hook.
|
|
93
|
+
7. **`rename`**: Scans filenames for `{variable}` patterns and renames them based on user answers.
|
|
94
|
+
8. **`render`**: Scans the content of all non-binary files for template syntax (`<%=...%>` or `${...}`) and renders them using the user's answers and any configured `helpers`.
|
|
95
|
+
9. **`emit`**: Writes the final, rendered files to the destination project directory. It runs an `emit` hook if one is defined.
|
|
96
|
+
10. **`install`**: If `config.install` is set to `'npm'`, `'yarn'`, or `'pnpm'`, it runs the corresponding installation command in the new project directory.
|
|
97
|
+
11. **`init`**: If `config.init` is `true` (or if a `.gitignore` file is present), it runs `git init`, `git add`, and `git commit`.
|
|
98
|
+
12. **`complete`**: Executes the `complete` hook, which is typically used to print a "getting started" message to the user. If not defined, it prints a default message listing the created files.
|
|
99
|
+
|
|
100
|
+
***
|
|
101
|
+
|
|
102
|
+
## Part 3: Practical Maintainer Tasks
|
|
103
|
+
|
|
104
|
+
With this knowledge, here is how to approach common maintenance tasks.
|
|
105
|
+
|
|
106
|
+
### How to Add a New Backend (e.g., "Fly.io")
|
|
107
|
+
|
|
108
|
+
1. **Modify Prompts (`index.js`)**: Add a new choice to the `backend` prompt. You can disable it initially if it's a work in progress.
|
|
109
|
+
```javascript
|
|
110
|
+
{
|
|
111
|
+
name: 'backend',
|
|
112
|
+
type: 'select',
|
|
113
|
+
message: 'Select your deployment backend',
|
|
114
|
+
choices: [
|
|
115
|
+
{ title: 'Cloudflare', value: 'cloudflare' },
|
|
116
|
+
// ...
|
|
117
|
+
{ title: 'Fly.io', value: 'fly', disabled: false }, // Add new choice
|
|
118
|
+
{ title: 'None (Core only)', value: 'none' }
|
|
119
|
+
]
|
|
120
|
+
}
|
|
121
|
+
```
|
|
122
|
+
2. **Update Setup Logic (`index.js`)**:
|
|
123
|
+
* Add the new backend to `backendPackageMap`.
|
|
124
|
+
* Add logic to handle the `file:` path for the new backend when `POSITRONIC_LOCAL_PATH` is set.
|
|
125
|
+
* Add the new dependency version to `ctx.answers`.
|
|
126
|
+
3. **Update Template Files (`template/package.json`)**: Add a conditional block to include the new dependency.
|
|
127
|
+
```json
|
|
128
|
+
"dependencies": {
|
|
129
|
+
...
|
|
130
|
+
<% if (backend === 'fly') { %>
|
|
131
|
+
"@positronic/fly": "<%= positronicFlyVersion %>"
|
|
132
|
+
<% } %>
|
|
133
|
+
}
|
|
134
|
+
```
|
|
135
|
+
4. **Add New Files**: If the new backend requires specific files (e.g., `fly.toml`), add them to the `template/` directory. You can use `filters` in `index.js` to ensure they are only included when that backend is selected.
|
|
136
|
+
|
|
137
|
+
### How to Test Changes Locally
|
|
138
|
+
|
|
139
|
+
This is the most important workflow for a maintainer.
|
|
140
|
+
|
|
141
|
+
1. Make your changes to the `template-new-project` repository.
|
|
142
|
+
2. In your terminal, navigate to a directory *outside* of the project.
|
|
143
|
+
3. To simulate a normal user's experience, run:
|
|
144
|
+
```bash
|
|
145
|
+
# Use 'npx caz' to run caz without a global install
|
|
146
|
+
# Point it to the local directory of your template
|
|
147
|
+
# 'my-test-app' is the output directory
|
|
148
|
+
npx caz ./path/to/template-new-project my-test-app --force
|
|
149
|
+
```
|
|
150
|
+
4. To test with local monorepo packages, set the environment variable first:
|
|
151
|
+
```bash
|
|
152
|
+
# Point to the root of your positronic monorepo
|
|
153
|
+
export POSITRONIC_LOCAL_PATH=~/dev/positronic
|
|
154
|
+
|
|
155
|
+
npx caz ./path/to/template-new-project my-local-test-app --force
|
|
156
|
+
```
|
|
157
|
+
5. Inspect the generated `my-test-app` or `my-local-test-app` directory to ensure the files and `package.json` are correct. The `--force` flag is useful for quickly re-running the command.
|
|
158
|
+
|
|
159
|
+
### Gotcha: Accidental Template Processing
|
|
160
|
+
|
|
161
|
+
The `caz` rendering engine is greedy. It scans every non-binary file for its template syntax. If it finds a match, it will attempt to process the *entire file*, which can cause unexpected behavior or break the scaffolding process.
|
|
162
|
+
|
|
163
|
+
**Important**: Any file containing text that matches the `<%= ... %>` or `${...}` patterns will be treated as a template and processed by the engine, which can cause errors if the content is not a valid template variable or expression.
|
|
164
|
+
|
|
165
|
+
For example, imagine you add a new shell script to the template called `configure.sh`:
|
|
166
|
+
|
|
167
|
+
```sh
|
|
168
|
+
#!/bin/bash
|
|
169
|
+
|
|
170
|
+
# This script configures the local environment
|
|
171
|
+
echo "Setting up for user: ${USER}"
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
When `caz` processes this file, it will see `${USER}` and interpret it as a template variable. It will search for a `USER` key in the answers provided by the user during the prompts. If it's not found, the output will likely be `Setting up for user: undefined`, or it could even throw an error, breaking the project generation.
|
|
175
|
+
|
|
176
|
+
### How to Prevent Accidental Processing
|
|
177
|
+
|
|
178
|
+
The codebase reveals two ways to handle this, depending on your goal.
|
|
179
|
+
|
|
180
|
+
1. **Escaping the Syntax (The Preferred Method)**
|
|
181
|
+
|
|
182
|
+
If you need the literal syntax (like `${USER}`) to appear in the final generated file, you must escape it using the template engine itself. The `caz` documentation (`docs/create-template.md`) explicitly describes this:
|
|
183
|
+
|
|
184
|
+
* To output a literal `<%= name %>`, you must write `<%= '\<%= name %\>' %>` in your template file.
|
|
185
|
+
* To output a literal `${name}`, you must write `<%= '${name}' %>` in your template file.
|
|
186
|
+
|
|
187
|
+
2. **Marking the File as Binary**
|
|
188
|
+
|
|
189
|
+
If a file should be copied verbatim with no processing whatsoever, the solution is to instruct `caz` to treat it as a binary file. The renderer explicitly skips binary files.
|
|
190
|
+
|
|
191
|
+
The `@positronic/template-new-project` template already does this for its documentation files as a preventative measure. You can add a similar rule in the `prepare` hook within `index.js`:
|
|
192
|
+
|
|
193
|
+
```javascript
|
|
194
|
+
// in index.js
|
|
195
|
+
prepare: async ctx => {
|
|
196
|
+
// ... existing prepare logic ...
|
|
197
|
+
|
|
198
|
+
// Add a rule to treat all .sh files as binary
|
|
199
|
+
ctx.files.forEach(file => {
|
|
200
|
+
if (file.path.endsWith('.sh')) {
|
|
201
|
+
// This is a made-up property for the example, but it would
|
|
202
|
+
// need to be supported by caz's `isBinary` check.
|
|
203
|
+
// A more robust way is to check the file contents.
|
|
204
|
+
// The key insight is that the prepare hook is where you would
|
|
205
|
+
// manipulate files before rendering.
|
|
206
|
+
// The current template shows a real example for .md files:
|
|
207
|
+
if (file.path.startsWith('docs/') && file.path.endsWith('.md')) {
|
|
208
|
+
file.binary = true; // This is a conceptual flag.
|
|
209
|
+
// The actual implementation is in caz's `isBinary` check.
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
```
|
|
215
|
+
This tells the `render` step to ignore the file completely, ensuring it is copied as-is.
|
package/index.js
ADDED
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
const { name, version } = require('./package.json')
|
|
2
|
+
const path = require('path')
|
|
3
|
+
const { existsSync } = require('fs')
|
|
4
|
+
|
|
5
|
+
module.exports = {
|
|
6
|
+
name,
|
|
7
|
+
version,
|
|
8
|
+
prompts: [
|
|
9
|
+
{
|
|
10
|
+
name: 'name',
|
|
11
|
+
type: 'text',
|
|
12
|
+
message: 'Project name',
|
|
13
|
+
initial: 'my-positronic-project'
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
name: 'backend',
|
|
17
|
+
type: 'select',
|
|
18
|
+
message: 'Select your deployment backend',
|
|
19
|
+
hint: ' ',
|
|
20
|
+
choices: [
|
|
21
|
+
{ title: 'Cloudflare', value: 'cloudflare' },
|
|
22
|
+
{ title: 'AWS Lambda', value: 'aws', disabled: true },
|
|
23
|
+
{ title: 'Azure Functions', value: 'azure', disabled: true },
|
|
24
|
+
{ title: 'Google Cloud Functions', value: 'gcp', disabled: true },
|
|
25
|
+
{ title: 'Fly.io', value: 'fly', disabled: true },
|
|
26
|
+
{ title: 'None (Core only)', value: 'none' }
|
|
27
|
+
],
|
|
28
|
+
initial: 0
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
name: 'install',
|
|
32
|
+
type: 'confirm',
|
|
33
|
+
message: 'Install dependencies',
|
|
34
|
+
initial: true
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
name: 'pm',
|
|
38
|
+
type: prev => prev ? 'select' : null,
|
|
39
|
+
message: 'Package manager',
|
|
40
|
+
hint: ' ',
|
|
41
|
+
choices: [
|
|
42
|
+
{ title: 'npm', value: 'npm' },
|
|
43
|
+
{ title: 'pnpm', value: 'pnpm' },
|
|
44
|
+
{ title: 'yarn', value: 'yarn' }
|
|
45
|
+
]
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
name: 'claudemd',
|
|
49
|
+
type: 'confirm',
|
|
50
|
+
message: 'Initialize CLAUDE.md for AI-assisted development',
|
|
51
|
+
initial: true
|
|
52
|
+
}
|
|
53
|
+
],
|
|
54
|
+
setup: async ctx => {
|
|
55
|
+
const devRootPath = process.env.POSITRONIC_LOCAL_PATH;
|
|
56
|
+
let coreVersion = 'latest';
|
|
57
|
+
let cloudflareVersion = 'latest';
|
|
58
|
+
let clientVercelVersion = 'latest';
|
|
59
|
+
|
|
60
|
+
// Map backend selection to package names
|
|
61
|
+
const backendPackageMap = {
|
|
62
|
+
'cloudflare': '@positronic/cloudflare',
|
|
63
|
+
'aws': '@positronic/aws-lambda', // Future
|
|
64
|
+
'azure': '@positronic/azure-functions', // Future
|
|
65
|
+
'gcp': '@positronic/gcp-functions', // Future
|
|
66
|
+
'fly': '@positronic/fly', // Future
|
|
67
|
+
'none': null
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
ctx.answers.backendPackage = backendPackageMap[ctx.answers.backend];
|
|
71
|
+
|
|
72
|
+
if (devRootPath) {
|
|
73
|
+
console.log(`Found POSITRONIC_LOCAL_PATH: ${devRootPath}. Using local file path for @positronic/core.`);
|
|
74
|
+
const corePath = path.resolve(devRootPath, 'packages', 'core');
|
|
75
|
+
coreVersion = `file:${corePath}`;
|
|
76
|
+
console.log(` - Mapping @positronic/core to ${coreVersion}`);
|
|
77
|
+
|
|
78
|
+
// Handle backend packages for local development
|
|
79
|
+
if (ctx.answers.backend === 'cloudflare') {
|
|
80
|
+
const cloudflarePath = path.resolve(devRootPath, 'packages', 'cloudflare');
|
|
81
|
+
if (existsSync(cloudflarePath)) {
|
|
82
|
+
cloudflareVersion = `file:${cloudflarePath}`;
|
|
83
|
+
// Also update the backend package for the config file
|
|
84
|
+
ctx.answers.backendPackage = `file:${cloudflarePath}`;
|
|
85
|
+
console.log(` - Mapping @positronic/cloudflare to ${cloudflareVersion}`);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
// Add similar blocks for other backends when they exist
|
|
89
|
+
// Example for future backends:
|
|
90
|
+
// if (ctx.answers.backend === 'aws') {
|
|
91
|
+
// const awsPath = path.resolve(devRootPath, 'packages', 'aws-lambda');
|
|
92
|
+
// if (existsSync(awsPath)) {
|
|
93
|
+
// ctx.answers.backendPackage = `file:${awsPath}`;
|
|
94
|
+
// }
|
|
95
|
+
// }
|
|
96
|
+
|
|
97
|
+
const clientVercelPath = path.resolve(devRootPath, 'packages', 'client-vercel');
|
|
98
|
+
if (existsSync(clientVercelPath)) {
|
|
99
|
+
clientVercelVersion = `file:${clientVercelPath}`;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
ctx.answers.positronicCoreVersion = coreVersion;
|
|
104
|
+
if (ctx.answers.backend === 'cloudflare') {
|
|
105
|
+
ctx.answers.positronicCloudflareVersion = cloudflareVersion;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
ctx.answers.positronicClientVercelVersion = clientVercelVersion;
|
|
109
|
+
|
|
110
|
+
if (ctx.answers.install) {
|
|
111
|
+
const pm = ctx.answers.pm;
|
|
112
|
+
if (!pm) {
|
|
113
|
+
ctx.config.install = 'npm';
|
|
114
|
+
ctx.answers.pm = 'npm';
|
|
115
|
+
} else {
|
|
116
|
+
ctx.config.install = pm;
|
|
117
|
+
}
|
|
118
|
+
} else {
|
|
119
|
+
ctx.config.install = false;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Disable git initialization in test environments
|
|
123
|
+
if (process.env.NODE_ENV === 'test') {
|
|
124
|
+
ctx.config.init = false;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
ctx.answers.projectName = ctx.answers.name;
|
|
128
|
+
},
|
|
129
|
+
prepare: async ctx => {
|
|
130
|
+
// Find our specially named gitignore file in the list of files to be processed
|
|
131
|
+
const gitignoreFile = ctx.files.find(file => file.path === '_gitignore');
|
|
132
|
+
if (gitignoreFile) {
|
|
133
|
+
// Change its path to '.gitignore' so it's correctly named in the generated project
|
|
134
|
+
gitignoreFile.path = '.gitignore';
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// Find our specially named env file in the list of files to be processed
|
|
138
|
+
const envFile = ctx.files.find(file => file.path === '_env');
|
|
139
|
+
if (envFile) {
|
|
140
|
+
// Change its path to '.env' so it's correctly named in the generated project
|
|
141
|
+
envFile.path = '.env';
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// Remove CLAUDE.md if user chose not to include it
|
|
145
|
+
if (!ctx.answers.claudemd) {
|
|
146
|
+
ctx.files = ctx.files.filter(file => file.path !== 'CLAUDE.md');
|
|
147
|
+
}
|
|
148
|
+
},
|
|
149
|
+
complete: async ctx => {
|
|
150
|
+
// Display getting started message
|
|
151
|
+
console.log('\n✨ Project created successfully!\n');
|
|
152
|
+
console.log(`📁 Project location: ${ctx.dest}\n`);
|
|
153
|
+
|
|
154
|
+
console.log('🚀 Getting started:\n');
|
|
155
|
+
console.log(` cd ${ctx.answers.name}`);
|
|
156
|
+
|
|
157
|
+
if (!ctx.answers.install) {
|
|
158
|
+
console.log(` ${ctx.answers.pm || 'npm'} install`);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
console.log(' px server # Start the development server');
|
|
162
|
+
console.log(' px brain list # List available brains');
|
|
163
|
+
console.log(' px brain run example # Run the example brain\n');
|
|
164
|
+
|
|
165
|
+
if (ctx.answers.backend === 'cloudflare') {
|
|
166
|
+
console.log('☁️ Cloudflare deployment:\n');
|
|
167
|
+
console.log(' wrangler login # Authenticate with Cloudflare');
|
|
168
|
+
console.log(' px deploy # Deploy to Cloudflare Workers\n');
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
console.log('📚 Resources:\n');
|
|
172
|
+
console.log(' • Documentation: https://positronic.dev');
|
|
173
|
+
console.log(' • GitHub: https://github.com/positronic-ai/positronic');
|
|
174
|
+
console.log(' • CLI Help: px --help\n');
|
|
175
|
+
|
|
176
|
+
if (ctx.answers.claudemd) {
|
|
177
|
+
console.log('💡 Pro tip: Check out CLAUDE.md in your project for AI-assisted development guidance!\n');
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@positronic/template-new-project",
|
|
3
|
+
"version": "0.0.2",
|
|
4
|
+
"publishConfig": {
|
|
5
|
+
"access": "public"
|
|
6
|
+
},
|
|
7
|
+
"description": "Caz template for generating a new Positronic project.",
|
|
8
|
+
"main": "index.js",
|
|
9
|
+
"scripts": {
|
|
10
|
+
"build": "",
|
|
11
|
+
"clean": "rm -rf node_modules"
|
|
12
|
+
},
|
|
13
|
+
"author": "",
|
|
14
|
+
"license": "MIT",
|
|
15
|
+
"devDependencies": {
|
|
16
|
+
"caz": "^2.0.0"
|
|
17
|
+
}
|
|
18
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "<%= projectName %>-server",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"private": true,
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "src/index.ts",
|
|
7
|
+
"scripts": {},
|
|
8
|
+
"dependencies": {
|
|
9
|
+
"@positronic/core": "<%= positronicCoreVersion %>", <% if (backend === 'cloudflare') { %>
|
|
10
|
+
"@positronic/cloudflare": "<%= positronicCloudflareVersion %>", <% } %>
|
|
11
|
+
"zod": "^3.24.1"
|
|
12
|
+
},
|
|
13
|
+
"devDependencies": {
|
|
14
|
+
"@cloudflare/workers-types": "^4.20240405.0",
|
|
15
|
+
"typescript": "^5.0.0"
|
|
16
|
+
}
|
|
17
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import {
|
|
2
|
+
api as app,
|
|
3
|
+
setManifest,
|
|
4
|
+
setBrainRunner,
|
|
5
|
+
BrainRunnerDO,
|
|
6
|
+
MonitorDO,
|
|
7
|
+
ScheduleDO,
|
|
8
|
+
PositronicManifest,
|
|
9
|
+
} from "@positronic/cloudflare";
|
|
10
|
+
// Import the generated manifest - NOTE the .js extension for runtime compatibility
|
|
11
|
+
// @ts-expect-error - _manifest.js is generated during template processing
|
|
12
|
+
import { staticManifest } from "./_manifest.js";
|
|
13
|
+
import { runner } from "./runner.js";
|
|
14
|
+
// Configure the manifest to use the statically generated list
|
|
15
|
+
const manifest = new PositronicManifest({
|
|
16
|
+
staticManifest,
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
setManifest(manifest);
|
|
20
|
+
setBrainRunner(runner);
|
|
21
|
+
|
|
22
|
+
// Define Env type based on wrangler.jsonc bindings
|
|
23
|
+
interface Env {
|
|
24
|
+
BRAIN_RUNNER_DO: DurableObjectNamespace<BrainRunnerDO>;
|
|
25
|
+
MONITOR_DO: DurableObjectNamespace<MonitorDO>;
|
|
26
|
+
SCHEDULE_DO: DurableObjectNamespace<ScheduleDO>;
|
|
27
|
+
RESOURCES_BUCKET: R2Bucket;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// Export the API handler and Durable Objects
|
|
31
|
+
export default {
|
|
32
|
+
fetch: app.fetch,
|
|
33
|
+
} satisfies ExportedHandler<Env>;
|
|
34
|
+
|
|
35
|
+
export { BrainRunnerDO, MonitorDO, ScheduleDO };
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ESNext",
|
|
4
|
+
"module": "ESNext",
|
|
5
|
+
"lib": ["ESNext"],
|
|
6
|
+
"moduleResolution": "node",
|
|
7
|
+
"esModuleInterop": true,
|
|
8
|
+
"strict": true,
|
|
9
|
+
"resolveJsonModule": true,
|
|
10
|
+
"isolatedModules": true,
|
|
11
|
+
"skipLibCheck": true,
|
|
12
|
+
"incremental": true,
|
|
13
|
+
"types": ["@cloudflare/workers-types"],
|
|
14
|
+
"outDir": "dist",
|
|
15
|
+
"rootDir": "src"
|
|
16
|
+
},
|
|
17
|
+
"include": ["src/**/*.ts"],
|
|
18
|
+
"exclude": ["node_modules", "dist"]
|
|
19
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://raw.githubusercontent.com/cloudflare/wrangler/main/config-schema.json",
|
|
3
|
+
"name": "<%= projectName %>",
|
|
4
|
+
"main": "src/index.ts",
|
|
5
|
+
"compatibility_date": "2024-09-23",
|
|
6
|
+
"compatibility_flags": ["nodejs_compat", "nodejs_compat_populate_process_env"],
|
|
7
|
+
"workers_dev": true,
|
|
8
|
+
"migrations": [
|
|
9
|
+
{
|
|
10
|
+
"tag": "v1",
|
|
11
|
+
"new_sqlite_classes": ["BrainRunnerDO", "MonitorDO", "ScheduleDO"]
|
|
12
|
+
}
|
|
13
|
+
],
|
|
14
|
+
"durable_objects": {
|
|
15
|
+
"bindings": [
|
|
16
|
+
{ "name": "BRAIN_RUNNER_DO", "class_name": "BrainRunnerDO" },
|
|
17
|
+
{ "name": "MONITOR_DO", "class_name": "MonitorDO" },
|
|
18
|
+
{ "name": "SCHEDULE_DO", "class_name": "ScheduleDO" }
|
|
19
|
+
]
|
|
20
|
+
},
|
|
21
|
+
"r2_buckets": [
|
|
22
|
+
{
|
|
23
|
+
"binding": "RESOURCES_BUCKET",
|
|
24
|
+
"bucket_name": "<%= projectName %>"
|
|
25
|
+
}
|
|
26
|
+
],
|
|
27
|
+
"vars": {
|
|
28
|
+
"R2_BUCKET_NAME": "<%= projectName %>"
|
|
29
|
+
},
|
|
30
|
+
"env": {
|
|
31
|
+
"production": {
|
|
32
|
+
"name": "<%= projectName %>",
|
|
33
|
+
"workers_dev": false,
|
|
34
|
+
"vars": {
|
|
35
|
+
"NODE_ENV": "production",
|
|
36
|
+
"R2_BUCKET_NAME": "<%= projectName %>"
|
|
37
|
+
},
|
|
38
|
+
"durable_objects": {
|
|
39
|
+
"bindings": [
|
|
40
|
+
{ "name": "BRAIN_RUNNER_DO", "class_name": "BrainRunnerDO" },
|
|
41
|
+
{ "name": "MONITOR_DO", "class_name": "MonitorDO" },
|
|
42
|
+
{ "name": "SCHEDULE_DO", "class_name": "ScheduleDO" }
|
|
43
|
+
]
|
|
44
|
+
},
|
|
45
|
+
"r2_buckets": [
|
|
46
|
+
{
|
|
47
|
+
"binding": "RESOURCES_BUCKET",
|
|
48
|
+
"bucket_name": "<%= projectName %>"
|
|
49
|
+
}
|
|
50
|
+
]
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
# CLAUDE.md
|
|
2
|
+
|
|
3
|
+
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
|
4
|
+
|
|
5
|
+
## Project Overview
|
|
6
|
+
|
|
7
|
+
This is a Positronic project - an AI-powered framework for building and running "brains" (stateful AI workflows) that can be deployed to various cloud backends. It provides a fluent DSL for defining AI workflows, resource management, and a CLI for development and deployment.
|
|
8
|
+
|
|
9
|
+
## Project Structure
|
|
10
|
+
|
|
11
|
+
- **`/brains`** - AI workflow definitions using the Brain DSL
|
|
12
|
+
- **`/resources`** - Files and documents that brains can access via the resource system
|
|
13
|
+
- **`/tests`** - Test files for brains (kept separate to avoid deployment issues)
|
|
14
|
+
- **`/docs`** - Documentation including brain testing guide
|
|
15
|
+
- **`/runner.ts`** - The main entry point for running brains locally
|
|
16
|
+
- **`/positronic.config.json`** - Project configuration
|
|
17
|
+
|
|
18
|
+
## Key Commands
|
|
19
|
+
|
|
20
|
+
### Development
|
|
21
|
+
|
|
22
|
+
- `px brain run <brain-name>` - Run a brain workflow
|
|
23
|
+
- `px brain list` - List all available brains
|
|
24
|
+
- `px resource list` - List all available resources
|
|
25
|
+
- `px server` - Start the local development server (add `-d` for background mode)
|
|
26
|
+
|
|
27
|
+
### Testing & Building
|
|
28
|
+
|
|
29
|
+
- `npm test` - Run tests (uses Jest with @positronic/core/testing utilities)
|
|
30
|
+
- `npm run build` - Build the project
|
|
31
|
+
- `npm run dev` - Start development mode with hot reload
|
|
32
|
+
|
|
33
|
+
For testing guidance, see `/docs/brain-testing-guide.md`
|
|
34
|
+
|
|
35
|
+
## Brain DSL
|
|
36
|
+
|
|
37
|
+
The Brain DSL provides a fluent API for defining AI workflows:
|
|
38
|
+
|
|
39
|
+
```typescript
|
|
40
|
+
import { brain } from '@positronic/core';
|
|
41
|
+
|
|
42
|
+
const myBrain = brain('my-brain')
|
|
43
|
+
.step('Initialize', ({ state }) => ({
|
|
44
|
+
...state,
|
|
45
|
+
initialized: true
|
|
46
|
+
}))
|
|
47
|
+
.step('Process', async ({ state, resources }) => {
|
|
48
|
+
// Access resources
|
|
49
|
+
const doc = await resources.get('example.md');
|
|
50
|
+
return {
|
|
51
|
+
...state,
|
|
52
|
+
processed: true,
|
|
53
|
+
content: doc.content
|
|
54
|
+
};
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
export default myBrain;
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## Resource System
|
|
61
|
+
|
|
62
|
+
Resources are files that brains can access during execution. They're stored in the `/resources` directory and are automatically typed based on the manifest.
|
|
63
|
+
|
|
64
|
+
## Development Workflow
|
|
65
|
+
|
|
66
|
+
1. Define your brain in `/brains`
|
|
67
|
+
2. Add any required resources to `/resources`
|
|
68
|
+
3. Run `px brain run <brain-name>` to test locally
|
|
69
|
+
4. Deploy using backend-specific commands
|
|
70
|
+
|
|
71
|
+
## Backend-Specific Notes
|
|
72
|
+
|
|
73
|
+
<% if (backend === 'cloudflare') { %>
|
|
74
|
+
### Cloudflare Workers
|
|
75
|
+
|
|
76
|
+
This project is configured for Cloudflare Workers deployment:
|
|
77
|
+
|
|
78
|
+
- Uses Durable Objects for state persistence
|
|
79
|
+
- R2 for resource storage
|
|
80
|
+
- Requires Cloudflare account and API keys
|
|
81
|
+
|
|
82
|
+
Deployment:
|
|
83
|
+
```bash
|
|
84
|
+
# Configure Cloudflare credentials
|
|
85
|
+
wrangler login
|
|
86
|
+
|
|
87
|
+
# Deploy
|
|
88
|
+
px deploy
|
|
89
|
+
```
|
|
90
|
+
<% } else if (backend === 'none') { %>
|
|
91
|
+
### Core Only
|
|
92
|
+
|
|
93
|
+
This project uses only the Positronic core without a specific deployment backend. You can:
|
|
94
|
+
|
|
95
|
+
- Run brains locally using `px brain run`
|
|
96
|
+
- Add a backend later by installing the appropriate package
|
|
97
|
+
- Use the framework for local AI workflow development
|
|
98
|
+
<% } %>
|
|
99
|
+
|
|
100
|
+
## Best Practices
|
|
101
|
+
|
|
102
|
+
1. **State Management**: Keep brain state minimal and serializable
|
|
103
|
+
2. **Resource Naming**: Use descriptive names for resources (e.g., `prompt-templates/customer-support.md`)
|
|
104
|
+
3. **Error Handling**: Always handle potential errors in brain steps
|
|
105
|
+
4. **Testing**: Write tests for your brains focusing on outcomes, not implementation details (see `/docs/brain-testing-guide.md`)
|
|
106
|
+
|
|
107
|
+
## Getting Help
|
|
108
|
+
|
|
109
|
+
- Documentation: https://positronic.dev
|
|
110
|
+
- GitHub: https://github.com/positronic-ai/positronic
|
|
111
|
+
- CLI Help: `px --help` or `px <command> --help`
|
|
112
|
+
|
|
113
|
+
## Project-Level Patterns
|
|
114
|
+
|
|
115
|
+
For project structure, the project brain pattern, and other Positronic conventions, see:
|
|
116
|
+
@docs/positronic-guide.md
|
|
117
|
+
|
|
118
|
+
## Additional Tips for AI Agents
|
|
119
|
+
|
|
120
|
+
@docs/tips-for-agents.md
|