@decohere/scaffold 0.0.1-dev.100

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 (208) hide show
  1. package/commonjs/src/add-api.action.js +37 -0
  2. package/commonjs/src/add-api.command.js +39 -0
  3. package/commonjs/src/add.command.js +22 -0
  4. package/commonjs/src/add.module.js +24 -0
  5. package/commonjs/src/auth0-configure.action.js +28 -0
  6. package/commonjs/src/auth0-configure.command.js +24 -0
  7. package/commonjs/src/auth0-login.action.js +28 -0
  8. package/commonjs/src/auth0-login.command.js +23 -0
  9. package/commonjs/src/auth0.command.js +22 -0
  10. package/commonjs/src/auth0.module.js +28 -0
  11. package/commonjs/src/cli.js +7 -0
  12. package/commonjs/src/create.command.js +22 -0
  13. package/commonjs/src/create.module.js +24 -0
  14. package/commonjs/src/domain/prompt.js +62 -0
  15. package/commonjs/src/index.js +48 -0
  16. package/commonjs/src/models/compose.model.js +258 -0
  17. package/commonjs/src/models/config-yaml.model.js +86 -0
  18. package/commonjs/src/models/const.js +30 -0
  19. package/commonjs/src/models/dev-container.model.js +172 -0
  20. package/commonjs/src/models/docker-image.model.js +277 -0
  21. package/commonjs/src/models/env-file.model.js +48 -0
  22. package/commonjs/src/models/gitignore.model.js +48 -0
  23. package/commonjs/src/models/launch-json.model.js +70 -0
  24. package/commonjs/src/models/package-json.model.js +156 -0
  25. package/commonjs/src/models/project.model.js +132 -0
  26. package/commonjs/src/models/source-file.js +4 -0
  27. package/commonjs/src/models/tsconfig.model.js +32 -0
  28. package/commonjs/src/models/vitest-config.model.js +33 -0
  29. package/commonjs/src/models/vscode-settings.model.js +29 -0
  30. package/commonjs/src/program.js +64 -0
  31. package/commonjs/src/resolve-target-dir.js +17 -0
  32. package/commonjs/src/services/scaffold.service.js +60 -0
  33. package/commonjs/src/version.js +55 -0
  34. package/commonjs/src/webapi/api-name.js +93 -0
  35. package/commonjs/src/webapi/auth0-commands.js +250 -0
  36. package/commonjs/src/webapi/authorize-scopes.js +79 -0
  37. package/commonjs/src/webapi/cli-options.js +99 -0
  38. package/commonjs/src/webapi/const.js +54 -0
  39. package/commonjs/src/webapi/create-api.js +265 -0
  40. package/commonjs/src/webapi/devcontainer/codebase-rag-mcp.js +164 -0
  41. package/commonjs/src/webapi/devcontainer/codebase-rag-mcp.ts +182 -0
  42. package/commonjs/src/webapi/devcontainer/host-scripts.js +50 -0
  43. package/commonjs/src/webapi/devcontainer/templates/codebase-rag.env.example +3 -0
  44. package/commonjs/src/webapi/devcontainer/templates/initialize.sh +169 -0
  45. package/commonjs/src/webapi/devcontainer/templates/post-create.sh +35 -0
  46. package/commonjs/src/webapi/devcontainer/templates/post-start.sh +65 -0
  47. package/commonjs/src/webapi/devcontainer/templates/tty-prompt.sh +38 -0
  48. package/commonjs/src/webapi/features/auth.feature.js +408 -0
  49. package/commonjs/src/webapi/features/auth0-cli.js +472 -0
  50. package/commonjs/src/webapi/features/biome-format.js +47 -0
  51. package/commonjs/src/webapi/features/database.feature.js +432 -0
  52. package/commonjs/src/webapi/features/finalize.feature.js +93 -0
  53. package/commonjs/src/webapi/features/postman.feature.js +322 -0
  54. package/commonjs/src/webapi/models/api-package-json.model.js +58 -0
  55. package/commonjs/src/webapi/models/auth-config.model.js +24 -0
  56. package/commonjs/src/webapi/models/controller-source-file.model.js +190 -0
  57. package/commonjs/src/webapi/models/github-ci.model.js +193 -0
  58. package/commonjs/src/webapi/models/main-source-file.model.js +165 -0
  59. package/commonjs/src/webapi/models/production-dockerfile.model.js +75 -0
  60. package/commonjs/src/webapi/models/sample-files.model.js +58 -0
  61. package/commonjs/src/webapi/models/webapi-image.model.js +25 -0
  62. package/commonjs/src/webapi/postman/hello-openapi.js +173 -0
  63. package/commonjs/src/webapi/postman/local-view.js +182 -0
  64. package/commonjs/src/webapi/postman/openapi-to-postman.js +200 -0
  65. package/commonjs/src/webapi/workspace-readme.js +109 -0
  66. package/commonjs/src/webapi.action.js +28 -0
  67. package/commonjs/src/webapi.command.js +43 -0
  68. package/es6/src/add-api.action.js +27 -0
  69. package/es6/src/add-api.command.js +29 -0
  70. package/es6/src/add.command.js +12 -0
  71. package/es6/src/add.module.js +14 -0
  72. package/es6/src/auth0-configure.action.js +18 -0
  73. package/es6/src/auth0-configure.command.js +14 -0
  74. package/es6/src/auth0-login.action.js +18 -0
  75. package/es6/src/auth0-login.command.js +13 -0
  76. package/es6/src/auth0.command.js +12 -0
  77. package/es6/src/auth0.module.js +18 -0
  78. package/es6/src/cli.js +3 -0
  79. package/es6/src/create.command.js +12 -0
  80. package/es6/src/create.module.js +14 -0
  81. package/es6/src/domain/prompt.js +41 -0
  82. package/es6/src/index.js +9 -0
  83. package/es6/src/models/compose.model.js +254 -0
  84. package/es6/src/models/config-yaml.model.js +74 -0
  85. package/es6/src/models/const.js +20 -0
  86. package/es6/src/models/dev-container.model.js +162 -0
  87. package/es6/src/models/docker-image.model.js +235 -0
  88. package/es6/src/models/env-file.model.js +44 -0
  89. package/es6/src/models/gitignore.model.js +38 -0
  90. package/es6/src/models/launch-json.model.js +60 -0
  91. package/es6/src/models/package-json.model.js +141 -0
  92. package/es6/src/models/project.model.js +117 -0
  93. package/es6/src/models/source-file.js +1 -0
  94. package/es6/src/models/tsconfig.model.js +22 -0
  95. package/es6/src/models/vitest-config.model.js +23 -0
  96. package/es6/src/models/vscode-settings.model.js +19 -0
  97. package/es6/src/program.js +46 -0
  98. package/es6/src/resolve-target-dir.js +7 -0
  99. package/es6/src/services/scaffold.service.js +50 -0
  100. package/es6/src/version.js +34 -0
  101. package/es6/src/webapi/api-name.js +57 -0
  102. package/es6/src/webapi/auth0-commands.js +185 -0
  103. package/es6/src/webapi/authorize-scopes.js +55 -0
  104. package/es6/src/webapi/cli-options.js +34 -0
  105. package/es6/src/webapi/const.js +21 -0
  106. package/es6/src/webapi/create-api.js +245 -0
  107. package/es6/src/webapi/devcontainer/codebase-rag-mcp.js +154 -0
  108. package/es6/src/webapi/devcontainer/codebase-rag-mcp.ts +182 -0
  109. package/es6/src/webapi/devcontainer/host-scripts.js +29 -0
  110. package/es6/src/webapi/devcontainer/templates/codebase-rag.env.example +3 -0
  111. package/es6/src/webapi/devcontainer/templates/initialize.sh +169 -0
  112. package/es6/src/webapi/devcontainer/templates/post-create.sh +35 -0
  113. package/es6/src/webapi/devcontainer/templates/post-start.sh +65 -0
  114. package/es6/src/webapi/devcontainer/templates/tty-prompt.sh +38 -0
  115. package/es6/src/webapi/features/auth.feature.js +357 -0
  116. package/es6/src/webapi/features/auth0-cli.js +406 -0
  117. package/es6/src/webapi/features/biome-format.js +40 -0
  118. package/es6/src/webapi/features/database.feature.js +422 -0
  119. package/es6/src/webapi/features/finalize.feature.js +83 -0
  120. package/es6/src/webapi/features/postman.feature.js +256 -0
  121. package/es6/src/webapi/models/api-package-json.model.js +53 -0
  122. package/es6/src/webapi/models/auth-config.model.js +20 -0
  123. package/es6/src/webapi/models/controller-source-file.model.js +181 -0
  124. package/es6/src/webapi/models/github-ci.model.js +186 -0
  125. package/es6/src/webapi/models/main-source-file.model.js +161 -0
  126. package/es6/src/webapi/models/production-dockerfile.model.js +66 -0
  127. package/es6/src/webapi/models/sample-files.model.js +40 -0
  128. package/es6/src/webapi/models/webapi-image.model.js +18 -0
  129. package/es6/src/webapi/postman/hello-openapi.js +166 -0
  130. package/es6/src/webapi/postman/local-view.js +167 -0
  131. package/es6/src/webapi/postman/openapi-to-postman.js +177 -0
  132. package/es6/src/webapi/workspace-readme.js +88 -0
  133. package/es6/src/webapi.action.js +18 -0
  134. package/es6/src/webapi.command.js +33 -0
  135. package/package.json +36 -0
  136. package/src/webapi/devcontainer/codebase-rag-mcp.ts +182 -0
  137. package/src/webapi/devcontainer/templates/codebase-rag.env.example +3 -0
  138. package/src/webapi/devcontainer/templates/initialize.sh +169 -0
  139. package/src/webapi/devcontainer/templates/post-create.sh +35 -0
  140. package/src/webapi/devcontainer/templates/post-start.sh +65 -0
  141. package/src/webapi/devcontainer/templates/tty-prompt.sh +38 -0
  142. package/types/src/add-api.action.d.ts +13 -0
  143. package/types/src/add-api.command.d.ts +7 -0
  144. package/types/src/add.command.d.ts +6 -0
  145. package/types/src/add.module.d.ts +14 -0
  146. package/types/src/auth0-configure.action.d.ts +7 -0
  147. package/types/src/auth0-configure.command.d.ts +6 -0
  148. package/types/src/auth0-login.action.d.ts +4 -0
  149. package/types/src/auth0-login.command.d.ts +6 -0
  150. package/types/src/auth0.command.d.ts +6 -0
  151. package/types/src/auth0.module.d.ts +18 -0
  152. package/types/src/cli.d.ts +2 -0
  153. package/types/src/create.command.d.ts +6 -0
  154. package/types/src/create.module.d.ts +14 -0
  155. package/types/src/domain/prompt.d.ts +4 -0
  156. package/types/src/index.d.ts +13 -0
  157. package/types/src/models/compose.model.d.ts +73 -0
  158. package/types/src/models/config-yaml.model.d.ts +27 -0
  159. package/types/src/models/const.d.ts +20 -0
  160. package/types/src/models/dev-container.model.d.ts +49 -0
  161. package/types/src/models/docker-image.model.d.ts +90 -0
  162. package/types/src/models/env-file.model.d.ts +16 -0
  163. package/types/src/models/gitignore.model.d.ts +9 -0
  164. package/types/src/models/launch-json.model.d.ts +7 -0
  165. package/types/src/models/package-json.model.d.ts +36 -0
  166. package/types/src/models/project.model.d.ts +60 -0
  167. package/types/src/models/source-file.d.ts +11 -0
  168. package/types/src/models/tsconfig.model.d.ts +5 -0
  169. package/types/src/models/vitest-config.model.d.ts +5 -0
  170. package/types/src/models/vscode-settings.model.d.ts +7 -0
  171. package/types/src/program.d.ts +3 -0
  172. package/types/src/resolve-target-dir.d.ts +5 -0
  173. package/types/src/services/scaffold.service.d.ts +22 -0
  174. package/types/src/version.d.ts +6 -0
  175. package/types/src/webapi/api-name.d.ts +14 -0
  176. package/types/src/webapi/auth0-commands.d.ts +11 -0
  177. package/types/src/webapi/authorize-scopes.d.ts +7 -0
  178. package/types/src/webapi/cli-options.d.ts +6 -0
  179. package/types/src/webapi/const.d.ts +19 -0
  180. package/types/src/webapi/create-api.d.ts +15 -0
  181. package/types/src/webapi/devcontainer/codebase-rag-mcp.d.ts +32 -0
  182. package/types/src/webapi/devcontainer/codebase-rag-mcp.ts +182 -0
  183. package/types/src/webapi/devcontainer/host-scripts.d.ts +7 -0
  184. package/types/src/webapi/devcontainer/templates/codebase-rag.env.example +3 -0
  185. package/types/src/webapi/devcontainer/templates/initialize.sh +169 -0
  186. package/types/src/webapi/devcontainer/templates/post-create.sh +35 -0
  187. package/types/src/webapi/devcontainer/templates/post-start.sh +65 -0
  188. package/types/src/webapi/devcontainer/templates/tty-prompt.sh +38 -0
  189. package/types/src/webapi/features/auth.feature.d.ts +10 -0
  190. package/types/src/webapi/features/auth0-cli.d.ts +84 -0
  191. package/types/src/webapi/features/biome-format.d.ts +5 -0
  192. package/types/src/webapi/features/database.feature.d.ts +7 -0
  193. package/types/src/webapi/features/finalize.feature.d.ts +2 -0
  194. package/types/src/webapi/features/postman.feature.d.ts +16 -0
  195. package/types/src/webapi/models/api-package-json.model.d.ts +18 -0
  196. package/types/src/webapi/models/auth-config.model.d.ts +26 -0
  197. package/types/src/webapi/models/controller-source-file.model.d.ts +46 -0
  198. package/types/src/webapi/models/github-ci.model.d.ts +9 -0
  199. package/types/src/webapi/models/main-source-file.model.d.ts +54 -0
  200. package/types/src/webapi/models/production-dockerfile.model.d.ts +18 -0
  201. package/types/src/webapi/models/sample-files.model.d.ts +7 -0
  202. package/types/src/webapi/models/webapi-image.model.d.ts +12 -0
  203. package/types/src/webapi/postman/hello-openapi.d.ts +19 -0
  204. package/types/src/webapi/postman/local-view.d.ts +17 -0
  205. package/types/src/webapi/postman/openapi-to-postman.d.ts +62 -0
  206. package/types/src/webapi/workspace-readme.d.ts +18 -0
  207. package/types/src/webapi.action.d.ts +14 -0
  208. package/types/src/webapi.command.d.ts +11 -0
