create-rakomi-app 0.1.0 → 0.1.1

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/README.md CHANGED
@@ -3,6 +3,8 @@
3
3
  Scaffold a [Rakomi](https://rakomi.com) quickstart app in seconds. Rakomi is EU-native
4
4
  authentication as a service.
5
5
 
6
+ ## Getting started
7
+
6
8
  ```sh
7
9
  npx create-rakomi-app@latest --template nextjs my-app
8
10
  ```
@@ -17,7 +19,7 @@ npm create @rakomi/rakomi-app@latest -- --template nextjs my-app
17
19
  > scoped package from the bare name. Use one of the two forms above. (An unscoped alias may be
18
20
  > offered in the future.)
19
21
 
20
- ## Templates
22
+ ## Usage
21
23
 
22
24
  | Slug | Description |
23
25
  | -------- | ----------------- |
@@ -26,57 +28,40 @@ npm create @rakomi/rakomi-app@latest -- --template nextjs my-app
26
28
  | `node` | Node quickstart |
27
29
  | `expo` | Expo quickstart |
28
30
 
29
- The template list is **not** hardcoded — it is generated from a shared quickstart registry that is
30
- the single source of truth. Adding a quickstart there makes it available here automatically.
31
-
32
- ## Options
33
-
34
- | Option | Meaning |
35
- | ------------------------- | --------------------------------------------------------- |
36
- | `--template <slug>` | which quickstart to scaffold (required) |
37
- | `--region <value>` | data region (default: `eu-central`) |
38
- | `--tenant-id <value>` | your tenant id |
39
- | `--template-source <url>` | override the archive base (mirror / offline) |
40
- | `--yes` | accept defaults, never prompt (non-interactive) |
41
- | `-h`, `--help` | show help |
42
- | `-V`, `--version` | print the version |
31
+ | Option | Meaning |
32
+ | ------------------------- | ------------------------------------------------- |
33
+ | `--template <slug>` | which quickstart to scaffold (required) |
34
+ | `--region <value>` | data region (default: `eu-central`) |
35
+ | `--tenant-id <value>` | your tenant id |
36
+ | `--template-source <url>` | override the archive base (mirror / offline) |
37
+ | `--yes` | accept defaults, never prompt (non-interactive) |
38
+ | `-h`, `--help` | show help |
39
+ | `-V`, `--version` | print the version |
43
40
 
44
41
  The CLI prompts for `RAKOMI_REGION`, `RAKOMI_TENANT_ID`, and `RAKOMI_API_KEY` and writes them to a
45
42
  local `.env` in the new project. Value precedence is: flag > environment variable > prompt >
46
43
  default. In a non-interactive context (a pipe, a continuous-integration job, or `--yes`) it never
47
44
  blocks — it uses flags / environment values / defaults and leaves the rest for you to fill in.
48
45
 
49
- The `RAKOMI_API_KEY` is a credential: it is read only from the prompt or the environment (never a
50
- command-line flag, which would persist in shell history), is written only to your local `.env`, is
51
- never printed back, and the new project's `.gitignore` is updated so you do not commit it.
52
-
53
- ### EU-region default
54
-
55
46
  `RAKOMI_REGION` defaults to `eu-central` — a visible data-residency stance, not a mandate. Override
56
47
  it with `--region` or the `RAKOMI_REGION` environment variable for any other region.
57
48
 
58
- ## Intentional choices
49
+ ## Caveats
59
50
 
51
+ - **`RAKOMI_API_KEY` is a credential.** It is read only from the prompt or the environment (never a
52
+ command-line flag, which would persist in shell history), is written only to your local `.env`,
53
+ is never printed back, and the new project's `.gitignore` is updated so you do not commit it.
60
54
  - **No telemetry.** The CLI collects nothing and phones home about nothing. There is no usage
61
55
  analytics and nothing to opt out of.
62
56
  - **Refuses to overwrite.** Scaffolding into a directory that already contains files is refused
63
57
  outright — your existing work is never clobbered. Pick a fresh directory name.
64
58
  - **Scoped package name.** The published name is scoped, which is why the bare `npm create
65
59
  rakomi-app` form does not resolve; use the two supported invocations above.
66
-
67
- ## Data handling
68
-
69
- This tool **stores nothing and transmits nothing**. The only data involved is the values you type,
70
- written to your own local `.env` file on your own machine — exporting or deleting that data is
71
- simply reading or removing that file. Adoption is observable only through public package-registry
72
- download counts, which are first-party to the registry, not to this tool.
73
-
74
- ## Continuity
75
-
76
- The package, its publishing credentials, and the shared template registry are company-held with a
77
- two-person backup, and the package is published by the release pipeline. The pipeline performs the
78
- public release once the public package registry serves the Rakomi SDK and the template repositories
79
- are public.
60
+ - **Stores and transmits nothing else.** The only data involved is the values you type, written to
61
+ your own local `.env` file on your own machine — exporting or deleting that data is simply
62
+ reading or removing that file.
63
+ - The template list above is generated from a shared quickstart registry; a new quickstart becomes
64
+ available here automatically once it is published.
80
65
 
81
66
  ## License
82
67
 
package/SECURITY.md ADDED
@@ -0,0 +1,206 @@
1
+ # Security Policy
2
+
3
+ ## Supported Versions
4
+
5
+ This policy covers the four JS-family SDK packages: `@rakomi/node`, `@rakomi/sdk-core`,
6
+ `@rakomi/react`, and `@rakomi/react-native`.
7
+
8
+ While these packages remain pre-1.0 (`0.x`), they carry **no stability or support guarantee**
9
+ (SemVer 2.0.0 §4); the latest `0.x` line receives security updates on a best-effort basis.
10
+
11
+ From version **1.0** onward, Rakomi maintains the current (N) and previous (N-1) MAJOR in parallel, with N-1 receiving
12
+ security-only fixes. The CRA support period for each MAJOR is determined in accordance with
13
+ **CRA Art. 13(8)** — at least five years, or the product's expected use time where shorter. The authoritative, machine-readable support windows are published at
14
+ [`https://api.rakomi.com/.well-known/sdk-support.json`](https://api.rakomi.com/.well-known/sdk-support.json)
15
+ and rendered for humans on the [SDK Support & Lifecycle page](https://rakomi.com/sdk-support). This
16
+ document points at that single source rather than re-typing dated rows.
17
+
18
+ Vulnerabilities in peer dependencies (e.g., React) are out of Rakomi's direct scope, but Rakomi will update minimum peer dependency versions when a peer dependency has a known critical CVE affecting SDK users.
19
+
20
+ ## Reporting a Vulnerability
21
+
22
+ **Please do NOT open a public GitHub issue for security vulnerabilities.**
23
+
24
+ We support two reporting channels:
25
+
26
+ 1. **Email:** security@rakomi.com (preferred for initial contact)
27
+ 2. **GitHub Private Vulnerability Reporting:** [Submit via GitHub Security](https://github.com/rakomidev/rakomi-js/security/advisories/new) — each report automatically receives a GHSA tracking identifier (GHSA-xxxx-xxxx-xxxx).
28
+
29
+ For encrypted communication, a PGP public key is available at:
30
+ `https://rakomi.com/rakomi-security-pgp-key.asc`
31
+
32
+ Include the key fingerprint from this file (section 14) for out-of-band verification when sending encrypted reports.
33
+
34
+ ## Response Targets
35
+
36
+ We strive to meet these response targets. Actual response times may vary based on issue complexity and team availability.
37
+
38
+ | Severity | First Response | Fix Target | Disclosure |
39
+ |----------|---------------|------------|------------|
40
+ | Critical (actively exploited) | without undue delay (target: 24h) | 72h patch/mitigation | Designated EU authority notified within 24h (CRA Art. 14) |
41
+ | High | without undue delay (target: 48h) | 14 days | Coordinated after fix |
42
+ | Medium | 5 business days | 90 days | Coordinated after fix |
43
+ | Low | 5 business days | Next release cycle | Changelog note |
44
+
45
+ Severity is assessed using industry-standard vulnerability scoring criteria.
46
+
47
+ Rakomi is maintained by a small team. During periods of reduced availability, the auto-reply from security@rakomi.com will confirm receipt and provide the PGP key. For actively exploited vulnerabilities, we will respond as quickly as humanly possible.
48
+
49
+ ## Coordinated Vulnerability Disclosure (CVD) Policy
50
+
51
+ We follow a coordinated disclosure model with a **90-day embargo** period from the date of acknowledgment. During this time:
52
+
53
+ - Rakomi will work to develop and release a fix
54
+ - We will keep the reporter updated on progress at least every 7 business days for Critical/High severity issues
55
+ - We will notify the reporter when a fix is released
56
+ - After 90 days, we will publish a security advisory regardless of fix status
57
+
58
+ We may request an extension if the fix requires significant infrastructure changes, and we will coordinate with the reporter before any deadline extension.
59
+
60
+ ## Safe Harbor
61
+
62
+ We will not pursue legal action against researchers who follow this disclosure policy and act in good faith. We consider security research conducted in accordance with this policy to be:
63
+ - Conducted lawfully and in good faith under applicable EU law
64
+ - Not subject to legal action by CRE8EVE Sp. z o.o.
65
+
66
+ Safe harbor **does not extend to**:
67
+ - Accessing or modifying other users' data
68
+ - Performing denial of service attacks
69
+ - Social engineering employees or users
70
+ - Exfiltrating data beyond what is necessary to demonstrate the vulnerability
71
+ - Any activity that violates applicable law
72
+
73
+ ## Scope
74
+
75
+ **In scope:**
76
+ - `@rakomi/node`, `@rakomi/sdk-core`, `@rakomi/react`, and `@rakomi/react-native` SDK source code and published npm packages
77
+ - Security properties of API interactions initiated by the SDKs (request signing, token verification, credential handling)
78
+ - Authentication flow logic within the SDKs
79
+
80
+ **Out of scope:**
81
+ - Social engineering attacks against Rakomi employees or users
82
+ - Denial of service attacks
83
+ - Physical security
84
+ - Vulnerabilities in third-party services used by Rakomi's backend
85
+ - Vulnerabilities in peer dependencies (e.g., React) — reported to the relevant maintainer, but Rakomi will update minimum peer dependency versions when a peer dependency has a known critical CVE affecting SDK users
86
+
87
+ This policy applies to the official `@rakomi/node`, `@rakomi/sdk-core`, `@rakomi/react`, and `@rakomi/react-native` packages distributed via npmjs.com. Forks and derivatives are maintained by their respective authors. Customers in regulated sectors (healthcare, finance) may have additional notification obligations beyond this general policy — contact security@rakomi.com for sector-specific compliance documentation.
88
+
89
+ ## EU Authority Reporting
90
+
91
+ It is our **policy** to report actively exploited vulnerabilities and severe security incidents
92
+ having an impact on the security of our products to the relevant EU authority in accordance with
93
+ **CRA Art. 14**, on the statutory timeline: an early warning, followed by a fuller notification, and
94
+ a final report.
95
+
96
+ We report to the national coordinator CSIRT designated for our Member State of main establishment
97
+ (Poland), which is our live reporting channel today; onboarding to the EU single reporting platform
98
+ is in progress, and that platform is the documented onward path as it becomes available to
99
+ manufacturers.
100
+
101
+ ## Security Update Notifications
102
+
103
+ Consumers of Rakomi SDKs can receive security update notifications through:
104
+ - **GitHub Security Advisories** on this repository (subscribe via GitHub "Watch" → "Security alerts")
105
+ - **npm audit:** `npm audit` or `pnpm audit` will flag known vulnerabilities in installed versions
106
+
107
+ In accordance with CRA Art. 14(8), after becoming aware of an actively exploited vulnerability or a severe incident having an impact on the security of our products, we will inform impacted users (and, where appropriate, all users) — together with any available risk-mitigation or corrective measures — through the above channels.
108
+
109
+ ## Manufacturer Identification
110
+
111
+ **Legal entity:** CRE8EVE Sp. z o.o.
112
+ **Registered address:** Tulipanowa 4, 72-003 Dobra, Poland (EU)
113
+ **Contact:** security@rakomi.com (role-based — no personal mailbox or phone is published)
114
+ **Products covered:** `@rakomi/node`, `@rakomi/sdk-core`, `@rakomi/react`, `@rakomi/react-native` (published on npmjs.com)
115
+
116
+ The manufacturer is itself EU-established (Poland), so no CRA Art. 18 Authorised Representative is required (Art. 18 applies to manufacturers established outside the Union). CRA conformity assessment (Art. 32): these are Class I important products (Annex III). The internal-control procedure (Annex VIII, Module A — manufacturer self-assessment, no notified-body involvement) is available for a Class I product **only where harmonised standards, common specifications, or a European cybersecurity certification scheme at assurance level at least 'substantial' are applied in full** (Art. 32(2)); otherwise a third-party route — EU-type examination plus conformity to type (modules B+C), or full quality assurance (module H) — is required. The applicable route will be confirmed against the harmonised standards in force at the CRA application date. The EU Declaration of Conformity and CE marking attach at the CRA application date (Dec 2027) and are not yet issued. See the full manufacturer record at https://docs.rakomi.dev/compliance/manufacturer/.
117
+
118
+ ## Export Control / Cryptography Notice
119
+
120
+ The four `@rakomi/*` SDK packages incorporate and invoke cryptography — they verify asymmetric
121
+ digital signatures (JWT/token verification), compare key material in constant time, and rely on the
122
+ host platform's TLS for transport security. They are distributed as **publicly available, mass-market
123
+ software** with cryptographic functionality the end user cannot readily modify.
124
+
125
+ - **EU — Regulation (EU) 2021/821 (Dual-Use):** the SDKs qualify for the **mass-market** treatment
126
+ under the Cryptography Note (Note 3) to Category 5, Part 2 of Annex I — generally available to the
127
+ public, sold without restriction, and not designed for the user to alter the cryptographic
128
+ functionality. No export authorisation is required for their distribution within or from the EU, and —
129
+ unlike the US path — the EU decontrol is **self-executing**, with no notification or filing step.
130
+ - **US — Export Administration Regulations (EAR):** the cryptographic functionality is classifiable
131
+ under **ECCN 5D002**. As **publicly available** open-source software the source code is **not
132
+ subject to the EAR** (15 CFR §734.7(a)), and the corresponding object code is distributed under the
133
+ mass-market provisions. The one-time email notification of the public source-code URL to the U.S.
134
+ BIS and NSA is filed at first public release (15 CFR §742.15(b)).
135
+
136
+ This notice is provided for transparency and is **not legal advice**. Downstream redistributors are
137
+ responsible for their own export, import, and use obligations in their jurisdiction.
138
+
139
+ ## Post-Market Surveillance
140
+
141
+ Rakomi monitors SDK health after release through:
142
+ - Automated dependency vulnerability scanning (npm audit, Dependabot)
143
+ - Runtime error patterns derived from API logs (SDK version reported in User-Agent header)
144
+ - Periodic security review of SDK code per the internal security review process
145
+
146
+ This constitutes the "effective and regular tests and reviews of the security of the product with digital elements" required under CRA Annex I, Part II, point (3). The coordinated-vulnerability-disclosure policy required under CRA Annex I, Part II, point (5) is set out in the "Coordinated Vulnerability Disclosure (CVD) Policy" section above.
147
+
148
+ ## No Bounty Program
149
+
150
+ Rakomi does not currently operate a paid bug bounty program. We deeply appreciate responsible disclosure and will acknowledge researchers in security advisories (with their consent).
151
+
152
+ ## Reference: security.txt
153
+
154
+ This policy is referenced in our machine-readable security contact file (RFC 9116):
155
+ `/.well-known/security.txt` — deployed at `https://rakomi.com/.well-known/security.txt`
156
+
157
+ ---
158
+
159
+ ## What to Include in Your Report
160
+
161
+ *(ISO/IEC 29147:2018 §6.5)*
162
+
163
+ To help us triage efficiently, please include:
164
+
165
+ 1. **Affected package** name and version (e.g., @rakomi/node 0.2.0)
166
+ 2. **Reproduction steps** — a minimal, reproducible example
167
+ 3. **Impact assessment** — what an attacker could achieve
168
+ 4. **Proof of concept** — if available (do not use real user data)
169
+ 5. **Reporter contact** — so we can keep you updated
170
+
171
+ Reports that do not include reproduction steps or fall outside the defined scope may be closed without a tracking ID.
172
+
173
+ ## Report Tracking
174
+
175
+ *(ISO/IEC 29147:2018 §6.6)*
176
+
177
+ Each report receives a unique tracking identifier upon acknowledgment. For reports submitted via GitHub Private Vulnerability Reporting, the GHSA identifier (e.g., GHSA-xxxx-xxxx-xxxx) serves as the tracking ID. For email reports, we will direct you to also submit via GitHub PVR for formal tracking.
178
+
179
+ ## Status Updates
180
+
181
+ *(ISO/IEC 29147:2018 §6.4)*
182
+
183
+ We provide status updates at least every **7 business days** for Critical/High severity issues, and upon resolution for Medium/Low severity issues.
184
+
185
+ ## CVE Assignment
186
+
187
+ Confirmed vulnerabilities with sufficient impact will receive CVE identifiers via GitHub's CNA (CVE Numbering Authority) program.
188
+
189
+ ## Reporter Data Privacy
190
+
191
+ *(GDPR Art. 6(1)(f) + Art. 13/14)*
192
+
193
+ Reporter personal data (name, email) is processed under GDPR Art. 6(1)(f) legitimate interest for vulnerability coordination. This data is:
194
+ - Retained for the duration of the vulnerability lifecycle plus 2 years
195
+ - Not shared with third parties except as required for CVE assignment or regulatory reporting (e.g., ENISA, national CSIRT)
196
+ - Accessible to the reporter upon request (GDPR Art. 15)
197
+ - Deletable upon request after vulnerability closure (GDPR Art. 17, where not overridden by regulatory retention obligations)
198
+
199
+ To exercise your GDPR rights, contact security@rakomi.com.
200
+
201
+ ## PGP Key Fingerprint
202
+
203
+ The PGP public key for encrypted communication is available at:
204
+ `https://rakomi.com/rakomi-security-pgp-key.asc`
205
+
206
+ Verify the key fingerprint through an independent channel (e.g., LinkedIn, Twitter/X, or a direct phone call) before sending sensitive information.
package/dist/env.js CHANGED
@@ -7,8 +7,6 @@ export const ENV_KEYS = ['RAKOMI_REGION', 'RAKOMI_TENANT_ID', 'RAKOMI_API_KEY'];
7
7
  export const DEFAULT_REGION = 'eu-central';
8
8
  /** Keys whose value is a credential and must never be echoed to stdout / logs / summaries. */
9
9
  export const SECRET_KEYS = new Set(['RAKOMI_API_KEY']);
10
- // Matches every ASCII control character (0x00-0x1F and 0x7F), including CR and LF.
11
- // eslint-disable-next-line no-control-regex -- deliberately strips ALL control chars from values
12
10
  const CONTROL_CHARS = /[\u0000-\u001F\u007F]/g;
13
11
  /**
14
12
  * Serialise a single `KEY=value` line in canonical dotenv form:
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  // SPDX-License-Identifier: MIT
3
- import { existsSync, readFileSync } from 'node:fs';
3
+ import { readFileSync } from 'node:fs';
4
4
  import { appendFile, readFile } from 'node:fs/promises';
5
5
  import { isAbsolute, join, relative, resolve } from 'node:path';
6
6
  import process from 'node:process';
@@ -30,7 +30,6 @@ export async function run(args, deps) {
30
30
  deps.stderr.write(`${e.message}\n`);
31
31
  return e.exitCode;
32
32
  }
33
- // Unknown error — never leak a stack trace, path, or version.
34
33
  deps.stderr.write('An unexpected error occurred.\n');
35
34
  return EXIT.FAIL;
36
35
  }
@@ -48,6 +47,7 @@ async function dispatch(args, deps) {
48
47
  'tenant-id': { type: 'string' },
49
48
  'template-source': { type: 'string' },
50
49
  yes: { type: 'boolean' },
50
+ connect: { type: 'boolean' },
51
51
  help: { type: 'boolean', short: 'h' },
52
52
  version: { type: 'boolean', short: 'V' },
53
53
  },
@@ -58,12 +58,10 @@ async function dispatch(args, deps) {
58
58
  catch {
59
59
  throw new UsageError('Unknown or malformed argument.');
60
60
  }
61
- // --help / -h / bare invocation → usage to stdout, exit 0.
62
61
  if (values.help === true || args.length === 0) {
63
62
  deps.stdout.write(helpText());
64
63
  return EXIT.OK;
65
64
  }
66
- // --version / -V → version to stdout, exit 0.
67
65
  if (values.version === true) {
68
66
  deps.stdout.write(`${deps.version}\n`);
69
67
  return EXIT.OK;
@@ -72,7 +70,6 @@ async function dispatch(args, deps) {
72
70
  if (!slug) {
73
71
  throw new UsageError(`Missing --template. Valid templates: ${slugList()}.`);
74
72
  }
75
- // Allow-list membership check — the slug is never sanitised or used in a path/URL before this.
76
73
  const template = findTemplate(slug);
77
74
  if (!template) {
78
75
  throw new UsageError(`Unknown template "${slug}". Valid templates: ${slugList()}.`);
@@ -80,7 +77,6 @@ async function dispatch(args, deps) {
80
77
  const rawTarget = positionals[0] ?? template.slug;
81
78
  const targetDir = resolveTarget(rawTarget, deps.cwd);
82
79
  await assertTargetWritable(targetDir);
83
- // Resolve env values by precedence: flag > env var > prompt > default.
84
80
  const flags = {};
85
81
  if (typeof values.region === 'string')
86
82
  flags.RAKOMI_REGION = values.region;
@@ -88,19 +84,28 @@ async function dispatch(args, deps) {
88
84
  flags.RAKOMI_TENANT_ID = values['tenant-id'];
89
85
  const interactive = deps.isTTY && values.yes !== true && !deps.env.CI;
90
86
  const envValues = await collectEnv({ flags, env: deps.env, interactive, ask: deps.ask });
91
- // Fetch + extract the template (safe, atomic) then write the local .env.
92
87
  const source = deps.source ?? makeDefaultSource(values, deps.env);
93
88
  const archive = await source.fetchArchive(template);
94
89
  await materializeArchive(archive, targetDir);
95
- // Git-ignore `.env` BEFORE writing the secret, so a failure between the two steps never leaves an
96
- // un-ignored secret on disk. The two calls touch independent files (.gitignore vs .env), so the
97
- // order is otherwise free.
98
90
  await ensureEnvIgnored(targetDir);
99
91
  await writeEnvFile(targetDir, envValues);
100
92
  const pm = detectPackageManager(deps.env.npm_config_user_agent);
101
93
  deps.stdout.write(postInstallMessage(template.slug, rawTarget, pm));
94
+ if (values.connect === true) {
95
+ deps.stdout.write(connectInstructions());
96
+ }
102
97
  return EXIT.OK;
103
98
  }
99
+ /** `--connect` next-step copy: how to connect an AI agent once the scaffolded app is set up. */
100
+ export function connectInstructions() {
101
+ return [
102
+ '',
103
+ 'Connect an AI agent (Claude Code, Claude Desktop) to this tenant:',
104
+ ' npx rakomi login',
105
+ ' npx rakomi connect',
106
+ '',
107
+ ].join('\n');
108
+ }
104
109
  function makeDefaultSource(values, env) {
105
110
  const base = (typeof values['template-source'] === 'string' ? values['template-source'] : undefined) ??
106
111
  env.CREATE_RAKOMI_TEMPLATE_BASE;
@@ -119,8 +124,13 @@ function resolveTarget(raw, cwd) {
119
124
  async function ensureEnvIgnored(targetDir) {
120
125
  const gitignore = join(targetDir, '.gitignore');
121
126
  let content = '';
122
- if (existsSync(gitignore))
127
+ try {
123
128
  content = await readFile(gitignore, 'utf8');
129
+ }
130
+ catch (err) {
131
+ if (err.code !== 'ENOENT')
132
+ throw err;
133
+ }
124
134
  const alreadyIgnored = content.split(/\r?\n/).some((line) => line.trim() === '.env');
125
135
  if (alreadyIgnored)
126
136
  return;
@@ -139,15 +149,10 @@ async function main() {
139
149
  version = String(JSON.parse(readFileSync(new URL('../package.json', import.meta.url), 'utf8')).version);
140
150
  }
141
151
  catch {
142
- // Fall back to a placeholder version rather than crash.
143
152
  }
144
153
  process.exitCode = await run(process.argv.slice(2), {
145
154
  stdout: { write: (text) => void process.stdout.write(text) },
146
155
  stderr: { write: (text) => void process.stderr.write(text) },
147
- // A standalone published CLI reads the real environment at its entry point; there is no
148
- // app config.ts to route through (that pattern is for the server). The whole env object is
149
- // handed to the injectable `run`, which is what the test suite exercises.
150
- // eslint-disable-next-line no-restricted-syntax -- CLI entry must read process.env directly
151
156
  env: process.env,
152
157
  cwd: process.cwd(),
153
158
  version,
package/dist/prompt.js CHANGED
@@ -31,7 +31,6 @@ export async function collectEnv(deps) {
31
31
  out[field.key] = answer !== '' ? answer : (field.defaultValue ?? '');
32
32
  continue;
33
33
  }
34
- // Non-interactive: use the default if any, otherwise leave the key for the user to fill.
35
34
  if (field.defaultValue !== undefined)
36
35
  out[field.key] = field.defaultValue;
37
36
  }
package/dist/source.js CHANGED
@@ -5,7 +5,6 @@ import { dirname, isAbsolute, join, relative, resolve } from 'node:path';
5
5
  import { createGunzip } from 'node:zlib';
6
6
  import { CliError, EXIT, UsageError } from './errors.js';
7
7
  import { archiveUrl, DEFAULT_TEMPLATE_BASE } from './templates.js';
8
- // --- Safety caps (constants, not configurable — a hardened scaffolder, not a general extractor) ---
9
8
  /** Max compressed bytes downloaded before aborting the stream. */
10
9
  export const COMPRESSED_CAP = 50 * 1024 * 1024;
11
10
  /** Max total decompressed bytes — aborts mid-inflate to defeat a decompression bomb. */
@@ -74,7 +73,6 @@ export class GithubCodeloadSource {
74
73
  }
75
74
  if (e instanceof CliError)
76
75
  throw e;
77
- // An unexpected network error (reset, DNS) — retryable, user-safe message.
78
76
  lastError = new FetchError('Network error while downloading the template.', true);
79
77
  if (attempt < this.maxRetries) {
80
78
  await delay(this.backoffMs * (attempt + 1));
@@ -92,7 +90,6 @@ export class GithubCodeloadSource {
92
90
  response = await this.fetchImpl(url, { redirect: 'error', signal: controller.signal });
93
91
  }
94
92
  catch {
95
- // Aborted (timeout) or a transport error — both retryable.
96
93
  throw new FetchError('Timed out or failed while downloading the template.', true);
97
94
  }
98
95
  finally {
@@ -210,23 +207,20 @@ export function parseTar(buf) {
210
207
  while (offset + 512 <= buf.length) {
211
208
  const block = buf.subarray(offset, offset + 512);
212
209
  if (block.every((b) => b === 0))
213
- break; // end-of-archive marker
210
+ break;
214
211
  const name = readCString(block, 0, 100);
215
212
  const size = parseOctal(block, 124, 12);
216
213
  const typeflag = String.fromCharCode(block[156] ?? 0);
217
214
  const prefix = readCString(block, 345, 155);
218
215
  offset += 512;
219
216
  const dataBlocks = Math.ceil(size / 512);
220
- // GNU/pax metadata entries — skip their payload, materialise nothing.
221
217
  if (typeflag === 'x' || typeflag === 'g' || typeflag === 'L' || typeflag === 'K') {
222
218
  offset += dataBlocks * 512;
223
219
  continue;
224
220
  }
225
- // Reject symlink / hardlink (CWE-59) — independent of the traversal check below.
226
221
  if (typeflag === '1' || typeflag === '2') {
227
222
  throw new CliError('The template archive contains a link entry; refusing for safety.', EXIT.FAIL);
228
223
  }
229
- // Reject character/block device and FIFO entries.
230
224
  if (typeflag === '3' || typeflag === '4' || typeflag === '6') {
231
225
  throw new CliError('The template archive contains a special-file entry; refusing for safety.', EXIT.FAIL);
232
226
  }
@@ -241,7 +235,6 @@ export function parseTar(buf) {
241
235
  entries.push({ type: 'dir', path: fullName });
242
236
  }
243
237
  else {
244
- // '0' or NUL typeflag → regular file.
245
238
  entries.push({ type: 'file', path: fullName, data: Buffer.from(buf.subarray(offset, offset + size)) });
246
239
  }
247
240
  offset += dataBlocks * 512;
@@ -267,7 +260,6 @@ export async function materializeArchive(gzipBytes, targetDir) {
267
260
  if (entries.length === 0) {
268
261
  throw new CliError('The template archive is empty; refusing.', EXIT.FAIL);
269
262
  }
270
- // The codeload tarball wraps everything in a single leading `<repo>-<sha>/` dir.
271
263
  const wrapper = normalizeEntryPath(entries[0].path).split('/')[0] ?? '';
272
264
  const target = resolve(targetDir);
273
265
  const parent = dirname(target);
@@ -276,14 +268,12 @@ export async function materializeArchive(gzipBytes, targetDir) {
276
268
  try {
277
269
  for (const entry of entries) {
278
270
  const norm = normalizeEntryPath(entry.path);
279
- // Every entry must live under the detected wrapper dir; anything else is an escape attempt
280
- // (absolute path, sibling-of-wrapper, escape-after-strip-1).
281
271
  if (norm !== wrapper && !norm.startsWith(`${wrapper}/`)) {
282
272
  throw new CliError('A template archive entry escapes the template root; refusing.', EXIT.FAIL);
283
273
  }
284
274
  const stripped = norm === wrapper ? '' : norm.slice(wrapper.length + 1);
285
275
  if (stripped === '')
286
- continue; // the wrapper dir itself
276
+ continue;
287
277
  const dest = resolve(tmpDir, stripped);
288
278
  const rel = relative(tmpDir, dest);
289
279
  if (rel === '' || rel.startsWith('..') || isAbsolute(rel)) {
@@ -305,7 +295,6 @@ export async function materializeArchive(gzipBytes, targetDir) {
305
295
  }
306
296
  }
307
297
  async function moveIntoPlace(tmpDir, target) {
308
- // The clobber check guarantees `target` is empty or absent before we get here.
309
298
  if (existsSync(target)) {
310
299
  await rmdir(target);
311
300
  }
@@ -1,8 +1,4 @@
1
1
  // SPDX-License-Identifier: MIT
2
- //
3
- // GENERATED FILE — do not edit by hand. Regenerate with `node scripts/gen-templates.mjs`
4
- // (runs automatically on build/typecheck). The data mirrors the shared quickstart manifest;
5
- // the parity test asserts they stay in sync.
6
2
  /** The full set of templates, in manifest order. */
7
3
  export const TEMPLATES = [
8
4
  { slug: "nextjs", label: "Next.js quickstart", publicRepo: "rakomidev/rakomi-nextjs-quickstart" },
package/dist/templates.js CHANGED
@@ -1,7 +1,7 @@
1
1
  // SPDX-License-Identifier: MIT
2
2
  import { TEMPLATES } from './templates.generated.js';
3
3
  /** The public portal host where each quickstart's walkthrough lives (post-install URL). */
4
- export const PORTAL_HOST = 'examples.rakomi.com';
4
+ export const PORTAL_HOST = 'examples.rakomi.dev';
5
5
  /**
6
6
  * The default base for fetching template archives. Overridable at runtime via
7
7
  * `--template-source` / `CREATE_RAKOMI_TEMPLATE_BASE` (mirror, offline, or emergency
@@ -39,6 +39,9 @@ export function portalUrl(slug) {
39
39
  * Pinned to the `main` branch ref (a future enhancement could pin a tagged ref per template).
40
40
  */
41
41
  export function archiveUrl(template, base = DEFAULT_TEMPLATE_BASE) {
42
- const trimmed = base.replace(/\/+$/, '');
42
+ let end = base.length;
43
+ while (end > 0 && base.charCodeAt(end - 1) === 47)
44
+ end -= 1;
45
+ const trimmed = base.slice(0, end);
43
46
  return `${trimmed}/${template.publicRepo}/tar.gz/refs/heads/main`;
44
47
  }
package/dist/usage.js CHANGED
@@ -38,6 +38,7 @@ export function helpText() {
38
38
  ' --tenant-id <value> your tenant id',
39
39
  ' --template-source <url> override the archive base (mirror / offline)',
40
40
  ' --yes accept defaults, never prompt (non-interactive)',
41
+ ' --connect print next steps for connecting an AI agent (the rakomi CLI)',
41
42
  ' -h, --help show this help and exit',
42
43
  ' -V, --version print the version and exit',
43
44
  '',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-rakomi-app",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "Scaffold a Rakomi quickstart app. EU-native auth-as-a-service. npx create-rakomi-app --template <slug>",
5
5
  "keywords": [
6
6
  "rakomi",
@@ -35,16 +35,13 @@
35
35
  "files": [
36
36
  "dist",
37
37
  "README.md",
38
- "LICENSE"
38
+ "LICENSE",
39
+ "SECURITY.md",
40
+ "sbom.cdx.json"
39
41
  ],
40
- "devDependencies": {
41
- "@types/node": "22.19.13",
42
- "typescript": "5.9.3",
43
- "vitest": "4.0.18"
44
- },
45
42
  "scripts": {
46
- "build": "node scripts/gen-templates.mjs && tsc",
47
- "typecheck": "node scripts/gen-templates.mjs && tsc --noEmit",
43
+ "build": "tsc",
44
+ "typecheck": "tsc --noEmit",
48
45
  "lint": "eslint src/ test/ --max-warnings=0",
49
46
  "test": "vitest run"
50
47
  }
package/sbom.cdx.json ADDED
@@ -0,0 +1,43 @@
1
+ {
2
+ "bomFormat": "CycloneDX",
3
+ "specVersion": "1.6",
4
+ "version": 1,
5
+ "metadata": {
6
+ "supplier": {
7
+ "name": "CRE8EVE Sp. z o.o.",
8
+ "url": [
9
+ "https://rakomi.com"
10
+ ]
11
+ },
12
+ "authors": [
13
+ {
14
+ "name": "CRE8EVE Sp. z o.o."
15
+ }
16
+ ],
17
+ "tools": {
18
+ "components": [
19
+ {
20
+ "type": "application",
21
+ "group": "rakomi",
22
+ "name": "generate-sbom",
23
+ "version": "sha256:ffabbf3f1638"
24
+ }
25
+ ]
26
+ },
27
+ "component": {
28
+ "type": "library",
29
+ "bom-ref": "pkg:npm/create-rakomi-app@0.1.1",
30
+ "name": "create-rakomi-app",
31
+ "version": "0.1.1",
32
+ "purl": "pkg:npm/create-rakomi-app@0.1.1",
33
+ "licenses": [
34
+ {
35
+ "license": {
36
+ "name": "NOASSERTION"
37
+ }
38
+ }
39
+ ]
40
+ }
41
+ },
42
+ "components": []
43
+ }