create-brutal-ai 0.0.0-stage → 0.1.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 +139 -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,139 @@
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
+ },
37
+ dependencies: {
38
+ "@brutal-ai/server": from ? resolveSpec(from) : `^${"0.1.0"}`,
39
+ cf: "1.0.0-beta.28",
40
+ wrangler: "^4.149.0"
41
+ }
42
+ }, null, 2)}
43
+ `,
44
+ "cloudflare.config.ts": `import { defineBrutal } from "@brutal-ai/server/config";
45
+
46
+ // Your settings are in brutal.config.json (\`bun run setup\` writes it).
47
+ export default defineBrutal({ dir: import.meta.dirname });
48
+ `,
49
+ ".github/workflows/deploy.yml": `name: Deploy
50
+
51
+ # Deploys this Brutal instance on every push to main, using the repo secret
52
+ # CLOUDFLARE_API_TOKEN. Without the secret, it skips with a notice.
53
+ on:
54
+ push:
55
+ branches: [main]
56
+ workflow_dispatch:
57
+
58
+ concurrency:
59
+ group: deploy
60
+ cancel-in-progress: false
61
+
62
+ jobs:
63
+ deploy:
64
+ runs-on: ubuntu-latest
65
+ env:
66
+ CLOUDFLARE_API_TOKEN: \${{ secrets.CLOUDFLARE_API_TOKEN }}
67
+ CF_SEND_TELEMETRY: "false"
68
+ steps:
69
+ - name: No token, no deploy
70
+ if: env.CLOUDFLARE_API_TOKEN == ''
71
+ run: echo "::notice::Not deploying. Add the CLOUDFLARE_API_TOKEN repo secret to deploy on push."
72
+ - uses: actions/checkout@v5
73
+ if: env.CLOUDFLARE_API_TOKEN != ''
74
+ - uses: oven-sh/setup-bun@v2
75
+ if: env.CLOUDFLARE_API_TOKEN != ''
76
+ - run: bun install --frozen-lockfile
77
+ if: env.CLOUDFLARE_API_TOKEN != ''
78
+ - run: bun run deploy
79
+ if: env.CLOUDFLARE_API_TOKEN != ''
80
+ `,
81
+ ".gitignore": `node_modules/
82
+ .cloudflare/
83
+ .wrangler/
84
+ `,
85
+ "README.md": `# My Brutal
86
+
87
+ A [Brutal](https://github.com/cldixon/brutal) instance: feedback from your agents, on your own
88
+ Cloudflare account. Its settings are in \`brutal.config.json\`; the code comes from
89
+ [\`@brutal-ai/server\`](https://www.npmjs.com/package/@brutal-ai/server).
90
+
91
+ | Command | What it does |
92
+ | --- | --- |
93
+ | \`bun run setup\` | Change the settings (re-run any time) |
94
+ | \`bun run deploy\` | Deploy. The first deploy prints the setup code: open \`/setup\` and enter it for your keys |
95
+ | \`bun run dev\` | Run it locally on http://localhost:8787 |
96
+
97
+ **Update Brutal:** \`bun update @brutal-ai/server && bun run deploy\`. Migrations apply themselves.
98
+
99
+ **Deploy on push (optional):** \`.github/workflows/deploy.yml\` deploys every push to \`main\` once
100
+ this repo has the secret \`CLOUDFLARE_API_TOKEN\` (a Cloudflare API token from the "Edit Cloudflare
101
+ Workers" template, plus D1 Edit). Until then it skips, and \`bun run deploy\` from your machine is
102
+ all you need.
103
+ `
104
+ };
105
+ for (const [name, content] of Object.entries(files)) {
106
+ mkdirSync(dirname(join(dir, name)), { recursive: true });
107
+ writeFileSync(join(dir, name), content);
108
+ }
109
+ console.log("\u2713 Wrote the instance folder");
110
+ await $`bun install`.cwd(dir).quiet();
111
+ console.log("\u2713 Installed @brutal-ai/server");
112
+ if ((await $`git --version`.nothrow().quiet()).exitCode === 0) {
113
+ await $`git init -q`.cwd(dir).nothrow();
114
+ }
115
+ var whoami = await $`bunx cf auth whoami`.cwd(dir).nothrow().quiet();
116
+ var signedIn = false;
117
+ try {
118
+ signedIn = whoami.exitCode === 0 && JSON.parse(whoami.stdout.toString()).authenticated;
119
+ } catch {}
120
+ if (!signedIn) {
121
+ console.log(`
122
+ Sign in to Cloudflare (a browser window opens):`);
123
+ await interactive(["bunx", "cf", "auth", "login"]);
124
+ }
125
+ await interactive(["bunx", "brutal-server", "setup"]);
126
+ var answer = noDeploy ? null : prompt("Deploy it now? [Y/n]");
127
+ if (answer !== null && answer.trim().toLowerCase() !== "n") {
128
+ await interactive(["bunx", "brutal-server", "deploy"]);
129
+ }
130
+ console.log(`Done. Your instance lives in ${folder}: commit it, and deploy with \`bun run deploy\`.
131
+ `);
132
+ async function interactive(cmd) {
133
+ const code = await Bun.spawn(cmd, { cwd: dir, stdio: ["inherit", "inherit", "inherit"] }).exited;
134
+ if (code !== 0)
135
+ process.exit(code);
136
+ }
137
+ function resolveSpec(spec) {
138
+ return /^(\.|\/).*\.tgz$/.test(spec) ? resolve(spec) : spec;
139
+ }
package/package.json CHANGED
@@ -1,6 +1,22 @@
1
1
  {
2
+ "version": "0.1.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
+ }