@p10i/create-nestbox 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 (43) hide show
  1. package/.dockerignore +34 -0
  2. package/.env.example +35 -0
  3. package/.gitattributes +10 -0
  4. package/INSTALL.md +258 -0
  5. package/LICENSE +21 -0
  6. package/README.md +41 -0
  7. package/bin/create-nestbox.js +184 -0
  8. package/docker/nginx/Dockerfile +31 -0
  9. package/docker/opencode/Dockerfile +36 -0
  10. package/docker/opencode/entrypoint.sh +9 -0
  11. package/docker/php-fpm/Dockerfile +43 -0
  12. package/docker/rollup/Dockerfile +16 -0
  13. package/docker-compose.yaml +139 -0
  14. package/home/code/.user.ini +1 -0
  15. package/home/code/404.php +104 -0
  16. package/home/code/__data/.gitignore +2 -0
  17. package/home/code/__includes/footer.php +3 -0
  18. package/home/code/__includes/functions.php +325 -0
  19. package/home/code/__includes/header.php +20 -0
  20. package/home/code/__templates/super-document.php +47 -0
  21. package/home/code/scripts-bundle.css +13295 -0
  22. package/home/code/scripts-bundle.js +6277 -0
  23. package/home/code/scripts.js +3 -0
  24. package/home/code/styles.css +36 -0
  25. package/home/configs/nginx/nginx.conf +199 -0
  26. package/home/configs/opencode/instance.example.md +16 -0
  27. package/home/configs/opencode/instructions.md +308 -0
  28. package/home/configs/opencode/opencode.json +15 -0
  29. package/home/configs/php/cli.php +33 -0
  30. package/home/configs/php/composer.json +10 -0
  31. package/home/configs/php/composer.lock +805 -0
  32. package/home/configs/php/index.php +105 -0
  33. package/home/configs/php/init.php +3 -0
  34. package/home/configs/php/php.ini +5 -0
  35. package/home/configs/php/preload.php +5 -0
  36. package/home/configs/rollup/package-lock.json +2089 -0
  37. package/home/configs/rollup/package.json +16 -0
  38. package/home/configs/rollup/rollup.config.js +28 -0
  39. package/package.json +42 -0
  40. package/scripts/check-package.js +31 -0
  41. package/template.gitignore +20 -0
  42. package/tests/nestbox.ps1 +322 -0
  43. package/tests/nestbox.sh +438 -0