@@ -0,0 +1,177 @@
1
+ import converter from "openapi-to-postmanv2";
2
+ function convertAsync(data, options) {
3
+ return new Promise((resolve, reject)=>{
4
+ converter.convert({
5
+ type: "json",
6
+ data
7
+ }, options, (err, result)=>{
8
+ if (err) {
9
+ reject(err);
10
+ return;
11
+ }
12
+ resolve(result);
13
+ });
14
+ });
15
+ }
16
+ /** Convert an OpenAPI document to a Postman Collection v2.1 via openapi-to-postmanv2. */ export async function openApiToPostmanCollection(document) {
17
+ const result = await convertAsync(document, {
18
+ folderStrategy: "Tags",
19
+ requestNameSource: "Fallback",
20
+ includeAuthInfoInExample: false
21
+ });
22
+ if (!result.result || result.output?.[0]?.data == null) {
23
+ throw new Error(result.reason ?? "openapi-to-postmanv2 conversion failed");
24
+ }
25
+ return result.output[0].data;
26
+ }
27
+ /**
28
+ * Overlay Auth0 OAuth2 (PKCE) collection auth and fix Local View-friendly URLs.
29
+ * Route items come from OpenAPI; auth/env remain hand-authored.
30
+ */ export function applyPostmanAuthOverlay(collection, projectName, auth, options) {
31
+ const next = {
32
+ ...collection,
33
+ info: {
34
+ ...collection.info,
35
+ name: `${projectName} WebAPI`,
36
+ description: options.description,
37
+ schema: collection.info.schema ?? "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
38
+ }
39
+ };
40
+ if (auth?.auth0Domain) {
41
+ next.auth = {
42
+ type: "oauth2",
43
+ oauth2: [
44
+ {
45
+ key: "grant_type",
46
+ value: "authorization_code_with_pkce",
47
+ type: "string"
48
+ },
49
+ {
50
+ key: "authUrl",
51
+ value: "https://{{auth0Domain}}/authorize?audience={{auth0Audience}}",
52
+ type: "string"
53
+ },
54
+ {
55
+ key: "accessTokenUrl",
56
+ value: "https://{{auth0Domain}}/oauth/token",
57
+ type: "string"
58
+ },
59
+ {
60
+ key: "clientId",
61
+ value: "{{auth0ClientId}}",
62
+ type: "string"
63
+ },
64
+ {
65
+ key: "scope",
66
+ value: options.scopes,
67
+ type: "string"
68
+ },
69
+ {
70
+ key: "redirect_uri",
71
+ value: options.callbacks[0],
72
+ type: "string"
73
+ },
74
+ {
75
+ key: "useBrowser",
76
+ value: "true",
77
+ type: "boolean"
78
+ },
79
+ {
80
+ key: "addTokenTo",
81
+ value: "header",
82
+ type: "string"
83
+ },
84
+ {
85
+ key: "challengeAlgorithm",
86
+ value: "S256",
87
+ type: "string"
88
+ },
89
+ {
90
+ key: "client_authentication",
91
+ value: "body",
92
+ type: "string"
93
+ },
94
+ {
95
+ key: "clientSecret",
96
+ value: "",
97
+ type: "string"
98
+ },
99
+ {
100
+ key: "tokenName",
101
+ value: `${projectName} Access Token`,
102
+ type: "string"
103
+ }
104
+ ]
105
+ };
106
+ }
107
+ next.item = rewriteCollectionItems(collection.item ?? [], {
108
+ markListAnonymous: true
109
+ });
110
+ return next;
111
+ }
112
+ function rewriteCollectionItems(items, opts) {
113
+ return items.map((item)=>{
114
+ if (item.item != null) {
115
+ return {
116
+ ...item,
117
+ item: rewriteCollectionItems(item.item, opts)
118
+ };
119
+ }
120
+ const request = item.request;
121
+ if (request == null) {
122
+ return item;
123
+ }
124
+ const method = (request.method ?? "GET").toUpperCase();
125
+ const rawUrl = requestUrlRaw(request);
126
+ const nextRequest = {
127
+ ...request,
128
+ url: toPostmanUrl(rawUrl)
129
+ };
130
+ // List Hello stays anonymous (matches scaffold AllowAnonymous on GET /hello).
131
+ if (opts.markListAnonymous && method === "GET" && normalizePath(rawUrl) === "/hello" && !rawUrl.includes("/:")) {
132
+ nextRequest.auth = {
133
+ type: "noauth"
134
+ };
135
+ }
136
+ return {
137
+ ...item,
138
+ request: nextRequest
139
+ };
140
+ });
141
+ }
142
+ function requestUrlRaw(request) {
143
+ const url = request.url;
144
+ if (typeof url === "string") {
145
+ return url;
146
+ }
147
+ if (url?.raw) {
148
+ return url.raw;
149
+ }
150
+ const host = url?.host?.[0] ?? "{{baseUrl}}";
151
+ const path = (url?.path ?? []).join("/");
152
+ return path ? `${host}/${path}` : host;
153
+ }
154
+ /** OpenAPI `{id}` → Postman `:id`, without touching `{{variables}}`. */ export function openApiPathParamsToColon(url) {
155
+ return url.replace(/(?<!\{)\{([A-Za-z_][\w]*)\}(?!\})/g, ":$1");
156
+ }
157
+ function normalizePath(rawUrl) {
158
+ const withoutBase = rawUrl.replace(/\{\{baseUrl\}\}/g, "").split("?")[0] ?? "";
159
+ const withSlash = withoutBase.startsWith("/") ? withoutBase : `/${withoutBase}`;
160
+ return openApiPathParamsToColon(withSlash);
161
+ }
162
+ /** Prefer Postman template URL shape used by existing Local View / API uploads. */ function toPostmanUrl(rawUrl) {
163
+ let pathPart = rawUrl.replace(/\{\{baseUrl\}\}/g, "");
164
+ if (!pathPart.startsWith("/")) {
165
+ pathPart = `/${pathPart}`;
166
+ }
167
+ // openapi-to-postman uses {id}; Local View / prior collections use :id
168
+ pathPart = openApiPathParamsToColon(pathPart);
169
+ const segments = pathPart.split("/").filter(Boolean);
170
+ return {
171
+ raw: `{{baseUrl}}${pathPart}`,
172
+ host: [
173
+ "{{baseUrl}}"
174
+ ],
175
+ path: segments
176
+ };
177
+ }
@@ -0,0 +1,88 @@
1
+ import { existsSync } from "node:fs";
2
+ import { join } from "node:path";
3
+ import { WELL_KNOWN_FILES } from "../models/const.js";
4
+ import { apiNameFromProjectDir, listExistingApiNames } from "./api-name.js";
5
+ /** Pure renderer — keep feature sections short and copy-paste ready. */ export function renderWorkspaceReadme(input) {
6
+ const { projectName, apiNames, hasPostgres, hasAuth0, hasPostman } = input;
7
+ const apis = apiNames.length > 0 ? apiNames : [
8
+ "api"
9
+ ];
10
+ const lines = [
11
+ `# ${projectName}`,
12
+ "",
13
+ "Decohere web API workspace. Scaffolded with [`@decohere/create`](https://www.npmjs.com/package/@decohere/create).",
14
+ "",
15
+ "## Get started",
16
+ "",
17
+ "1. Open this folder in VS Code / Cursor.",
18
+ "2. **Reopen in Container** (or rebuild the Dev Container).",
19
+ " Host `initialize.sh` and container `post-start.sh` ask y/N (TTY only) before npm auth or codebase RAG MCP.",
20
+ " Skip or decline; missing TTY skips. RAG credentials land in `.cursor/` (gitignored).",
21
+ "3. Install dependencies if needed:",
22
+ "",
23
+ "```bash",
24
+ "pnpm install",
25
+ "```",
26
+ "",
27
+ "## Develop",
28
+ "",
29
+ "```bash",
30
+ "pnpm dev # Flux HMR (first API)",
31
+ "pnpm start # run the compiled first API",
32
+ "pnpm run build # forge release build",
33
+ "pnpm run package # release build + .package/ for each API (what Docker copies)"
34
+ ];
35
+ for (const name of apis){
36
+ lines.push(`pnpm run dev:${name} # HMR for ${name}`);
37
+ lines.push(`pnpm run start:${name} # production entry for ${name}`);
38
+ }
39
+ lines.push("```", "");
40
+ lines.push("## Logging", "", "In development, Scribe uses pino-pretty and rolling files under `logs/` (gitignored).", "When `NODE_ENV=production`, the process writes structured JSON to stdout and does not rotate files.", "Ship and retain those streams outside the app (platform logs, a sidecar, or a log agent) so shipping can change without rebuilding.", "", "Override roll path and rotation without changing app code:", "`SCRIBE_LOG_FILE`, `SCRIBE_LOG_SIZE`, `SCRIBE_LOG_FREQUENCY`, `SCRIBE_LOG_KEEP`.", "", 'Opt into in-process pretty or roll in production only with `{ when: "always" }` on `scribe.withPinoPretty` / `scribe.withPinoRoll`.', "");
41
+ if (hasPostgres) {
42
+ lines.push("## Database", "", "Postgres runs via Dev Container Compose:", "", "```bash", "pnpm db:up", "pnpm db:down", "pnpm db:reset", "```", "");
43
+ }
44
+ lines.push("## Deploy", "", "Each API has its own `projects/<name>/Dockerfile`. It copies `.package/`, which `forge package`", "writes before Docker runs: the API's compiled code, the workspace libraries it imports, and", "a plain `node_modules` of the third-party packages it uses (no symlinks). The image build", "installs nothing and needs no npm token.", "", "```bash", "pnpm run package # release build, then .package/ in every API", `docker build -t ${projectName} projects/api`, "```", "", "One API: `pnpm run package:<name>`, then `docker build projects/<name>`.", "A host that builds from a git clone does not have `.package/`; build and push the image", "from CI instead (`forge docker` after `pnpm run package`).", "");
45
+ lines.push("## Add another API", "", "```bash", "decohere add api <name>", "```", "", "Example: `decohere add api billing`", "");
46
+ if (hasAuth0) {
47
+ lines.push("## Auth0", "", "JWT auth is wired from `.env` (`AUTH0_ISSUER_BASE_URL`, `AUTH0_AUDIENCE`).", 'After you add a controller, `@Authorize("read", "Invoice")` becomes the Auth0 permission `read:Invoice`.', "Sync the Auth0 API, admin grants, and Postman token scopes without re-scaffolding:", "", "```bash", "npx @decohere/create auth0 login # once / when the CLI token expires", "npx @decohere/create auth0 configure # after new @Authorize decorators", "```", "");
48
+ }
49
+ if (hasPostman) {
50
+ lines.push("## Postman", "", "Open this **project root** in Postman Local View (Files → Open folder), not the `postman/` subfolder.", "Select the Local environment, then Authorization → Get New Access Token when Auth0 is enabled.", "");
51
+ }
52
+ lines.push("## More", "", "- Package docs: https://www.npmjs.com/package/@decohere/create", "- After `decohere add api`, this README is refreshed so scripts and APIs stay accurate.", "");
53
+ return `${lines.join("\n")}`;
54
+ }
55
+ export function resolveWorkspaceReadmeInput(workspace, options) {
56
+ const newNames = workspace.getProjects().map((project)=>apiNameFromProjectDir(project.dir));
57
+ const apiNames = options.add ? mergeApiNames(listExistingApiNames(workspace.targetDir), newNames) : newNames;
58
+ const pkg = workspace.getModel(WELL_KNOWN_FILES.PACKAGE_JSON);
59
+ const env = workspace.getModel(WELL_KNOWN_FILES.ENV_FILE);
60
+ const hasPostgres = options.databaseProvider === "postgres/typeorm" || Boolean(pkg?.hasScript("db:up"));
61
+ const hasAuth0 = options.authProvider === "auth0" || Boolean(env?.hasEntry("AUTH0_ISSUER_BASE_URL"));
62
+ const hasPostman = options.postmanProvider === "local" || options.postmanProvider === "api" || existsSync(join(workspace.targetDir, "postman")) || existsSync(join(workspace.targetDir, ".postman"));
63
+ return {
64
+ projectName: workspace.name,
65
+ apiNames,
66
+ hasPostgres,
67
+ hasAuth0,
68
+ hasPostman
69
+ };
70
+ }
71
+ export function writeWorkspaceReadme(workspace, options) {
72
+ const content = renderWorkspaceReadme(resolveWorkspaceReadmeInput(workspace, options));
73
+ workspace.addFile({
74
+ files: ()=>[
75
+ {
76
+ filePath: "README.md",
77
+ content
78
+ }
79
+ ]
80
+ });
81
+ }
82
+ function mergeApiNames(existing, added) {
83
+ const seen = new Set(existing);
84
+ return [
85
+ ...existing,
86
+ ...added.filter((name)=>!seen.has(name))
87
+ ];
88
+ }
@@ -0,0 +1,18 @@
1
+ function _ts_decorate(decorators, target, key, desc) {
2
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
3
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
4
+ else for(var i = decorators.length - 1; i >= 0; i--)if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
5
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
6
+ }
7
+ import { injectable } from "@decohere/crucible/decorators";
8
+ import { ActionResponse } from "@decohere/nexus/cli";
9
+ import { runScaffoldApi, toApiOptions } from "./webapi/cli-options.js";
10
+ export class WebapiAction {
11
+ async run(options) {
12
+ await runScaffoldApi(toApiOptions(options.directory, options));
13
+ return ActionResponse.Success("webapi");
14
+ }
15
+ }
16
+ WebapiAction = _ts_decorate([
17
+ injectable()
18
+ ], WebapiAction);
@@ -0,0 +1,33 @@
1
+ import { DecohereCommand } from "@decohere/nexus/cli";
2
+ import { addWebapiFeatureOptions, collectName } from "./webapi/cli-options.js";
3
+ import { WebapiAction } from "./webapi.action.js";
4
+ export class WebapiCommand extends DecohereCommand {
5
+ get actions() {
6
+ return [
7
+ WebapiAction
8
+ ];
9
+ }
10
+ constructor(name){
11
+ super(name);
12
+ this.description("Scaffold a Cortex web API workspace");
13
+ this.argument("[directory]", "Target directory (default: -d or current directory)");
14
+ this.option("-d <directory>", "Target directory (default: current directory)", ".");
15
+ this.option("--name <slug>", "API name under projects/ (repeatable; default: api)", collectName, []);
16
+ addWebapiFeatureOptions(this);
17
+ }
18
+ /**
19
+ * Commander passes positional args before options when `[directory]` is declared.
20
+ * ModuleFactory expects `(opts, cmd)` — stash the directory on the command.
21
+ */ action(fn) {
22
+ return super.action((...args)=>{
23
+ const cmd = args.at(-1);
24
+ const first = args[0];
25
+ const hasPositional = typeof first === "string" || first === undefined;
26
+ const opts = hasPositional ? args[1] : args[0];
27
+ if (typeof first === "string" && first !== "") {
28
+ cmd.setOptionValue("directory", first);
29
+ }
30
+ return fn(opts, cmd);
31
+ });
32
+ }
33
+ }
package/package.json ADDED
@@ -0,0 +1,36 @@
1
+ {
2
+ "name": "@decohere/scaffold",
3
+ "version": "0.0.1-dev.100",
4
+ "description": "Decohere project scaffolder (runs inside ghcr.io/typesmiths/decohere/create)",
5
+ "keywords": [
6
+ "decohere",
7
+ "scaffold"
8
+ ],
9
+ "license": "ISC",
10
+ "author": "Typesmiths",
11
+ "type": "module",
12
+ "exports": {
13
+ ".": {
14
+ "types": "./types/src/index.d.ts",
15
+ "import": "./es6/src/index.js"
16
+ }
17
+ },
18
+ "bin": {
19
+ "decohere": "./es6/src/cli.js"
20
+ },
21
+ "scripts": {
22
+ "test": "echo \"Error: no test specified\" && exit 1"
23
+ },
24
+ "dependencies": {
25
+ "@decohere/crucible": "0.0.1-dev.100",
26
+ "@decohere/nexus": "0.0.1-dev.100",
27
+ "commander": "^13.1.0",
28
+ "openapi-to-postmanv2": "^5.0.0",
29
+ "yaml": "^2.9.1"
30
+ },
31
+ "devDependencies": {},
32
+ "decohere": {
33
+ "dockerImageRepository": "ghcr.io/typesmiths/decohere/create",
34
+ "devcontainerImageRepository": "ghcr.io/typesmiths/decohere/devcontainer"
35
+ }
36
+ }
@@ -0,0 +1,182 @@
1
+ import {
2
+ chmodSync,
3
+ mkdirSync,
4
+ readFileSync,
5
+ renameSync,
6
+ statSync,
7
+ writeFileSync,
8
+ } from "node:fs";
9
+ import { dirname, join } from "node:path";
10
+ import { fileURLToPath } from "node:url";
11
+
12
+ export type CodebaseRagCredentials = {
13
+ url: string;
14
+ token: string;
15
+ };
16
+
17
+ export type McpJson = {
18
+ mcpServers?: Record<string, unknown>;
19
+ [key: string]: unknown;
20
+ };
21
+
22
+ function isRecord(value: unknown): value is Record<string, unknown> {
23
+ return value != null && typeof value === "object" && !Array.isArray(value);
24
+ }
25
+
26
+ function loadDotenv(path: string, env: NodeJS.ProcessEnv): void {
27
+ let raw: string;
28
+ try {
29
+ raw = readFileSync(path, "utf8");
30
+ } catch {
31
+ return;
32
+ }
33
+ for (const line of raw.split(/\r?\n/)) {
34
+ const trimmed = line.trim();
35
+ if (!trimmed || trimmed.startsWith("#") || !trimmed.includes("=")) continue;
36
+ const eq = trimmed.indexOf("=");
37
+ const key = trimmed.slice(0, eq).trim();
38
+ const value = trimmed
39
+ .slice(eq + 1)
40
+ .trim()
41
+ .replace(/^["']|["']$/g, "");
42
+ if (key && env[key] === undefined) env[key] = value;
43
+ }
44
+ }
45
+
46
+ function seedFromMcpJson(raw: string): CodebaseRagCredentials | null {
47
+ let data: unknown;
48
+ try {
49
+ data = JSON.parse(raw);
50
+ } catch {
51
+ return null;
52
+ }
53
+ if (!isRecord(data)) return null;
54
+ const servers = data.mcpServers;
55
+ if (!isRecord(servers)) return null;
56
+ const server = servers["codebase-rag"];
57
+ if (!isRecord(server)) return null;
58
+ const url = typeof server.url === "string" ? server.url.trim() : "";
59
+ const headers = isRecord(server.headers) ? server.headers : {};
60
+ const auth = typeof headers.Authorization === "string" ? headers.Authorization.trim() : "";
61
+ const token = auth.toLowerCase().startsWith("bearer ") ? auth.slice(7).trim() : "";
62
+ if (!url.startsWith("http") || token.length === 0) return null;
63
+ return { url, token };
64
+ }
65
+
66
+ function mergeIntoMcpJson(input: {
67
+ mcpPath: string;
68
+ url: string;
69
+ token: string;
70
+ hostRoot: string;
71
+ }): { skipped?: string; path?: string; root?: string } {
72
+ const { mcpPath, url, token, hostRoot } = input;
73
+ if (!url || !token) {
74
+ return { skipped: "no URL/token" };
75
+ }
76
+
77
+ const xRoot = hostRoot || "${workspaceFolder}";
78
+ mkdirSync(dirname(mcpPath), { recursive: true });
79
+
80
+ let data: McpJson = { mcpServers: {} };
81
+ try {
82
+ if (statSync(mcpPath).size > 0) {
83
+ const loaded: unknown = JSON.parse(readFileSync(mcpPath, "utf8"));
84
+ if (isRecord(loaded)) data = loaded as McpJson;
85
+ }
86
+ } catch (err) {
87
+ const code = isRecord(err) && typeof err.code === "string" ? err.code : "";
88
+ if (code !== "ENOENT") {
89
+ console.log(
90
+ "codebase-rag MCP: existing mcp.json is invalid JSON; replacing rag entry only after backup",
91
+ );
92
+ renameSync(mcpPath, `${mcpPath}.bak`);
93
+ }
94
+ }
95
+
96
+ const servers = isRecord(data.mcpServers) ? data.mcpServers : {};
97
+ data.mcpServers = servers;
98
+ servers["codebase-rag"] = {
99
+ url,
100
+ headers: {
101
+ Authorization: `Bearer ${token}`,
102
+ "X-Codebase-Root": xRoot,
103
+ },
104
+ };
105
+ writeFileSync(mcpPath, `${JSON.stringify(data, null, 2)}\n`, "utf8");
106
+ try {
107
+ chmodSync(mcpPath, 0o600);
108
+ } catch {
109
+ // memfs and some hosts omit chmod
110
+ }
111
+ return { path: mcpPath, root: xRoot };
112
+ }
113
+
114
+ function workspaceRoot(env: NodeJS.ProcessEnv = process.env): string {
115
+ const fromEnv = env.CODEBASE_RAG_WORKSPACE?.trim();
116
+ if (fromEnv) return fromEnv;
117
+ // Scaffolded copy lives at <repo>/.devcontainer/codebase-rag-mcp.ts
118
+ return dirname(dirname(fileURLToPath(import.meta.url)));
119
+ }
120
+
121
+ function mergeFromEnv(env: NodeJS.ProcessEnv = process.env): number {
122
+ const root = workspaceRoot(env);
123
+ const cursorDir = join(root, ".cursor");
124
+ loadDotenv(join(cursorDir, "codebase-rag.env"), env);
125
+ const url = (env.CODEBASE_RAG_MCP_URL ?? "").trim();
126
+ const token = (env.CODEBASE_RAG_MCP_TOKEN ?? "").trim();
127
+ const hostRoot = (env.CODEBASE_RAG_MCP_ROOT ?? "").trim() || root;
128
+ const result = mergeIntoMcpJson({
129
+ mcpPath: join(cursorDir, "mcp.json"),
130
+ url,
131
+ token,
132
+ hostRoot,
133
+ });
134
+ if (result.skipped != null) {
135
+ console.log(`codebase-rag MCP: skipped (${result.skipped})`);
136
+ return 0;
137
+ }
138
+ console.log(`codebase-rag MCP: wrote ${result.path} url=${url} root=${result.root}`);
139
+ return 0;
140
+ }
141
+
142
+ function seedPrint(mcpPath: string): number {
143
+ let raw: string;
144
+ try {
145
+ raw = readFileSync(mcpPath, "utf8");
146
+ } catch {
147
+ return 1;
148
+ }
149
+ const creds = seedFromMcpJson(raw);
150
+ if (creds == null) return 1;
151
+ console.log(creds.url);
152
+ console.log(creds.token);
153
+ return 0;
154
+ }
155
+
156
+ function main(argv = process.argv.slice(2)): number {
157
+ const command = argv[0] ?? "merge";
158
+ if (command === "seed-print") {
159
+ const mcpPath = argv[1];
160
+ if (mcpPath == null || mcpPath.length === 0) return 1;
161
+ return seedPrint(mcpPath);
162
+ }
163
+ if (command === "merge") {
164
+ return mergeFromEnv();
165
+ }
166
+ console.error(`codebase-rag MCP: unknown command ${command}`);
167
+ return 1;
168
+ }
169
+
170
+ const invoked = (process.argv[1] ?? "").replaceAll("\\", "/");
171
+ if (invoked.endsWith("/codebase-rag-mcp.ts") || invoked.endsWith("/codebase-rag-mcp.js")) {
172
+ process.exit(main());
173
+ }
174
+
175
+ export const codebaseRagMcp = {
176
+ loadDotenv,
177
+ seedFromMcpJson,
178
+ mergeIntoMcpJson,
179
+ mergeFromEnv,
180
+ seedPrint,
181
+ main,
182
+ };
@@ -0,0 +1,3 @@
1
+ # Optional. Written to .cursor/codebase-rag.env by initialize.sh (gitignored).
2
+ # CODEBASE_RAG_MCP_URL=https://rag.example.com/mcp
3
+ # CODEBASE_RAG_MCP_TOKEN=