create-brutal-ai 0.0.0-stage → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +6 -2
  2. package/index.js +141 -0
  3. package/package.json +20 -4
package/README.md CHANGED
@@ -1,3 +1,7 @@
1
- # Temporary Holding Version
1
+ # create-brutal-ai
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Deploy your own [Brutal](https://github.com/cldixon/brutal) to your Cloudflare account:
4
+
5
+ ```sh
6
+ bun create brutal-ai my-brutal
7
+ ```
package/index.js ADDED
@@ -0,0 +1,141 @@
1
+ #!/usr/bin/env bun
2
+ // @bun
3
+
4
+ // packages/create-brutal-ai/src/index.ts
5
+ import { existsSync, mkdirSync, readdirSync, writeFileSync } from "fs";
6
+ import { dirname, join, resolve } from "path";
7
+ var {$ } = globalThis.Bun;
8
+ var args = process.argv.slice(2);
9
+ var flag = (name) => {
10
+ const i = args.indexOf(name);
11
+ return i === -1 ? undefined : args.splice(i, 2)[1];
12
+ };
13
+ var from = flag("--from");
14
+ var noDeploy = args.includes("--no-deploy");
15
+ var folder = args.find((a) => !a.startsWith("--")) ?? "my-brutal";
16
+ var dir = resolve(folder);
17
+ process.env.CF_SEND_TELEMETRY ??= "false";
18
+ if (existsSync(dir) && readdirSync(dir).length > 0) {
19
+ console.error(`${folder} already exists and isn't empty.`);
20
+ process.exit(1);
21
+ }
22
+ console.log(`
23
+ Creating a Brutal instance in ${folder}
24
+ `);
25
+ mkdirSync(dir, { recursive: true });
26
+ var files = {
27
+ "package.json": `${JSON.stringify({
28
+ name: folder.split("/").pop(),
29
+ private: true,
30
+ type: "module",
31
+ scripts: {
32
+ setup: "brutal-server setup",
33
+ deploy: "brutal-server deploy",
34
+ dev: "brutal-server dev",
35
+ secrets: "brutal-server secrets",
36
+ access: "brutal-server access"
37
+ },
38
+ dependencies: {
39
+ "@brutal-ai/server": from ? resolveSpec(from) : `^${"0.2.0"}`,
40
+ cf: "1.0.0-beta.28",
41
+ wrangler: "^4.149.0"
42
+ }
43
+ }, null, 2)}
44
+ `,
45
+ "cloudflare.config.ts": `import { defineBrutal } from "@brutal-ai/server/config";
46
+
47
+ // Your settings are in brutal.config.json (\`bun run setup\` writes it).
48
+ export default defineBrutal({ dir: import.meta.dirname });
49
+ `,
50
+ ".github/workflows/deploy.yml": `name: Deploy
51
+
52
+ # Deploys this Brutal instance on every push to main, using the repo secret
53
+ # CLOUDFLARE_API_TOKEN. Without the secret, it skips with a notice.
54
+ on:
55
+ push:
56
+ branches: [main]
57
+ workflow_dispatch:
58
+
59
+ concurrency:
60
+ group: deploy
61
+ cancel-in-progress: false
62
+
63
+ jobs:
64
+ deploy:
65
+ runs-on: ubuntu-latest
66
+ env:
67
+ CLOUDFLARE_API_TOKEN: \${{ secrets.CLOUDFLARE_API_TOKEN }}
68
+ CF_SEND_TELEMETRY: "false"
69
+ steps:
70
+ - name: No token, no deploy
71
+ if: env.CLOUDFLARE_API_TOKEN == ''
72
+ run: echo "::notice::Not deploying. Add the CLOUDFLARE_API_TOKEN repo secret to deploy on push."
73
+ - uses: actions/checkout@v5
74
+ if: env.CLOUDFLARE_API_TOKEN != ''
75
+ - uses: oven-sh/setup-bun@v2
76
+ if: env.CLOUDFLARE_API_TOKEN != ''
77
+ - run: bun install --frozen-lockfile
78
+ if: env.CLOUDFLARE_API_TOKEN != ''
79
+ - run: bun run deploy
80
+ if: env.CLOUDFLARE_API_TOKEN != ''
81
+ `,
82
+ ".gitignore": `node_modules/
83
+ .cloudflare/
84
+ .wrangler/
85
+ `,
86
+ "README.md": `# My Brutal
87
+
88
+ A [Brutal](https://github.com/cldixon/brutal) instance: feedback from your agents, on your own
89
+ Cloudflare account. Its settings are in \`brutal.config.json\`; the code comes from
90
+ [\`@brutal-ai/server\`](https://www.npmjs.com/package/@brutal-ai/server).
91
+
92
+ | Command | What it does |
93
+ | --- | --- |
94
+ | \`bun run setup\` | Change the settings (re-run any time) |
95
+ | \`bun run deploy\` | Deploy. The first deploy prints the setup code: open \`/setup\` and enter it for your keys |
96
+ | \`bun run dev\` | Run it locally on http://localhost:8787 |
97
+ | \`bun run access\` | Sign in to \`/dashboard\` with Cloudflare Access (a code mailed to you) instead of the read key; needs a custom domain. Then \`bun run deploy\` |
98
+
99
+ **Update Brutal:** \`bun update @brutal-ai/server && bun run deploy\`. Migrations apply themselves.
100
+
101
+ **Deploy on push (optional):** \`.github/workflows/deploy.yml\` deploys every push to \`main\` once
102
+ this repo has the secret \`CLOUDFLARE_API_TOKEN\` (a Cloudflare API token from the "Edit Cloudflare
103
+ Workers" template, plus D1 Edit). Until then it skips, and \`bun run deploy\` from your machine is
104
+ all you need.
105
+ `
106
+ };
107
+ for (const [name, content] of Object.entries(files)) {
108
+ mkdirSync(dirname(join(dir, name)), { recursive: true });
109
+ writeFileSync(join(dir, name), content);
110
+ }
111
+ console.log("\u2713 Wrote the instance folder");
112
+ await $`bun install`.cwd(dir).quiet();
113
+ console.log("\u2713 Installed @brutal-ai/server");
114
+ if ((await $`git --version`.nothrow().quiet()).exitCode === 0) {
115
+ await $`git init -q`.cwd(dir).nothrow();
116
+ }
117
+ var whoami = await $`bunx cf auth whoami`.cwd(dir).nothrow().quiet();
118
+ var signedIn = false;
119
+ try {
120
+ signedIn = whoami.exitCode === 0 && JSON.parse(whoami.stdout.toString()).authenticated;
121
+ } catch {}
122
+ if (!signedIn) {
123
+ console.log(`
124
+ Sign in to Cloudflare (a browser window opens):`);
125
+ await interactive(["bunx", "cf", "auth", "login"]);
126
+ }
127
+ await interactive(["bunx", "brutal-server", "setup"]);
128
+ var answer = noDeploy ? null : prompt("Deploy it now? [Y/n]");
129
+ if (answer !== null && answer.trim().toLowerCase() !== "n") {
130
+ await interactive(["bunx", "brutal-server", "deploy"]);
131
+ }
132
+ console.log(`Done. Your instance lives in ${folder}: commit it, and deploy with \`bun run deploy\`.
133
+ `);
134
+ async function interactive(cmd) {
135
+ const code = await Bun.spawn(cmd, { cwd: dir, stdio: ["inherit", "inherit", "inherit"] }).exited;
136
+ if (code !== 0)
137
+ process.exit(code);
138
+ }
139
+ function resolveSpec(spec) {
140
+ return /^(\.|\/).*\.tgz$/.test(spec) ? resolve(spec) : spec;
141
+ }
package/package.json CHANGED
@@ -1,6 +1,22 @@
1
1
  {
2
+ "version": "0.2.0",
3
+ "license": "MIT",
2
4
  "name": "create-brutal-ai",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
5
+ "description": "Create your own Brutal instance on your Cloudflare account",
6
+ "type": "module",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "git+https://github.com/cldixon/brutal.git",
10
+ "directory": "packages/create-brutal-ai"
11
+ },
12
+ "bin": {
13
+ "create-brutal-ai": "index.js"
14
+ },
15
+ "files": [
16
+ "index.js",
17
+ "README.md"
18
+ ],
19
+ "engines": {
20
+ "bun": ">=1.2.0"
21
+ }
22
+ }