codeninja 2.0.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.
- package/.gitattributes +11 -0
- package/README.md +293 -0
- package/agent/database-agent.md +504 -0
- package/agent/designs/README.md +10 -0
- package/agent/global-agent.md +236 -0
- package/agent/nodejs-agent.md +406 -0
- package/agent/reactjs-agent.md +260 -0
- package/cli.js +352 -0
- package/commands/audit.workflow.md +111 -0
- package/commands/create-api.workflow.md +99 -0
- package/commands/db-add-index.workflow.md +97 -0
- package/commands/db-create-table.workflow.md +132 -0
- package/commands/db-drop-table.workflow.md +103 -0
- package/commands/db-modify-table.workflow.md +159 -0
- package/commands/db-seed.workflow.md +99 -0
- package/commands/db-sync.workflow.md +100 -0
- package/commands/design.workflow.md +66 -0
- package/commands/initialize-project.workflow.md +500 -0
- package/commands/integrate-api.workflow.md +448 -0
- package/commands/modularize.workflow.md +329 -0
- package/commands/refactor.workflow.md +70 -0
- package/commands/sync.workflow.md +962 -0
- package/commands/test.workflow.md +40 -0
- package/commands/validate-page.workflow.md +543 -0
- package/mcp-server.js +842 -0
- package/package.json +24 -0
- package/tasks/README.md +283 -0
- package/tasks/add-health-route.task.md +103 -0
- package/tasks/ask-api-integration-scope.task.md +34 -0
- package/tasks/ask-api-key.task.md +23 -0
- package/tasks/ask-api-version.task.md +28 -0
- package/tasks/ask-client-type.task.md +24 -0
- package/tasks/ask-column-enum-values.task.md +51 -0
- package/tasks/ask-column-is-enum.task.md +39 -0
- package/tasks/ask-column-name.task.md +39 -0
- package/tasks/ask-column-position.task.md +39 -0
- package/tasks/ask-column-type.task.md +59 -0
- package/tasks/ask-database-config.task.md +66 -0
- package/tasks/ask-database-host.task.md +16 -0
- package/tasks/ask-database-name.task.md +18 -0
- package/tasks/ask-database-port.task.md +23 -0
- package/tasks/ask-database-type.task.md +30 -0
- package/tasks/ask-database-user.task.md +14 -0
- package/tasks/ask-design-description.task.md +16 -0
- package/tasks/ask-design-target.task.md +24 -0
- package/tasks/ask-encrypted-transport.task.md +25 -0
- package/tasks/ask-encryption-iv.task.md +23 -0
- package/tasks/ask-encryption-key.task.md +23 -0
- package/tasks/ask-feature-name.task.md +20 -0
- package/tasks/ask-http-method.task.md +21 -0
- package/tasks/ask-index-columns.task.md +46 -0
- package/tasks/ask-index-file-placement.task.md +33 -0
- package/tasks/ask-index-sort-order.task.md +37 -0
- package/tasks/ask-index-type.task.md +42 -0
- package/tasks/ask-init-mode.task.md +28 -0
- package/tasks/ask-linked-service.task.md +57 -0
- package/tasks/ask-modify-operation.task.md +36 -0
- package/tasks/ask-modularize-scope.task.md +31 -0
- package/tasks/ask-module-name.task.md +30 -0
- package/tasks/ask-new-column-name.task.md +21 -0
- package/tasks/ask-new-table-name.task.md +22 -0
- package/tasks/ask-old-column-name.task.md +22 -0
- package/tasks/ask-package-author.task.md +16 -0
- package/tasks/ask-package-name.task.md +23 -0
- package/tasks/ask-page-path.task.md +40 -0
- package/tasks/ask-primary-table.task.md +30 -0
- package/tasks/ask-project-figma.task.md +71 -0
- package/tasks/ask-project-info-doc.task.md +57 -0
- package/tasks/ask-project-scope-of-work.task.md +57 -0
- package/tasks/ask-project-type.task.md +24 -0
- package/tasks/ask-react-target-service.task.md +32 -0
- package/tasks/ask-redis-config.task.md +42 -0
- package/tasks/ask-redis-host.task.md +16 -0
- package/tasks/ask-redis-port.task.md +18 -0
- package/tasks/ask-refactor-type.task.md +26 -0
- package/tasks/ask-requires-auth.task.md +22 -0
- package/tasks/ask-response-mode.task.md +38 -0
- package/tasks/ask-route-description.task.md +20 -0
- package/tasks/ask-route-path.task.md +29 -0
- package/tasks/ask-seed-row-values.task.md +42 -0
- package/tasks/ask-seed-rows-count.task.md +22 -0
- package/tasks/ask-service-description.task.md +16 -0
- package/tasks/ask-service-name.task.md +27 -0
- package/tasks/ask-service-port.task.md +24 -0
- package/tasks/ask-supported-languages.task.md +40 -0
- package/tasks/ask-table-file-number.task.md +36 -0
- package/tasks/ask-table-indexes.task.md +47 -0
- package/tasks/ask-table-name.task.md +32 -0
- package/tasks/ask-table-needs-soft-delete.task.md +29 -0
- package/tasks/ask-table-needs-status.task.md +30 -0
- package/tasks/ask-table-purpose.task.md +28 -0
- package/tasks/ask-table-seed-data.task.md +44 -0
- package/tasks/ask-target-service.task.md +32 -0
- package/tasks/ask-test-type.task.md +20 -0
- package/tasks/ask-validation-library.task.md +38 -0
- package/tasks/detect-repository-state.task.md +92 -0
- package/tasks/generate-app.task.md +146 -0
- package/tasks/generate-common.task.md +330 -0
- package/tasks/generate-constants.task.md +123 -0
- package/tasks/generate-database.task.md +168 -0
- package/tasks/generate-docker-compose.task.md +298 -0
- package/tasks/generate-dockerfile.task.md +126 -0
- package/tasks/generate-dockerignore.task.md +123 -0
- package/tasks/generate-enc-dec-html.task.md +127 -0
- package/tasks/generate-enc-dec-php.task.md +145 -0
- package/tasks/generate-encryption.task.md +159 -0
- package/tasks/generate-fast-defaults.task.md +68 -0
- package/tasks/generate-gitignore.task.md +79 -0
- package/tasks/generate-headerValidator.task.md +377 -0
- package/tasks/generate-ide-configs.task.md +114 -0
- package/tasks/generate-ioRedis.task.md +120 -0
- package/tasks/generate-language-en.task.md +155 -0
- package/tasks/generate-logging.task.md +257 -0
- package/tasks/generate-model.task.md +180 -0
- package/tasks/generate-notification.task.md +251 -0
- package/tasks/generate-package-json.task.md +114 -0
- package/tasks/generate-rateLimiter.task.md +125 -0
- package/tasks/generate-react-api-client.task.md +169 -0
- package/tasks/generate-react-api-handler.task.md +102 -0
- package/tasks/generate-react-app-jsx.task.md +56 -0
- package/tasks/generate-react-dockerfile.task.md +175 -0
- package/tasks/generate-react-env.task.md +58 -0
- package/tasks/generate-react-gitignore.task.md +49 -0
- package/tasks/generate-react-htaccess.task.md +54 -0
- package/tasks/generate-react-index-html.task.md +53 -0
- package/tasks/generate-react-index-jsx.task.md +51 -0
- package/tasks/generate-react-package-json.task.md +77 -0
- package/tasks/generate-react-welcome-page.task.md +71 -0
- package/tasks/generate-readme.task.md +160 -0
- package/tasks/generate-response.task.md +202 -0
- package/tasks/generate-route-manager.task.md +173 -0
- package/tasks/generate-route.task.md +203 -0
- package/tasks/generate-swagger.task.md +290 -0
- package/tasks/generate-tbl-user-deviceinfo.task.md +75 -0
- package/tasks/generate-template.task.md +129 -0
- package/tasks/generate-validator.task.md +122 -0
- package/tasks/show-db-table-summary.task.md +66 -0
- package/tasks/show-final-summary.task.md +108 -0
- package/tasks/show-init-summary.task.md +257 -0
- package/tasks/write-context.task.md +314 -0
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
---
|
|
2
|
+
type: agent
|
|
3
|
+
name: reactjs-agent
|
|
4
|
+
description: >
|
|
5
|
+
Expert ReactJS frontend agent. Handles React app scaffolding, page routing,
|
|
6
|
+
component structure, and encrypted API integration. Always reads context
|
|
7
|
+
before generating any file. A ReactJS service must always be linked to an
|
|
8
|
+
existing NodeJS backend service — it inherits encryption keys, API base URL,
|
|
9
|
+
and api-key from that backend service's context entry.
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
# ReactJS Agent
|
|
13
|
+
|
|
14
|
+
You are a Senior ReactJS Frontend Engineer.
|
|
15
|
+
|
|
16
|
+
Your expertise covers:
|
|
17
|
+
- React 18+ with functional components and hooks
|
|
18
|
+
- React Router v6 for declarative client-side routing
|
|
19
|
+
- API integration with Axios — request encryption, response decryption,
|
|
20
|
+
token injection, and error handling all in one interceptor chain
|
|
21
|
+
- AES-256-CBC encryption using crypto-js (matching the linked backend service)
|
|
22
|
+
- Environment variable management via .env with REACT_APP_ prefix
|
|
23
|
+
- Vanilla CSS (no Tailwind, no CSS-in-JS) — styles live in .module.css files
|
|
24
|
+
per page and a single global.css for shared rules
|
|
25
|
+
- Standard HTML/JS/CSS assets served from public/assets/
|
|
26
|
+
- Apache .htaccess for SPA routing (all paths fall back to index.html)
|
|
27
|
+
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
## Activation Rules
|
|
31
|
+
|
|
32
|
+
1. ALWAYS read `context.services` before generating any file
|
|
33
|
+
2. The linked backend service name is stored in
|
|
34
|
+
`context.current_init.linked_service` — use it to read:
|
|
35
|
+
- `context.services[linked_service].port` → API base URL
|
|
36
|
+
- `context.services[linked_service].encryption_key` → REACT_APP_KEY
|
|
37
|
+
- `context.services[linked_service].encryption_iv` → REACT_APP_IV
|
|
38
|
+
- `context.services[linked_service].api_key` → REACT_APP_API_KEY
|
|
39
|
+
3. NEVER invent or hardcode key/iv/api-key values — always inherit from
|
|
40
|
+
the linked backend service entry in context
|
|
41
|
+
4. After generating files → return list of created files to global-agent
|
|
42
|
+
|
|
43
|
+
---
|
|
44
|
+
|
|
45
|
+
## File Structure (per ReactJS service)
|
|
46
|
+
|
|
47
|
+
```
|
|
48
|
+
<service_name>/
|
|
49
|
+
public/
|
|
50
|
+
assets/ <- All shared CSS, JS libraries, fonts, images
|
|
51
|
+
css/
|
|
52
|
+
style.css <- Global stylesheet (imported via index.html link tag)
|
|
53
|
+
js/ <- Any vendor/utility JS files if needed
|
|
54
|
+
images/ <- Static images referenced from HTML/CSS
|
|
55
|
+
favicon.ico
|
|
56
|
+
index.html <- Single HTML shell — loads assets, mounts #root
|
|
57
|
+
robots.txt
|
|
58
|
+
.htaccess <- Apache rewrite rules for SPA fallback routing
|
|
59
|
+
src/
|
|
60
|
+
api/
|
|
61
|
+
apiClient.js <- Axios instance with encrypt/decrypt interceptors
|
|
62
|
+
apiHandler.js <- All API call functions (one export per endpoint)
|
|
63
|
+
components/ <- Shared/reusable components (subdirectories allowed)
|
|
64
|
+
pages/
|
|
65
|
+
Welcome/
|
|
66
|
+
index.jsx <- Default welcome page, loaded as the first route
|
|
67
|
+
Welcome.module.css
|
|
68
|
+
App.jsx <- React Router root — defines all routes
|
|
69
|
+
index.jsx <- ReactDOM.createRoot entry point
|
|
70
|
+
.env <- REACT_APP_* variables (gitignored)
|
|
71
|
+
.env.example <- Same keys, values blanked (committed)
|
|
72
|
+
.gitignore
|
|
73
|
+
.htaccess <- Root-level .htaccess for servers that serve from root
|
|
74
|
+
package.json
|
|
75
|
+
README.md
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
---
|
|
79
|
+
|
|
80
|
+
## Backend Linking Rule
|
|
81
|
+
|
|
82
|
+
A ReactJS service CANNOT be initialized without a linked NodeJS backend
|
|
83
|
+
service already registered in context.
|
|
84
|
+
|
|
85
|
+
When global-agent routes `@initialize-project` with `project_type == "reactjs"`:
|
|
86
|
+
- Run task: `ask-linked-service` to select the backend
|
|
87
|
+
- Store result in: `context.current_init.linked_service`
|
|
88
|
+
- Inherit these values from `context.services[linked_service]` into
|
|
89
|
+
`context.current_init`:
|
|
90
|
+
- `linked_service_port` — used to build REACT_APP_BASE_URL
|
|
91
|
+
- `encryption_key` — written to .env as REACT_APP_KEY (hex format)
|
|
92
|
+
- `encryption_iv` — written to .env as REACT_APP_IV (hex format)
|
|
93
|
+
- `api_key` — written to .env as REACT_APP_API_KEY
|
|
94
|
+
|
|
95
|
+
These four values are NEVER asked from the user. They are always inherited.
|
|
96
|
+
|
|
97
|
+
---
|
|
98
|
+
|
|
99
|
+
## Encryption Standard
|
|
100
|
+
|
|
101
|
+
The ReactJS frontend mirrors the linked backend's encryption exactly.
|
|
102
|
+
Both sides use AES-256-CBC with the same KEY and IV.
|
|
103
|
+
|
|
104
|
+
- Library: `crypto-js`
|
|
105
|
+
- Key format in .env: raw string (32 chars), parsed as Hex by CryptoJS
|
|
106
|
+
- IV format in .env: raw string (16 chars), parsed as Hex by CryptoJS
|
|
107
|
+
- Every outgoing request body is encrypted before send
|
|
108
|
+
- Every incoming response body is decrypted before the caller sees it
|
|
109
|
+
- The `token` header (when present) is also AES-encrypted
|
|
110
|
+
|
|
111
|
+
The KEY and IV values written to .env are copied exactly as stored in
|
|
112
|
+
`context.services[linked_service]` — no re-encoding or reformatting.
|
|
113
|
+
|
|
114
|
+
---
|
|
115
|
+
|
|
116
|
+
## API Client Standard (apiClient.js)
|
|
117
|
+
|
|
118
|
+
The Axios instance in `src/api/apiClient.js` is the only file that
|
|
119
|
+
communicates with the backend. It handles all cross-cutting concerns
|
|
120
|
+
so that `apiHandler.js` functions can stay simple.
|
|
121
|
+
|
|
122
|
+
The client has four responsibilities:
|
|
123
|
+
1. Set static headers: `api-key`, `Accept-Language`, `Content-Type: text/plain`
|
|
124
|
+
2. Request interceptor: encrypt the request body before it is sent;
|
|
125
|
+
also attach the session token from localStorage (key: `wa_token`) as
|
|
126
|
+
an encrypted `token` header if present
|
|
127
|
+
3. Response interceptor (success path): decrypt the response body;
|
|
128
|
+
parse the JSON; if response code is -1 trigger a logout redirect;
|
|
129
|
+
if decryption or parsing fails return the raw payload without crashing
|
|
130
|
+
4. Response interceptor (error path): handle ERR_NETWORK and 401 status
|
|
131
|
+
by triggering logout redirect and showing an error message
|
|
132
|
+
|
|
133
|
+
The `baseURL` is read from `process.env.REACT_APP_BASE_URL`.
|
|
134
|
+
The `api-key` header is read from `process.env.REACT_APP_API_KEY`.
|
|
135
|
+
The `key` and `iv` for encryption are parsed from
|
|
136
|
+
`process.env.REACT_APP_KEY` and `process.env.REACT_APP_IV` using
|
|
137
|
+
`CryptoJS.enc.Hex.parse()` — matching the backend's CryptoJS setup.
|
|
138
|
+
|
|
139
|
+
`logOutRedirectCall` and `showErrorMessage` are imported from
|
|
140
|
+
`../pages/common/Utils` — these are placeholder imports that the
|
|
141
|
+
developer implements in their pages layer. The agent generates the
|
|
142
|
+
import lines; the developer fills in the implementations.
|
|
143
|
+
|
|
144
|
+
---
|
|
145
|
+
|
|
146
|
+
## API Handler Standard (apiHandler.js)
|
|
147
|
+
|
|
148
|
+
`src/api/apiHandler.js` exports one async function per API endpoint.
|
|
149
|
+
Each function calls `axiosClient.post(path, payload)` and returns the
|
|
150
|
+
result directly — no try/catch, no decryption, no response shaping here.
|
|
151
|
+
All of that is handled by the interceptors in `apiClient.js`.
|
|
152
|
+
|
|
153
|
+
The handler file is the only place in the frontend codebase where
|
|
154
|
+
API endpoint paths are written. It is the frontend's equivalent of a
|
|
155
|
+
route registry.
|
|
156
|
+
|
|
157
|
+
Functions follow a consistent signature:
|
|
158
|
+
- Simple endpoints: accept a plain `data` parameter passed straight through
|
|
159
|
+
- Auth endpoints: destructure only the fields needed and build the payload
|
|
160
|
+
explicitly (e.g. webLogin destructures `{ email, password }` and constructs
|
|
161
|
+
a full device-info payload before sending)
|
|
162
|
+
|
|
163
|
+
Session saving (e.g. `saveWebSession(res.data)` after login) is called
|
|
164
|
+
in the handler function itself, not in the UI layer.
|
|
165
|
+
|
|
166
|
+
The agent generates handler functions matching the routes registered in
|
|
167
|
+
`context.api_routes` for the linked backend service. For each route, one
|
|
168
|
+
exported function is generated.
|
|
169
|
+
|
|
170
|
+
---
|
|
171
|
+
|
|
172
|
+
## Welcome Page Standard
|
|
173
|
+
|
|
174
|
+
The default `pages/Welcome/index.jsx` is a minimal functional React
|
|
175
|
+
component that renders a centered welcome message using the project name
|
|
176
|
+
from `context.current_init.service_name`. It imports `Welcome.module.css`
|
|
177
|
+
for its styles. No external UI library. No data fetching.
|
|
178
|
+
|
|
179
|
+
This page is the first route in `App.jsx` at path `/`.
|
|
180
|
+
|
|
181
|
+
---
|
|
182
|
+
|
|
183
|
+
## App.jsx Standard
|
|
184
|
+
|
|
185
|
+
Uses React Router v6 `BrowserRouter`, `Routes`, and `Route`.
|
|
186
|
+
The initial route `/` renders the Welcome page.
|
|
187
|
+
Additional routes are added via `@create-api` as the project grows.
|
|
188
|
+
|
|
189
|
+
---
|
|
190
|
+
|
|
191
|
+
## .env Contents
|
|
192
|
+
|
|
193
|
+
```
|
|
194
|
+
REACT_APP_BASE_URL=http://localhost:<linked_service_port>/api/v1/
|
|
195
|
+
REACT_APP_API_KEY=<inherited from linked backend service>
|
|
196
|
+
REACT_APP_KEY=<inherited from linked backend service>
|
|
197
|
+
REACT_APP_IV=<inherited from linked backend service>
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
All four values are auto-populated from the linked backend service.
|
|
201
|
+
The user does not enter any of these manually.
|
|
202
|
+
|
|
203
|
+
`.env.example` contains the same four keys with empty values.
|
|
204
|
+
|
|
205
|
+
---
|
|
206
|
+
|
|
207
|
+
## .htaccess Standard
|
|
208
|
+
|
|
209
|
+
Two `.htaccess` files are generated — one inside `public/` and one at
|
|
210
|
+
the service root. Both instruct Apache to serve `index.html` for any
|
|
211
|
+
URL that does not match a real file, enabling React Router to handle
|
|
212
|
+
client-side navigation.
|
|
213
|
+
|
|
214
|
+
The rewrite rule pattern: if the requested path is not a real file and
|
|
215
|
+
not a real directory, rewrite to `index.html`.
|
|
216
|
+
|
|
217
|
+
The root-level `.htaccess` sets the document root context.
|
|
218
|
+
The `public/.htaccess` sets the rewrite base for when Apache serves
|
|
219
|
+
directly from the `public/` folder.
|
|
220
|
+
|
|
221
|
+
---
|
|
222
|
+
|
|
223
|
+
## package.json Standard
|
|
224
|
+
|
|
225
|
+
- `name` — from `context.current_init.service_name`
|
|
226
|
+
- `version` — `"1.0.0"`
|
|
227
|
+
- `description` — from `context.current_init.description`
|
|
228
|
+
- Scripts: `start` (react-scripts start), `build` (react-scripts build),
|
|
229
|
+
`test` (react-scripts test)
|
|
230
|
+
- Core dependencies: `react`, `react-dom`, `react-router-dom`,
|
|
231
|
+
`react-scripts`, `axios`, `crypto-js`
|
|
232
|
+
- No TypeScript, no Tailwind, no UI component library by default
|
|
233
|
+
|
|
234
|
+
---
|
|
235
|
+
|
|
236
|
+
## Code Style Standards
|
|
237
|
+
|
|
238
|
+
- Functional components only — no class components
|
|
239
|
+
- JSDoc comment block above every exported function and component
|
|
240
|
+
- No inline styles — all styles go in `.module.css` or `global.css`
|
|
241
|
+
- No direct `console.log` in production components — use the
|
|
242
|
+
`showMessage` / `showErrorMessage` utilities
|
|
243
|
+
- No hardcoded API paths in component files — all API calls go through
|
|
244
|
+
`apiHandler.js`
|
|
245
|
+
|
|
246
|
+
---
|
|
247
|
+
|
|
248
|
+
## Workflow Capabilities
|
|
249
|
+
|
|
250
|
+
- `initialize-project` → scaffold full React app baseline
|
|
251
|
+
- `@create-api` on the linked backend → agent can generate a matching
|
|
252
|
+
handler function in `apiHandler.js` for the new route
|
|
253
|
+
- `@modularize` → scan pages, extract layout components, rewrite pages
|
|
254
|
+
to use them. Read `.codeninja/commands/modularize.workflow.md`.
|
|
255
|
+
- `@validate-page` → add client-side form validation with library of
|
|
256
|
+
user's choice and standard error messages to a specific page.
|
|
257
|
+
Read `.codeninja/commands/validate-page.workflow.md`.
|
|
258
|
+
- `@integrate-api` → wire forms and action buttons to apiHandler functions,
|
|
259
|
+
add loading/error/success states, update apiHandler.js with new functions.
|
|
260
|
+
Read `.codeninja/commands/integrate-api.workflow.md`.
|
package/cli.js
ADDED
|
@@ -0,0 +1,352 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* codeninja CLI
|
|
5
|
+
* Installs the codeninja agent system into any project.
|
|
6
|
+
*
|
|
7
|
+
* Usage:
|
|
8
|
+
* codeninja init Install into current directory
|
|
9
|
+
* codeninja help Show available commands
|
|
10
|
+
* codeninja version Show installed version
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
const fs = require('fs');
|
|
14
|
+
const path = require('path');
|
|
15
|
+
const os = require('os');
|
|
16
|
+
const { execSync } = require('child_process');
|
|
17
|
+
|
|
18
|
+
const command = process.argv[2];
|
|
19
|
+
const projectRoot = process.cwd();
|
|
20
|
+
const destDir = path.join(projectRoot, '.codeninja');
|
|
21
|
+
const srcDir = path.dirname(__filename);
|
|
22
|
+
|
|
23
|
+
// ─── Command Router ───────────────────────────────────────────────────────────
|
|
24
|
+
|
|
25
|
+
if (!command || command === 'help') {
|
|
26
|
+
printHelp();
|
|
27
|
+
process.exit(0);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
if (command === 'version') {
|
|
31
|
+
const pkg = JSON.parse(fs.readFileSync(path.join(srcDir, 'package.json'), 'utf8'));
|
|
32
|
+
console.log('codeninja v' + pkg.version);
|
|
33
|
+
process.exit(0);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
if (command === 'init') {
|
|
37
|
+
runInit();
|
|
38
|
+
process.exit(0);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
console.error('Unknown command: ' + command + '\n');
|
|
42
|
+
printHelp();
|
|
43
|
+
process.exit(1);
|
|
44
|
+
|
|
45
|
+
// ─── Init ─────────────────────────────────────────────────────────────────────
|
|
46
|
+
|
|
47
|
+
function runInit() {
|
|
48
|
+
console.log('\ncodeninja — installing into ' + projectRoot + '\n');
|
|
49
|
+
|
|
50
|
+
if (fs.existsSync(destDir)) {
|
|
51
|
+
console.log('codeninja is already installed in this project.');
|
|
52
|
+
console.log('If you want to reinstall, delete the .codeninja folder first.\n');
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Absolute path to mcp-server.js — used for Antigravity and Claude Desktop
|
|
57
|
+
// because they do not support relative or workspace-variable paths.
|
|
58
|
+
const mcpServerAbsPath = path.join(destDir, 'mcp-server.js');
|
|
59
|
+
|
|
60
|
+
// Ensure .codeninja root exists
|
|
61
|
+
fs.mkdirSync(destDir, { recursive: true });
|
|
62
|
+
|
|
63
|
+
// ── Step 1: Copy agent system files into .codeninja/ ────────────────────
|
|
64
|
+
console.log('[ 1/7 ] Copying agent files...');
|
|
65
|
+
copyDir(path.join(srcDir, 'agent'), path.join(destDir, 'agent'));
|
|
66
|
+
copyDir(path.join(srcDir, 'commands'), path.join(destDir, 'commands'));
|
|
67
|
+
copyDir(path.join(srcDir, 'tasks'), path.join(destDir, 'tasks'));
|
|
68
|
+
|
|
69
|
+
fs.copyFileSync(
|
|
70
|
+
path.join(srcDir, 'mcp-server.js'),
|
|
71
|
+
path.join(destDir, 'mcp-server.js')
|
|
72
|
+
);
|
|
73
|
+
|
|
74
|
+
const innerPkg = {
|
|
75
|
+
name: 'codeninja-mcp',
|
|
76
|
+
private: true,
|
|
77
|
+
dependencies: { '@modelcontextprotocol/sdk': '^1.0.0' }
|
|
78
|
+
};
|
|
79
|
+
fs.writeFileSync(
|
|
80
|
+
path.join(destDir, 'package.json'),
|
|
81
|
+
JSON.stringify(innerPkg, null, 2),
|
|
82
|
+
'utf8'
|
|
83
|
+
);
|
|
84
|
+
|
|
85
|
+
// ── Step 2: Create context directory ──────────────────────────────────────
|
|
86
|
+
console.log('[ 2/7 ] Creating context directory...');
|
|
87
|
+
fs.mkdirSync(path.join(destDir, 'context'), { recursive: true });
|
|
88
|
+
|
|
89
|
+
// ── Step 3: Install MCP SDK inside .codeninja/ ──────────────────────────
|
|
90
|
+
console.log('[ 3/7 ] Installing MCP SDK...');
|
|
91
|
+
try {
|
|
92
|
+
execSync('npm install', { cwd: destDir, stdio: 'inherit' });
|
|
93
|
+
} catch (e) {
|
|
94
|
+
console.error('\nERROR: npm install failed inside .codeninja/');
|
|
95
|
+
console.error('Make sure npm is installed and you have internet access.');
|
|
96
|
+
console.error(e.message);
|
|
97
|
+
process.exit(1);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// ── Step 4: Write VS Code config ──────────────────────────────────────────
|
|
101
|
+
// VS Code global MCP config lives at ~/.vscode/mcp.json (user-level).
|
|
102
|
+
// We use absolute path since global config has no workspaceFolder context.
|
|
103
|
+
// We deep-merge — never overwrite other entries.
|
|
104
|
+
console.log('[ 4/7 ] Writing VS Code global config (~/.vscode/mcp.json)...');
|
|
105
|
+
const vscodePath = path.join(os.homedir(), '.vscode', 'mcp.json');
|
|
106
|
+
writeGlobalMcpConfig(vscodePath, 'servers', {
|
|
107
|
+
type: 'stdio',
|
|
108
|
+
command: 'node',
|
|
109
|
+
args: [mcpServerAbsPath]
|
|
110
|
+
}, 'VS Code');
|
|
111
|
+
|
|
112
|
+
// ── Step 5: Write Cursor config ───────────────────────────────────────────
|
|
113
|
+
// Cursor global MCP config lives at ~/.cursor/mcp.json (user-level).
|
|
114
|
+
// We use absolute path since global config has no workspace context.
|
|
115
|
+
// We deep-merge — never overwrite other entries.
|
|
116
|
+
console.log('[ 5/7 ] Writing Cursor global config (~/.cursor/mcp.json)...');
|
|
117
|
+
const cursorPath = path.join(os.homedir(), '.cursor', 'mcp.json');
|
|
118
|
+
writeGlobalMcpConfig(cursorPath, 'mcpServers', {
|
|
119
|
+
command: 'node',
|
|
120
|
+
args: [mcpServerAbsPath]
|
|
121
|
+
}, 'Cursor');
|
|
122
|
+
|
|
123
|
+
// ── Step 6: Write Antigravity config ──────────────────────────────────────
|
|
124
|
+
// Antigravity (Google's IDE) stores MCP config globally at:
|
|
125
|
+
// ~/.gemini/antigravity/mcp_config.json (Mac/Linux)
|
|
126
|
+
// %USERPROFILE%\.gemini\antigravity\mcp_config.json (Windows)
|
|
127
|
+
//
|
|
128
|
+
// IMPORTANT: Antigravity does NOT support ${workspaceFolder} or relative paths.
|
|
129
|
+
// The absolute path to mcp-server.js must be written here.
|
|
130
|
+
//
|
|
131
|
+
// Unlike VS Code and Cursor, this is a GLOBAL file shared across all projects.
|
|
132
|
+
// We deep-merge our entry into it — never overwrite the whole file.
|
|
133
|
+
console.log('[ 6/7 ] Writing Antigravity config (~/.gemini/antigravity/mcp_config.json)...');
|
|
134
|
+
const antigravityConfigPath = path.join(
|
|
135
|
+
os.homedir(), '.gemini', 'antigravity', 'mcp_config.json'
|
|
136
|
+
);
|
|
137
|
+
writeAntigravityConfig(antigravityConfigPath, mcpServerAbsPath);
|
|
138
|
+
|
|
139
|
+
// ── Step 7: Update .gitignore if it already exists ────────────────────────
|
|
140
|
+
// We never create a .gitignore — that is the project's responsibility.
|
|
141
|
+
// The agent's generate-gitignore task already includes .codeninja/node_modules/
|
|
142
|
+
// in the .gitignore it writes during @initialize-project.
|
|
143
|
+
// This step handles the case where codeninja init runs on a project that
|
|
144
|
+
// already has a .gitignore but has NOT yet run @initialize-project.
|
|
145
|
+
console.log('[ 7/7 ] Checking .gitignore...');
|
|
146
|
+
const gitignorePath = path.join(projectRoot, '.gitignore');
|
|
147
|
+
const entry = '\n# codeninja — MCP server dependencies (do not commit)\n.codeninja/node_modules/\n';
|
|
148
|
+
if (fs.existsSync(gitignorePath)) {
|
|
149
|
+
const content = fs.readFileSync(gitignorePath, 'utf8');
|
|
150
|
+
if (!content.includes('.codeninja/node_modules')) {
|
|
151
|
+
fs.appendFileSync(gitignorePath, entry, 'utf8');
|
|
152
|
+
console.log(' .gitignore updated ✓');
|
|
153
|
+
} else {
|
|
154
|
+
console.log(' .gitignore already has entry — skipped');
|
|
155
|
+
}
|
|
156
|
+
} else {
|
|
157
|
+
console.log(' .gitignore not found — skipped');
|
|
158
|
+
console.log(' (The @initialize-project command will add this automatically)');
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// ── Done ──────────────────────────────────────────────────────────────────
|
|
162
|
+
printInstallSummary(mcpServerAbsPath);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// ─── Global MCP Config Writer (VS Code & Cursor) ──────────────────────────────
|
|
166
|
+
|
|
167
|
+
function writeGlobalMcpConfig(configPath, serversKey, entry, ideName) {
|
|
168
|
+
try {
|
|
169
|
+
fs.mkdirSync(path.dirname(configPath), { recursive: true });
|
|
170
|
+
|
|
171
|
+
let existing = {};
|
|
172
|
+
|
|
173
|
+
if (fs.existsSync(configPath)) {
|
|
174
|
+
try {
|
|
175
|
+
existing = JSON.parse(fs.readFileSync(configPath, 'utf8'));
|
|
176
|
+
} catch {
|
|
177
|
+
console.log(' Warning: existing ' + path.basename(configPath) + ' could not be parsed — will overwrite.');
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
if (!existing[serversKey]) existing[serversKey] = {};
|
|
182
|
+
|
|
183
|
+
const current = existing[serversKey].codeninja;
|
|
184
|
+
if (current && JSON.stringify(current.args) === JSON.stringify(entry.args)) {
|
|
185
|
+
console.log(' ' + ideName + ' global config already has codeninja entry — skipped');
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
existing[serversKey].codeninja = entry;
|
|
190
|
+
fs.writeFileSync(configPath, JSON.stringify(existing, null, 2), 'utf8');
|
|
191
|
+
console.log(' ' + configPath + ' written ✓');
|
|
192
|
+
} catch (e) {
|
|
193
|
+
console.log(' ' + ideName + ' global config skipped — could not write: ' + e.message);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// ─── Antigravity Config Writer ────────────────────────────────────────────────
|
|
198
|
+
|
|
199
|
+
function writeAntigravityConfig(configPath, mcpServerAbsPath) {
|
|
200
|
+
const newEntry = {
|
|
201
|
+
command: 'node',
|
|
202
|
+
args: [mcpServerAbsPath]
|
|
203
|
+
};
|
|
204
|
+
|
|
205
|
+
try {
|
|
206
|
+
fs.mkdirSync(path.dirname(configPath), { recursive: true });
|
|
207
|
+
|
|
208
|
+
let existing = { mcpServers: {} };
|
|
209
|
+
|
|
210
|
+
// If the file already exists, read and parse it safely.
|
|
211
|
+
// We deep-merge — never overwrite other entries the developer has added.
|
|
212
|
+
if (fs.existsSync(configPath)) {
|
|
213
|
+
try {
|
|
214
|
+
const raw = fs.readFileSync(configPath, 'utf8');
|
|
215
|
+
const parsed = JSON.parse(raw);
|
|
216
|
+
existing = parsed;
|
|
217
|
+
} catch {
|
|
218
|
+
// File is corrupt or empty — start fresh but preserve the file
|
|
219
|
+
console.log(' Warning: existing mcp_config.json could not be parsed — will overwrite.');
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
if (!existing.mcpServers) existing.mcpServers = {};
|
|
224
|
+
|
|
225
|
+
// Check if our entry is already there with the same path — skip if so
|
|
226
|
+
const current = existing.mcpServers.codeninja;
|
|
227
|
+
if (current && current.args && current.args[0] === mcpServerAbsPath) {
|
|
228
|
+
console.log(' Antigravity config already has codeninja entry — skipped');
|
|
229
|
+
return;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// Write our entry (adds or updates only the codeninja key)
|
|
233
|
+
existing.mcpServers.codeninja = newEntry;
|
|
234
|
+
|
|
235
|
+
fs.writeFileSync(configPath, JSON.stringify(existing, null, 2), 'utf8');
|
|
236
|
+
console.log(' ~/.gemini/antigravity/mcp_config.json written ✓');
|
|
237
|
+
|
|
238
|
+
} catch (e) {
|
|
239
|
+
// Antigravity may not be installed — this is non-fatal
|
|
240
|
+
console.log(' Antigravity config skipped — could not write: ' + e.message);
|
|
241
|
+
console.log(' (This is fine if you are not using Antigravity IDE)');
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
// ─── Summary ──────────────────────────────────────────────────────────────────
|
|
246
|
+
|
|
247
|
+
function printInstallSummary(mcpServerAbsPath) {
|
|
248
|
+
const platform = process.platform;
|
|
249
|
+
|
|
250
|
+
// Claude Desktop config path varies by OS
|
|
251
|
+
let claudeConfigPath;
|
|
252
|
+
if (platform === 'win32') {
|
|
253
|
+
claudeConfigPath = path.join(process.env.APPDATA || '%APPDATA%', 'Claude', 'claude_desktop_config.json');
|
|
254
|
+
} else if (platform === 'darwin') {
|
|
255
|
+
claudeConfigPath = path.join(os.homedir(), 'Library', 'Application Support', 'Claude', 'claude_desktop_config.json');
|
|
256
|
+
} else {
|
|
257
|
+
claudeConfigPath = path.join(os.homedir(), '.config', 'claude', 'claude_desktop_config.json');
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
console.log(`
|
|
261
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
262
|
+
codeninja installed successfully
|
|
263
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
264
|
+
|
|
265
|
+
✓ .codeninja/ agent files installed
|
|
266
|
+
✓ ~/.vscode/mcp.json VS Code configured (global)
|
|
267
|
+
✓ ~/.cursor/mcp.json Cursor configured (global)
|
|
268
|
+
✓ ~/.gemini/antigravity/ Antigravity configured (global)
|
|
269
|
+
|
|
270
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
271
|
+
CLAUDE DESKTOP (manual step — one time only)
|
|
272
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
273
|
+
|
|
274
|
+
Open this file:
|
|
275
|
+
${claudeConfigPath}
|
|
276
|
+
|
|
277
|
+
Add this inside the "mcpServers" object:
|
|
278
|
+
|
|
279
|
+
"codeninja": {
|
|
280
|
+
"command": "node",
|
|
281
|
+
"args": ["${mcpServerAbsPath}"]
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
Save and restart Claude Desktop.
|
|
285
|
+
|
|
286
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
287
|
+
IDE NOTES
|
|
288
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
289
|
+
|
|
290
|
+
VS Code — reopen this project to activate the MCP server
|
|
291
|
+
Cursor — reopen this project to activate the MCP server
|
|
292
|
+
Antigravity — go to ... → MCP Servers → Manage MCP Servers
|
|
293
|
+
and click Refresh to pick up the new entry
|
|
294
|
+
|
|
295
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
296
|
+
AVAILABLE COMMANDS (type in your AI chat)
|
|
297
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
298
|
+
|
|
299
|
+
@initialize-project Bootstrap a new service or database
|
|
300
|
+
@create-api Add a new API module to a service
|
|
301
|
+
@design Plan a feature before coding
|
|
302
|
+
@audit Security and quality review
|
|
303
|
+
@test Generate Jest test files
|
|
304
|
+
@refactor Rename with full context tracking
|
|
305
|
+
@sync Rebuild context from repo
|
|
306
|
+
|
|
307
|
+
Database commands:
|
|
308
|
+
@db:create-table Design and generate a new table
|
|
309
|
+
@db:modify-table Add / rename / drop a column
|
|
310
|
+
@db:add-index Add a new index
|
|
311
|
+
@db:drop-table Generate a DROP migration
|
|
312
|
+
@db:seed Add seed data
|
|
313
|
+
@db:sync Rebuild DB schema in context
|
|
314
|
+
|
|
315
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
316
|
+
|
|
317
|
+
Next step: open this project in your IDE,
|
|
318
|
+
then type @initialize-project to get started.
|
|
319
|
+
|
|
320
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
321
|
+
`);
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
function printHelp() {
|
|
325
|
+
console.log(`
|
|
326
|
+
codeninja — AI agent scaffolding system
|
|
327
|
+
|
|
328
|
+
Usage:
|
|
329
|
+
codeninja init Install into the current project directory
|
|
330
|
+
codeninja version Show the installed version
|
|
331
|
+
codeninja help Show this help message
|
|
332
|
+
|
|
333
|
+
After running init, open your project in VS Code, Cursor, or Antigravity.
|
|
334
|
+
The MCP server connects automatically.
|
|
335
|
+
`);
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
// ─── Helpers ─────────────────────────────────────────────────────────────────
|
|
339
|
+
|
|
340
|
+
function copyDir(src, dest) {
|
|
341
|
+
if (!fs.existsSync(src)) return;
|
|
342
|
+
fs.mkdirSync(dest, { recursive: true });
|
|
343
|
+
for (const entry of fs.readdirSync(src, { withFileTypes: true })) {
|
|
344
|
+
const srcPath = path.join(src, entry.name);
|
|
345
|
+
const destPath = path.join(dest, entry.name);
|
|
346
|
+
if (entry.isDirectory()) {
|
|
347
|
+
copyDir(srcPath, destPath);
|
|
348
|
+
} else {
|
|
349
|
+
fs.copyFileSync(srcPath, destPath);
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
}
|