package/.dockerignore ADDED
@@ -0,0 +1,34 @@
1
+ # Build context hygiene — keep image builds small and reproducible.
2
+
3
+ # VCS / IDE
4
+ .git
5
+ **/.git
6
+ .gitignore
7
+ .vscode
8
+ .idea
9
+
10
+ # Local environment
11
+ .env
12
+ .env.*
13
+ !.env.example
14
+
15
+ # npm / Composer caches that may live in the working tree
16
+ node_modules
17
+ vendor
18
+
19
+ # Page state is local by default and must not leak into image layers. A page
20
+ # that deliberately ships durable data must explicitly revise this policy.
21
+ home/code/**/__data/**
22
+
23
+ # OS junk
24
+ .DS_Store
25
+ Thumbs.db
26
+
27
+ # Bundles are produced inside the build, not pulled from the host.
28
+ home/code/scripts-bundle.js
29
+ home/code/scripts-bundle.css
30
+
31
+ # Docs and metadata not needed at build time
32
+ README.md
33
+ LICENSE
34
+ tests
package/.env.example ADDED
@@ -0,0 +1,35 @@
1
+ # Copy this file to .env next to docker-compose.yaml and adjust as needed.
2
+
3
+ # Docker Compose
4
+ # Set a unique slug for this project before starting Nestbox.
5
+ COMPOSE_PROJECT_NAME=nestbox-change-me
6
+
7
+ # Workspace exposed to PHP and OpenCode. Use . when Nestbox fills the selected
8
+ # directory, or .. when this installation is <workspace>/.nestbox.
9
+ WORKSPACE_PATH=.
10
+
11
+ # Use 127.0.0.1 for local access only or 0.0.0.0 for all interfaces.
12
+ BIND_ADDRESS=127.0.0.1
13
+
14
+ # Published port shared by Nestbox and OpenCode. The installer confirms this
15
+ # default is available and asks before selecting another port.
16
+ WEB_PORT=4180
17
+
18
+ # Optional public path when a reverse proxy strips this prefix before forwarding.
19
+ NESTBOX_URL_PREFIX=
20
+
21
+ # Optional absolute browser origin when the agent.* host cannot be derived from
22
+ # the incoming Nestbox host, for example https://agent.project.example.
23
+ NESTBOX_OPENCODE_PUBLIC_URL=
24
+
25
+ # Runtime ownership
26
+ APP_UID=1000
27
+ APP_GID=1000
28
+
29
+ # Secrets
30
+ # OpenCode receives every variable in this file. Keep unrelated secrets out.
31
+ # Leave the server password empty to disable HTTP basic authentication.
32
+ OPENCODE_SERVER_USERNAME=opencode
33
+ OPENCODE_SERVER_PASSWORD=
34
+ ANTHROPIC_API_KEY=
35
+ OPENAI_API_KEY=
package/.gitattributes ADDED
@@ -0,0 +1,10 @@
1
+ * text=auto
2
+ *.css text eol=lf
3
+ *.js text eol=lf
4
+ *.json text eol=lf
5
+ *.md text eol=lf
6
+ *.php text eol=lf
7
+ *.ps1 text eol=lf
8
+ *.sh text eol=lf
9
+ *.yaml text eol=lf
10
+ *.yml text eol=lf
package/INSTALL.md ADDED
@@ -0,0 +1,258 @@
1
+ # Agent Installation Guide
2
+
3
+ Follow this protocol when installing Nestbox into a selected directory or at `<workspace>/.nestbox`.
4
+
5
+ ## Npm Creator
6
+
7
+ For ordinary new projects, prefer the npm creator:
8
+
9
+ ```sh
10
+ npm create @p10i/nestbox@latest
11
+ ```
12
+
13
+ The published npm package is `@p10i/create-nestbox`; npm maps `npm create @p10i/nestbox` to that package. The creator scaffolds one of the two default layouts, copies `.env.example` to `.env`, copies `home/configs/opencode/instance.example.md` to `instance.md`, records npm package provenance in the instance policy, and initializes fresh installation Git history when Git is available.
14
+
15
+ The creator does not start Docker or collect secrets. After it finishes, edit `.env`, then run from the installation directory:
16
+
17
+ ```sh
18
+ docker compose config --quiet
19
+ docker compose up -d --build
20
+ ```
21
+
22
+ Then run the host test suite documented in section 7.
23
+
24
+ ## 1. Choose Language
25
+
26
+ Before technical questions, ask which language to use for installation communication. Then distinguish three independent choices:
27
+
28
+ - agent communication only in that language;
29
+ - a translated Nestbox interface;
30
+ - an extensible i18n setup for multiple interface languages.
31
+
32
+ Record the communication and interface choices in `home/configs/opencode/instance.md`. Nestbox source is English by default. Translation and i18n are feature work for the internal OpenCode agent after basic startup, not a reason for the external installer to maintain a second implementation path.
33
+
34
+ ## 2. Choose One Default Layout
35
+
36
+ Resolve and report the absolute selected directory. Offer exactly two defaults:
37
+
38
+ 1. **Current directory:** install Nestbox directly into the selected directory, then initialize fresh installation history at `<selected>/.git`.
39
+ 2. **`.nestbox`:** treat the selected directory as the workspace, install Nestbox at `<workspace>/.nestbox`, then initialize fresh installation history at `<workspace>/.nestbox/.git`.
40
+
41
+ Both defaults keep pages, Compose, Dockerfiles, configuration, dependencies, and documentation in the same fresh installation repository. Pages remain under the installation's `home/code`. Upstream Nestbox commit history is not imported into the user's installation history.
42
+
43
+ Do not present vendoring, a parent-owned engine, a submodule, an external page tree, or another shared-repository arrangement as a default. These are optional adaptations discussed only when the user requests one.
44
+
45
+ Never overwrite an existing installation. Inspect an existing target and ask whether to update, repair, reuse, or stop.
46
+
47
+ ## 3. Check Requirements And Git
48
+
49
+ Run:
50
+
51
+ ```sh
52
+ git --version
53
+ docker version
54
+ docker compose version
55
+ ```
56
+
57
+ Nestbox requires Git, a running Docker Engine, and Docker Compose v2. If something is missing, explain the platform-specific requirement and pause. Do not install system software without approval.
58
+
59
+ Inspect Git status for the selected directory and installation target before changing files. Preserve unrelated changes. The fresh Nestbox repository remains independent in both default layouts, including when `.nestbox` is nested inside another Git workspace.
60
+
61
+ If the `.nestbox` target is inside another Git repository, ask whether to exclude `.nestbox/` through that repository's local `.git/info/exclude` or tracked `.gitignore`. Do not modify the parent repository without approval.
62
+
63
+ ## 4. Create The Installation
64
+
65
+ For the current-directory layout, shallow-clone the source into the selected empty directory:
66
+
67
+ ```sh
68
+ git clone --depth 1 https://github.com/piontkovsk11andre1/nestbox .
69
+ ```
70
+
71
+ For the `.nestbox` layout, run from the confirmed workspace:
72
+
73
+ ```sh
74
+ git clone --depth 1 https://github.com/piontkovsk11andre1/nestbox .nestbox
75
+ ```
76
+
77
+ Immediately after cloning:
78
+
79
+ 1. Resolve and record the source revision with `git rev-parse HEAD` from the clone.
80
+ 2. Remove only the `.git` directory created by this new clone. Never remove Git metadata from a pre-existing path.
81
+ 3. Run `git init` in the installation directory.
82
+ 4. Continue configuration and verification before creating the installation baseline commit.
83
+
84
+ The resulting `.git` contains only this installation's history. Keeping upstream history or adding an upstream remote is an optional adaptation, not a default.
85
+
86
+ ## 5. Configure The Instance
87
+
88
+ Copy `.env.example` to `.env` and `home/configs/opencode/instance.example.md` to `home/configs/opencode/instance.md` in the installation directory. Both defaults use the installation's `./home/code` page tree. Configure the workspace mount as follows:
89
+
90
+ | Layout | `WORKSPACE_PATH` |
91
+ | --- | --- |
92
+ | Current directory | `.` |
93
+ | `<workspace>/.nestbox` | `..` |
94
+
95
+ Relative bind paths resolve from the installation directory. The page tree is mounted as `/home/code` in runtime services and as the writable `/nestbox/home/code` agent path. `WORKSPACE_PATH` is mounted as `/workspace` in PHP and OpenCode. `NESTBOX_PAGE_PATH` remains an optional advanced override for a user-requested external page tree.
96
+
97
+ Update the new `home/configs/opencode/instance.md` with:
98
+
99
+ - the communication language;
100
+ - interface language or requested i18n direction;
101
+ - installation and workspace paths;
102
+ - the Nestbox Git root;
103
+ - the recorded upstream source revision;
104
+ - whether verified Nestbox changes are committed automatically, require per-commit confirmation, or remain uncommitted.
105
+
106
+ This is agent guidance and belongs in `instance.md`, not `.env`.
107
+
108
+ Then ask about:
109
+
110
+ - a unique lowercase `COMPOSE_PROJECT_NAME`;
111
+ - whether to use default port `4180`; verify availability and ask before choosing another port;
112
+ - loopback-only or network access;
113
+ - OpenCode username and password;
114
+ - AI providers;
115
+ - Linux UID and GID when bind-mount ownership matters;
116
+ - additional directories required by PHP and OpenCode;
117
+ - an optional reverse-proxy URL prefix;
118
+ - rolling or pinned container images;
119
+ - `NESTBOX_OPENCODE_PUBLIC_URL` when `agent.<nestbox-host>` cannot be derived.
120
+
121
+ Open `.env` in a local editor for runtime settings and secrets. Never ask the user to paste provider keys into chat or print the completed file.
122
+
123
+ Before startup, query existing Docker containers with the proposed Compose project label. Reject the name if an existing container belongs to another Compose working directory or uses a service not defined by this installation. Do not remove foreign containers or volumes automatically.
124
+
125
+ For additional directories, mount the same host path at the same container path in PHP and OpenCode. Extend OpenCode's `external_directory` permission when unattended access is intended. Prefer read-only mounts unless writing is required.
126
+
127
+ Keep PHP-FPM and OpenCode's PHP CLI on the same PHP version. For a path-prefixed reverse proxy, the outer proxy removes the prefix before forwarding and configures `NESTBOX_URL_PREFIX`. Set `NESTBOX_OPENCODE_PUBLIC_URL` explicitly whenever the public OpenCode scheme or host differs from direct Nestbox access.
128
+
129
+ ## 6. Keep The Public Page Tree Empty
130
+
131
+ The installation already contains Nestbox support files in `home/code`: `404.php`, protected includes and templates, browser sources, and generated asset baselines. Do not create `index.php` or another public project page.
132
+
133
+ Opening `/` must retain HTTP 404 and render the form that starts an OpenCode session for the first screen. Any route segment beginning with `__` remains private over HTTP and through the CLI router.
134
+
135
+ ## 7. Start And Verify
136
+
137
+ Run from the installation directory:
138
+
139
+ ```sh
140
+ docker compose config --quiet
141
+ docker compose up -d --build
142
+ docker compose ps
143
+ docker compose port nginx 80
144
+ ```
145
+
146
+ Verify:
147
+
148
+ - `/` retains HTTP 404 and renders the OpenCode implementation form;
149
+ - `/__templates/super-document` returns 404;
150
+ - `php /sources/php/cli.php __templates/super-document` is rejected;
151
+ - `/Agent` redirects to OpenCode on the same published port;
152
+ - the actual OpenCode URL works in the user's client;
153
+ - a provider and model are available for agent work, without printing credentials or sending a billable prompt unless the user approves it;
154
+ - `/_nestbox/health` returns HTTP 200;
155
+ - a transient PHP event reaches an EventSource subscriber through Nchan;
156
+ - every `__data` directory remains inaccessible over HTTP;
157
+ - no service repeatedly restarts.
158
+
159
+ Compare the resolved Compose bind sources with the running containers after startup. Recreate a service when its actual mounts do not match the current Compose model; do not claim a path change is active merely because `docker compose config` is valid.
160
+
161
+ Run the host test suite:
162
+
163
+ ```sh
164
+ bash tests/nestbox.sh
165
+ ```
166
+
167
+ ```powershell
168
+ .\tests\nestbox.ps1
169
+ ```
170
+
171
+ The scripts support both the fresh 404-first tree and later installations with `index.php`. `--start` for Bash and `-Start` for PowerShell may build and start application services first. Lifecycle mode never runs `down`, removes volumes, or starts an absent Rollup watcher that could rewrite bundles.
172
+
173
+ Some clients resolve `.localhost` internally even when an operating-system DNS query does not. Test the actual OpenCode URL. If it fails to resolve, ask before adding this hosts-file entry:
174
+
175
+ ```text
176
+ 127.0.0.1 agent.localhost
177
+ ```
178
+
179
+ For another device, configure explicit names for both Nestbox and OpenCode instead of relying on `agent.localhost`.
180
+
181
+ ## 8. Integrate The Host Agent
182
+
183
+ Before handoff, register this Nestbox location with the external installation agent so future host-maintenance requests resolve the correct installation through the agent's own integration mechanism.
184
+
185
+ Use the best integration mechanism supported by that agent, in this order:
186
+
187
+ 1. a project-local or user-local agent skill;
188
+ 2. an agent-native project command, workspace integration, plugin, or configuration entry;
189
+ 3. a small start/status/maintenance script that invokes the agent with the required context.
190
+
191
+ The integration must record or supply:
192
+
193
+ - the absolute installation, workspace, and Git-root paths;
194
+ - the Nestbox and OpenCode URLs;
195
+ - the selected communication language;
196
+ - the path to this `INSTALL.md` and `home/configs/opencode/instructions.md`;
197
+ - that page, feature, translation, package, service, and integration work belongs to the internal OpenCode agent;
198
+ - that the external agent handles installation, repair, operating-system integration, and host-side Compose validation and activation;
199
+ - the recorded commit behavior and repository boundary;
200
+ - the normal host test command.
201
+
202
+ Do not put `.env` contents, credentials, provider keys, or passwords into the integration. Prefer project-local scope. Ask before modifying global agent configuration, user-wide skills, shell profiles, or operating-system paths.
203
+
204
+ Verify that the resulting skill, command, or script can identify this exact installation and state its maintenance boundary. Report what was created and where. If the current agent cannot persist an integration, provide the user with the exact reusable prompt or command needed to restore this context later.
205
+
206
+ ## 9. Handoff And Extension Request
207
+
208
+ Report:
209
+
210
+ - Nestbox and OpenCode URLs without credentials;
211
+ - installation, workspace, and Git-root paths;
212
+ - selected communication and interface language;
213
+ - commit behavior;
214
+ - remaining uncommitted changes.
215
+
216
+ Ask the user to open OpenCode. Then ask what they want to change or extend in Nestbox. Typical requests include:
217
+
218
+ - creating the first screen or another super-document;
219
+ - translating the interface or adding i18n;
220
+ - integrating a Python program, database, worker, native tool, or host service;
221
+ - installing browser, PHP, system, or agent-toolchain packages;
222
+ - adding mounts, networks, proxy routes, or persistent volumes.
223
+
224
+ Send this work to the internal OpenCode agent. A useful prompt is:
225
+
226
+ ```text
227
+ Extend this Nestbox installation for the following requirement: <request>. Inspect /workspace and /nestbox, follow the internal Nestbox manual, implement and verify all file changes you can, and report the exact host-side activation command for any container or infrastructure change.
228
+ ```
229
+
230
+ The internal agent edits pages and infrastructure definitions. It has no Docker socket and must not claim host activation. The external maintainer resumes only to inspect the proposed infrastructure files, run `docker compose config`, apply the narrowest build or recreate operation, and verify health.
231
+
232
+ ## 10. Git And Generated State
233
+
234
+ The fresh Git root is the installation's `.git` in current-directory mode or `.nestbox/.git` in `.nestbox` mode. It owns reasonable Nestbox source changes, including:
235
+
236
+ - super-documents and selected durable `__data` files explicitly opted into Git history;
237
+ - reusable helpers and browser source;
238
+ - generated browser bundle baselines, which keep the initial interface available before the watcher starts;
239
+ - Compose files and Dockerfiles;
240
+ - Nginx, PHP, Rollup, and OpenCode configuration;
241
+ - portable integration definitions and dependency lockfiles;
242
+ - documentation and translation resources.
243
+
244
+ Follow the commit behavior recorded in `instance.md`:
245
+
246
+ - automatic: create a focused commit after verification;
247
+ - confirmation: show the exact paths and ask before each commit;
248
+ - no-commit: leave changes uncommitted.
249
+
250
+ After basic verification, create or offer one focused baseline commit containing the installed source and non-secret instance configuration. A concise default message is `Initialize Nestbox`. If the selected policy requires confirmation or forbids commits, leave the baseline pending and report that clearly.
251
+
252
+ Stage exact intended files only. Preserve unrelated changes. Never commit `.env`, provider credentials, caches, incidental state, or unrelated project files. Never amend, change Git configuration, discard changes, or push without explicit approval.
253
+
254
+ When a super-document intentionally changes files in `/workspace`, follow that workspace repository's own policy separately.
255
+
256
+ ## 11. Optional Adaptations
257
+
258
+ Retaining upstream Git history, adding an upstream remote, external page trees, parent-owned engines, submodules, vendoring, shared repositories, alternate Compose layouts, and operating-system shortcuts are optional. Explain their ownership and update consequences and obtain explicit confirmation before creating them. They must not obscure the two default installation paths.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Nestbox contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,41 @@
1
+ # Nestbox
2
+
3
+ ## Introduction
4
+
5
+ Nestbox is an agent-guided workspace for building focused interfaces around projects, tools, data, and containerized services.
6
+
7
+ Its core design is the **super-document**: one cohesive PHP and Markdown document can provide server logic, command-line behavior, forms, HTML, and browser JavaScript. The internal OpenCode agent creates and extends these documents, integrates project runtimes, and prepares infrastructure changes.
8
+
9
+ Nestbox runs with Docker Compose using Nginx, PHP-FPM, Rollup, and OpenCode. A fresh installation opens with a prompt for creating its first screen instead of shipping a predefined application.
10
+
11
+ ## Learn More
12
+
13
+ Copy and send this to an agent with web access:
14
+
15
+ ```text
16
+ Learn about Nestbox from https://github.com/piontkovsk11andre1/nestbox. Read its current documentation and explain its super-document design, agent workflow, container integration model, best use cases, and limitations. Do not install or modify anything.
17
+ ```
18
+
19
+ ## Installation
20
+
21
+ Create a new Nestbox project with npm:
22
+
23
+ ```sh
24
+ npm create @p10i/nestbox@latest
25
+ ```
26
+
27
+ The creator offers the two default layouts: install directly into the selected directory or install at `<workspace>/.nestbox`. After creation, edit `.env`, then run the Docker Compose validation and startup commands printed by the creator.
28
+
29
+ For agent-guided installation or repair work, copy and send this to an agent with filesystem, Git, Docker, and terminal access:
30
+
31
+ ```text
32
+ Install Nestbox by following https://github.com/piontkovsk11andre1/nestbox/blob/main/INSTALL.md completely. Ask all required questions before making changes, preserve unrelated files and Git history, and do not expose secrets.
33
+ ```
34
+
35
+ ## Update
36
+
37
+ Run an update request from the workspace containing Nestbox, then copy and send this prompt to an agent with filesystem, Git, Docker, and terminal access:
38
+
39
+ ```text
40
+ Update this Nestbox installation from https://github.com/piontkovsk11andre1/nestbox. Locate the installation, inspect its local Git history and configuration, read the current remote INSTALL.md and internal Nestbox instructions, preserve pages, instance policy, secrets, volumes, and unrelated changes, explain the proposed update, and ask before destructive or system-level operations. Validate Compose and run the Nestbox host tests after applying the update.
41
+ ```
@@ -0,0 +1,184 @@
1
+ #!/usr/bin/env node
2
+ import { spawnSync } from 'node:child_process';
3
+ import { createInterface } from 'node:readline/promises';
4
+ import { stdin as input, stdout as output } from 'node:process';
5
+ import { dirname, join, resolve } from 'node:path';
6
+ import { fileURLToPath } from 'node:url';
7
+ import { cp, mkdir, readdir, readFile, stat, writeFile } from 'node:fs/promises';
8
+ import { constants } from 'node:fs';
9
+
10
+ const packageRoot = resolve(dirname(fileURLToPath(import.meta.url)), '..');
11
+ const packageJson = JSON.parse(await readFile(join(packageRoot, 'package.json'), 'utf8'));
12
+
13
+ const copyEntries = [
14
+ 'docker',
15
+ 'home',
16
+ 'tests',
17
+ 'docker-compose.yaml',
18
+ '.dockerignore',
19
+ '.env.example',
20
+ '.gitattributes',
21
+ 'INSTALL.md',
22
+ 'README.md',
23
+ 'LICENSE'
24
+ ];
25
+
26
+ function parseArgs(argv) {
27
+ const options = {
28
+ target: '',
29
+ layout: '',
30
+ yes: false,
31
+ git: true,
32
+ help: false
33
+ };
34
+
35
+ for (let index = 0; index < argv.length; index++) {
36
+ const arg = argv[index];
37
+ if (arg === '--help' || arg === '-h') options.help = true;
38
+ else if (arg === '--yes' || arg === '-y') options.yes = true;
39
+ else if (arg === '--no-git') options.git = false;
40
+ else if (arg === '--layout') options.layout = argv[++index] || '';
41
+ else if (arg.startsWith('--layout=')) options.layout = arg.slice('--layout='.length);
42
+ else if (arg === '--target') options.target = argv[++index] || '';
43
+ else if (arg.startsWith('--target=')) options.target = arg.slice('--target='.length);
44
+ else if (!arg.startsWith('-') && !options.target) options.target = arg;
45
+ else throw new Error(`Unknown argument: ${arg}`);
46
+ }
47
+
48
+ return options;
49
+ }
50
+
51
+ function printHelp() {
52
+ console.log(`Create a Nestbox project.\n\nUsage:\n npm create @p10i/nestbox@latest [target] [options]\n npx @p10i/create-nestbox [target] [options]\n\nOptions:\n --layout current|nestbox Install directly into target or into target/.nestbox\n --target <path> Target directory\n --yes, -y Use defaults for missing prompts\n --no-git Do not initialize fresh installation Git history\n --help, -h Show this help\n`);
53
+ }
54
+
55
+ async function pathExists(path) {
56
+ try {
57
+ await stat(path);
58
+ return true;
59
+ } catch (error) {
60
+ if (error && error.code === 'ENOENT') return false;
61
+ throw error;
62
+ }
63
+ }
64
+
65
+ async function isDirectoryEmpty(path) {
66
+ if (!(await pathExists(path))) return true;
67
+ const entries = await readdir(path);
68
+ return entries.length === 0;
69
+ }
70
+
71
+ function validateLayout(layout) {
72
+ if (layout !== 'current' && layout !== 'nestbox') {
73
+ throw new Error('Layout must be "current" or "nestbox".');
74
+ }
75
+ }
76
+
77
+ async function ask(question, defaultValue, rl, yes) {
78
+ if (yes) return defaultValue;
79
+ const suffix = defaultValue ? ` (${defaultValue})` : '';
80
+ const answer = (await rl.question(`${question}${suffix}: `)).trim();
81
+ return answer || defaultValue;
82
+ }
83
+
84
+ async function copyTemplate(installDir) {
85
+ for (const entry of copyEntries) {
86
+ await cp(join(packageRoot, entry), join(installDir, entry), {
87
+ recursive: true,
88
+ errorOnExist: true,
89
+ force: false,
90
+ mode: constants.COPYFILE_FICLONE
91
+ });
92
+ }
93
+
94
+ await cp(join(packageRoot, 'template.gitignore'), join(installDir, '.gitignore'), {
95
+ errorOnExist: true,
96
+ force: false
97
+ });
98
+ }
99
+
100
+ async function configureInstance(installDir, workspaceDir, layout) {
101
+ await cp(join(installDir, '.env.example'), join(installDir, '.env'), {
102
+ errorOnExist: true,
103
+ force: false
104
+ });
105
+
106
+ const instanceExample = join(installDir, 'home', 'configs', 'opencode', 'instance.example.md');
107
+ const instancePath = join(installDir, 'home', 'configs', 'opencode', 'instance.md');
108
+ let instance = await readFile(instanceExample, 'utf8');
109
+
110
+ const workspaceDescription = layout === 'nestbox'
111
+ ? 'the parent workspace directory'
112
+ : 'the installation directory';
113
+ const layoutDescription = layout === 'nestbox'
114
+ ? '.nestbox'
115
+ : 'current directory';
116
+
117
+ instance = instance
118
+ .replace('- Layout: current directory.', `- Layout: ${layoutDescription}.`)
119
+ .replace('- Workspace: `/workspace` maps to the installation directory.', `- Workspace: \`/workspace\` maps to ${workspaceDescription}.`)
120
+ .replace('- Upstream source revision: record during installation.', `- Upstream source revision: npm package ${packageJson.name}@${packageJson.version}.`)
121
+ .replace('- Commit policy: unconfigured; ask before any commit until the installer replaces this line with the user\'s automatic, per-commit confirmation, or no-commit choice.', '- Commit policy: per-commit confirmation; ask before each commit.')
122
+ .trimEnd();
123
+
124
+ instance += `\n\n- Host installation path: ${installDir}\n- Host workspace path: ${workspaceDir}\n`;
125
+
126
+ await writeFile(instancePath, instance, 'utf8');
127
+ }
128
+
129
+ function commandExists(command) {
130
+ const result = spawnSync(command, ['--version'], { stdio: 'ignore', shell: process.platform === 'win32' });
131
+ return result.status === 0;
132
+ }
133
+
134
+ function initGit(installDir) {
135
+ if (!commandExists('git')) return false;
136
+ const result = spawnSync('git', ['init'], { cwd: installDir, stdio: 'inherit', shell: process.platform === 'win32' });
137
+ if (result.status !== 0) throw new Error('git init failed.');
138
+ return true;
139
+ }
140
+
141
+ async function main() {
142
+ const options = parseArgs(process.argv.slice(2));
143
+ if (options.help) {
144
+ printHelp();
145
+ return;
146
+ }
147
+
148
+ const rl = createInterface({ input, output });
149
+ try {
150
+ const targetInput = options.target || await ask('Target directory', '.', rl, options.yes);
151
+ const layoutInput = options.layout || await ask('Layout: current or nestbox', 'nestbox', rl, options.yes);
152
+ validateLayout(layoutInput);
153
+
154
+ const workspaceDir = resolve(process.cwd(), targetInput);
155
+ const installDir = layoutInput === 'nestbox' ? join(workspaceDir, '.nestbox') : workspaceDir;
156
+
157
+ if (!(await isDirectoryEmpty(installDir))) {
158
+ throw new Error(`Refusing to overwrite non-empty installation target: ${installDir}`);
159
+ }
160
+
161
+ await mkdir(installDir, { recursive: true });
162
+ await copyTemplate(installDir);
163
+ await configureInstance(installDir, workspaceDir, layoutInput);
164
+ const gitInitialized = options.git ? initGit(installDir) : false;
165
+
166
+ console.log('\nNestbox project created.');
167
+ console.log(`Installation: ${installDir}`);
168
+ console.log(`Workspace: ${workspaceDir}`);
169
+ console.log(`Git initialized: ${gitInitialized ? 'yes' : 'no'}`);
170
+ console.log('\nNext steps:');
171
+ console.log(` cd ${installDir}`);
172
+ console.log(' Edit .env and set COMPOSE_PROJECT_NAME, WEB_PORT, OpenCode credentials, and provider keys.');
173
+ console.log(' docker compose config --quiet');
174
+ console.log(' docker compose up -d --build');
175
+ console.log(process.platform === 'win32' ? ' .\\tests\\nestbox.ps1' : ' bash tests/nestbox.sh');
176
+ } finally {
177
+ rl.close();
178
+ }
179
+ }
180
+
181
+ main().catch((error) => {
182
+ console.error(`create-nestbox: ${error.message}`);
183
+ process.exit(1);
184
+ });
@@ -0,0 +1,31 @@
1
+ # syntax=docker/dockerfile:1.7
2
+
3
+ # ---- Stage 1: build the JS/CSS bundles with Rollup ----
4
+ FROM node:alpine AS bundles
5
+
6
+ WORKDIR /sources/rollup
7
+ COPY ./home/configs/rollup/package.json ./package.json
8
+ COPY ./home/configs/rollup/package-lock.json ./package-lock.json
9
+ RUN npm ci --no-audit --no-fund
10
+
11
+ COPY ./home/configs/rollup/rollup.config.js ./rollup.config.js
12
+ COPY ./home/code /home/code
13
+ RUN npx rollup -c
14
+
15
+ # ---- Stage 2: runtime ----
16
+ FROM alpine:3.23
17
+
18
+ RUN apk add --no-cache nginx nginx-mod-http-nchan && \
19
+ mkdir -p /run/nginx /var/lib/nginx/tmp
20
+
21
+ COPY ./home/configs/nginx/nginx.conf /etc/nginx/nginx.conf
22
+ COPY ./home/code /var/www/html
23
+ # Overwrite the source bundles with the freshly built ones from stage 1.
24
+ COPY --from=bundles /home/code/scripts-bundle.js /var/www/html/scripts-bundle.js
25
+ COPY --from=bundles /home/code/scripts-bundle.css /var/www/html/scripts-bundle.css
26
+
27
+ EXPOSE 80 8080
28
+
29
+ STOPSIGNAL SIGQUIT
30
+
31
+ CMD ["nginx", "-g", "daemon off;"]
@@ -0,0 +1,36 @@
1
+ FROM composer:latest AS vendor
2
+
3
+ WORKDIR /build
4
+ COPY ./home/configs/php/composer.json ./composer.json
5
+ COPY ./home/configs/php/composer.lock ./composer.lock
6
+ RUN composer install --no-interaction --prefer-dist --optimize-autoloader --no-dev
7
+
8
+ FROM ghcr.io/anomalyco/opencode:latest
9
+
10
+ ARG APP_UID=1000
11
+ ARG APP_GID=1000
12
+
13
+ RUN apk add --no-cache git php85 php85-dom php85-mbstring php85-openssl php85-phar php85-tokenizer php85-xml php85-xmlwriter su-exec && \
14
+ ln -s /usr/bin/php85 /usr/local/bin/php && \
15
+ addgroup -g "${APP_GID}" -S nestbox && \
16
+ adduser -u "${APP_UID}" -G nestbox -S -h /home/nestbox nestbox
17
+
18
+ ENV OPENCODE_CONFIG=/etc/opencode/opencode.json
19
+ ENV HOME=/home/nestbox
20
+
21
+ COPY --from=vendor /build/vendor /vendor
22
+ COPY ./home/configs/php /sources/php
23
+ COPY ./home/configs/opencode /etc/opencode
24
+ COPY ./home/code /home/code
25
+ COPY ./docker/opencode/entrypoint.sh /usr/local/bin/nestbox-opencode-entrypoint
26
+
27
+ RUN chmod 0755 /usr/local/bin/nestbox-opencode-entrypoint && \
28
+ mkdir -p /home/nestbox/.local/share/opencode && \
29
+ chown -R nestbox:nestbox /home/nestbox /home/code
30
+
31
+ WORKDIR /workspace
32
+
33
+ EXPOSE 4096
34
+
35
+ ENTRYPOINT ["/usr/local/bin/nestbox-opencode-entrypoint"]
36
+ CMD ["serve", "--hostname", "0.0.0.0", "--port", "4096"]
@@ -0,0 +1,9 @@
1
+ #!/bin/sh
2
+ set -eu
3
+
4
+ # Named volumes retain ownership across image recreation. Repair only OpenCode's
5
+ # private state volume before dropping privileges; never chown host bind mounts.
6
+ mkdir -p "$HOME/.local/share/opencode"
7
+ chown -R nestbox:nestbox "$HOME/.local/share/opencode"
8
+
9
+ exec su-exec nestbox:nestbox opencode "$@"