diff --git a/DOCS.md b/DOCS.md index 5515c5b..c43c6a4 100644 --- a/DOCS.md +++ b/DOCS.md @@ -99,9 +99,9 @@ Signing in is **optional** and is the first question of the installer The decision tree on every run: -1. `CREATE_IAI_TOKEN` set (CI/automation) → verified; an invalid token **fails +1. `IMPACTUS_TOKEN` set (CI/automation) → verified; an invalid token **fails loudly** (automation never silently degrades to a harness-only install). -2. A saved token exists (`~/.create-iai/auth.json`) → verified. Valid → +2. A saved token exists (`~/.impactus-cli/auth.json`) → verified. Valid → "Welcome back" and no questions. Expired/revoked (401) → cleared, the offer below includes a fresh login. Community unreachable (5xx/network) → the run exits WITHOUT deleting the credential ("your login is still saved"). @@ -116,7 +116,7 @@ The decision tree on every run: calls `POST /api/cli/device/start` on the community API, opens the browser at the community's `/cli` page with a user code, and polls `POST /api/cli/device/poll` until approved (10-minute deadline, `slow_down` -honored). The token is saved to `~/.create-iai/auth.json` (mode **600**) with +honored). The token is saved to `~/.impactus-cli/auth.json` (mode **600**) with the API base and a device label (` ()`) shown in the student's token list. During an install, a DENIED authorization falls back to guest mode (the run continues); `--login` standalone still exits 1. @@ -135,7 +135,9 @@ npx impactus --whoami # "Authenticated as — subscription active ✓" npx impactus --logout # revokes server-side (best-effort) + deletes auth.json ``` -State on disk — `~/.create-iai/` (dir mode 700): +State on disk — `~/.impactus-cli/` (dir mode 700; a pre-rebrand +`~/.create-iai/` found on the machine is RENAMED to this on first touch, so +the login, keys and logs survive — `src/lib/state-dir.js`): | Path | What | | --- | --- | @@ -143,7 +145,7 @@ State on disk — `~/.create-iai/` (dir mode 700): | `keys/.env` | Service keys pasted in the web UI (`--keys` reads them; mode 600, machine-local only). | | `logs/run-.log` | Full log of each installer run (secrets redacted) — what students attach to bug reports. | -Dev/testing: `--api ` or `CREATE_IAI_API` point the CLI at another +Dev/testing: `--api ` or `IMPACTUS_API` point the CLI at another community deployment; a custom base prints a one-time warning (the saved token is sent to that host). @@ -229,7 +231,7 @@ template declares them in `requires` — see §4.4 and `src/lib/pipeline.js`). | 10 | Impeccable — design skill | `steps/impeccable.js` | Optional, default on | | 11 | Final summary | `steps/finish.js` | Harness next steps (no `npm run dev`) | -Logs of each run: `~/.create-iai/logs/run-.log`. +Logs of each run: `~/.impactus-cli/logs/run-.log`. ### 3.3 The stack catalog and the layer-by-layer wizard @@ -395,7 +397,7 @@ Step behaviors worth knowing: is backed up/restored (the registry version fails the template's eslint and would block every commit). A warn-only `npx tsc --noEmit` closes the step. - **Convex** — two phases. `setupConvex` logs in - (`npx convex login --device-name "create-iai installer"`) and provisions + (`npx convex login --device-name "impactus installer"`) and provisions with `npx convex dev --once --configure=new --project --dev-deployment cloud --tail-logs disable`; success is judged by the env vars, NOT the exit code — the very first push fails ON PURPOSE @@ -422,7 +424,7 @@ Step behaviors worth knowing: (`https://.convex.site/clerk-users-webhook`, events `user.created`, `user.updated`, `user.deleted`), captures the `whsec_` secret (written to `.env.local` FIRST, then `npx convex env set CLERK_WEBHOOK_SECRET`). -- **GitHub** — commit `feat: project configured by create-iai (Convex + +- **GitHub** — commit `feat: project configured by impactus (Convex + Clerk + shadcn/ui)`; a lefthook/eslint rejection shows the lint output and the documented escape hatch `LEFTHOOK=0 git commit -m "initial setup"` (never misblamed on git identity). Push runs `gh repo create @@ -509,14 +511,14 @@ The generated project gets `imp/iai.config.json` (older versions kept it at the root): ```json -{ "createdWith": "create-iai", "addons": ["analyzer", "commitlint", "csp", "knip", "rate-limit", "sentry"] } +{ "createdWith": "impactus", "addons": ["analyzer", "commitlint", "csp", "knip", "rate-limit", "sentry"] } ``` and the manifest is removed. `npm install` runs only after the pruning (never downloads what was cut) and `convex dev --once` (the Convex step) regenerates `convex/_generated` for the reduced set. After the install the CLI runs `npx prettier --write .` and amends the initial commit -(`chore: initial template (create-iai)`, with `LEFTHOOK=0` so the project's +(`chore: initial template (impactus)`, with `LEFTHOOK=0` so the project's fresh git hooks don't fire on the internal amend) — stripping blocks can leave formatting Prettier would rewrite, and the amend keeps `format:check` green in the generated project's CI. `.env.local` is seeded from the already-pruned @@ -555,9 +557,11 @@ allowlist (`cli-paid-gate.md`, private `impactus-internal-docs` repo) + ### 4.5 Crash recovery: the state marker, resume and rollback -- `installTemplate` writes `.create-iai-state.json` into the target FIRST +- `installTemplate` writes `.impactus-cli-state.json` into the target FIRST (`{ version, startedAt, mode }`); the finish step removes it. A leftover - marker therefore means a full install died midway. + marker therefore means a full install died midway. Installs started by a + pre-rebrand CLI wrote `.create-iai-state.json` — readers accept both names + (`hasStateMarker`) and the finish step removes both. - A folder with the marker is a **resume**, never an "existing project": re-running the installer there warns, recommends continuing the full install (the pipeline is idempotent — it reuses the folder, `.env.local` @@ -688,7 +692,7 @@ regex) and the AI prompt steps. Three consumers: e.g. Claude in Chrome — the agent finds/creates the keys in the dashboard and returns `KEY=value` lines), a paste box that fills the fields by itself, and regex-validated fields. Pasted keys are saved to - `~/.create-iai/keys/.env` (permission **600**, machine-local only) + `~/.impactus-cli/keys/.env` (permission **600**, machine-local only) and the generated command references the path via `--keys` — no secret ever appears in the command/history. 2. **Terminal (`steps/service-keys.js`)** — same flow without the UI: per @@ -1775,8 +1779,7 @@ happen in the browser: at the end the page highlights the ready `npx impactus …` command; the student copies it and runs it in the terminal (an "Open the terminal for me" button opens the OS terminal app). The terminal wizard remains the default entry point — `--ui` is opt-in; -`--terminal`/`--no-ui` are accepted for compatibility. The page still ships -with the historical `create-iai` branding (title/header). +`--terminal`/`--no-ui` are accepted for compatibility. What the page offers, top to bottom: @@ -1800,11 +1803,11 @@ What the page offers, top to bottom: paste it into a browser-automation agent, e.g. Claude in Chrome), a paste box that parses the returned `KEY=value` block and fills the regex-validated fields, and a collapsed manual path with the dashboard link. Keys autosave - to `~/.create-iai/keys/.env` (mode 600, machine-local; a project + to `~/.impactus-cli/keys/.env` (mode 600, machine-local; a project rename deletes the old slug's file; the command references the path via `--keys` — no secret ever appears in the command or leaves the machine). Fully-automatic services (Clerk, Convex) are listed as "nothing to do". -6. **Sign-in** — the access bar checks `~/.create-iai/auth.json` against the +6. **Sign-in** — the access bar checks `~/.impactus-cli/auth.json` against the community API and, when needed, runs the whole device flow **server-side** (the browser only sees the code/link; the token never travels to the page). An inactive subscription is reported as such — a fresh sign-in won't fix @@ -1831,7 +1834,7 @@ npx impactus --dir /Users/ana/my-app --stack depois npx impactus --dir /Users/ana/my-app --stack frontend=nextjs,backend=convex,auth=clerk,blob=r2,automations=none,deploy=vercel # With pasted keys: -npx impactus --dir /Users/ana/my-app --mode full --template-id live1 … --keys /Users/ana/.create-iai/keys/my-app.env +npx impactus --dir /Users/ana/my-app --mode full --template-id live1 … --keys /Users/ana/.impactus-cli/keys/my-app.env ``` Endpoints, for the curious: `GET /` (the page), `GET /api/catalog`, @@ -2103,7 +2106,7 @@ imp Non-interactive full install (CI or a second machine): ```bash -CREATE_IAI_TOKEN= npx impactus my-saas --yes --preset saas --payments asaas --storage r2 --keys ~/.create-iai/keys/my-saas.env +IMPACTUS_TOKEN= npx impactus my-saas --yes --preset saas --payments asaas --storage r2 --keys ~/.impactus-cli/keys/my-saas.env ``` ### 15.3 An EXISTING web app (brownfield) @@ -2220,7 +2223,7 @@ Customization Services --keys .env file with service keys (generated by the --ui - web UI in ~/.create-iai/keys/; machine-local, mode 600) + web UI in ~/.impactus-cli/keys/; machine-local, mode 600) --tenancy single (Live 1, default) | multi (Live 2: your app's own organizations in Convex — per-organization data/billing, roles/permissions, /admin with org @@ -2251,7 +2254,7 @@ without it the installer delivers the harness + agent only — see §2) --login Authenticates this computer (browser) and exits --logout Removes/revokes the CLI token and exits --whoami Shows subscription status and exits - --api Community API base (dev/testing; or CREATE_IAI_API) + --api Community API base (dev/testing; or IMPACTUS_API) General --ui, --web Opens the local web UI to build the install command @@ -2274,7 +2277,7 @@ General configures R2 non-interactively), **harness installed**, local commit without a remote repo, no integration-CLI logins, FIA + Impeccable on. For harness only without prompts: `--harness-only` (or `--mode harness`). Without a valid -login (or `CREATE_IAI_TOKEN`), `--yes` continues as **guest** — harness + +login (or `IMPACTUS_TOKEN`), `--yes` continues as **guest** — harness + agent only — and `--mode full`/`--stack recomendada` error out instead of silently downgrading. @@ -2284,8 +2287,8 @@ Recognized by the installer: | Variable | Effect | | --- | --- | -| `CREATE_IAI_TOKEN` | CI/automation access token — the non-interactive way to run a full install. An invalid token fails loudly (never a silent guest downgrade). | -| `CREATE_IAI_API` | Community API base override (same as `--api`; a custom base warns once — the token is sent to that host). | +| `IMPACTUS_TOKEN` | CI/automation access token — the non-interactive way to run a full install. An invalid token fails loudly (never a silent guest downgrade). Legacy `CREATE_IAI_TOKEN` is still accepted. | +| `IMPACTUS_API` | Community API base override (same as `--api`; a custom base warns once — the token is sent to that host). Legacy `CREATE_IAI_API` is still accepted. | | `GH_TOKEN` / `GITHUB_TOKEN` | Accepted by the gh preflight as authentication (no `gh auth login` needed). | | `VERCEL_TOKEN` | Accepted by the vercel preflight as authentication. | | `LEFTHOOK=0` | The documented one-time escape hatch when the template's pre-commit lint blocks the initial commit. | @@ -2313,7 +2316,7 @@ Recognized by the `imp` launcher: | `PI_SKIP_VERSION_CHECK=1` | Set by imp on every passthrough — suppresses Pi's own pi-branded update notice (imp prints its own after the session). | | `IMP_SKIP_VERSION_CHECK` / `PI_OFFLINE` | Skip imp's post-session update probe entirely. | -State on disk, outside projects: `~/.create-iai/` (auth token, web-UI keys +State on disk, outside projects: `~/.impactus-cli/` (auth token, web-UI keys files, run logs — §2) and `~/.pi/agent/` (Pi's own credential store, settings and session logs; never imported from `~/.claude`/`~/.codex` — rotating refresh tokens invalidate each other). diff --git a/src/config.js b/src/config.js index f15a120..f0e9e72 100644 --- a/src/config.js +++ b/src/config.js @@ -12,19 +12,24 @@ // // `apiBase` must point to the community's PRODUCTION deployment — the same one // the www.impactus.academy site uses (verified against the production bundle). -// Override in dev/test: env CREATE_IAI_API=https://.convex.site -// or the --api flag. `checkoutUrl` is shown when access is inactive. +// Override in dev/test: env IMPACTUS_API=https://.convex.site +// (legacy CREATE_IAI_API still accepted) or the --api flag. +// `checkoutUrl` is shown when access is inactive. export const COMMUNITY = { name: 'IAI Cursos', apiBase: 'https://tidy-dodo-19.convex.site', checkoutUrl: 'https://www.impactus.academy', }; -// CLI token stored locally (~/.create-iai/auth.json, permission 600). +// CLI token stored locally (~/.impactus-cli/auth.json, permission 600). export const AUTH_FILE = 'auth.json'; -// State folder in the home directory: token, keys pasted in the UI, and run logs. -export const STATE_DIR = '.create-iai'; +// State folder in the home directory: token, keys pasted in the UI, and run +// logs. Installs made before the rebrand used `.create-iai` — +// src/lib/state-dir.js RENAMES that folder on first touch, so tokens, keys +// and logs survive without a re-login. +export const STATE_DIR = '.impactus-cli'; +export const LEGACY_STATE_DIR = '.create-iai'; // ── Template catalog ───────────────────────────────────────────────────────── // Single source of the installable templates. Downloads are ALWAYS gated: the @@ -642,7 +647,7 @@ export const SERVICES = [ prompt: { url: 'https://resend.com/api-keys', steps: [ - 'Click "Create API Key". Name: "{{PROJECT}} (create-iai)". Permission: "Sending access". Domain: All domains.', + 'Click "Create API Key". Name: "{{PROJECT}} (impactus)". Permission: "Sending access". Domain: All domains.', 'Copy the created key (starts with re_ — it is shown only ONCE).', ], rules: ['Do not delete or change existing keys.'], @@ -766,7 +771,7 @@ export const SERVICES = [ 'Note the "Account ID" shown in the R2 overview (right sidebar).', 'Create a bucket: "Create bucket", name "{{PROJECT}}-files" (adjust if the name is invalid/taken; only lowercase letters, numbers, and hyphens), automatic location.', 'In the created bucket: Settings → CORS Policy → Edit and paste EXACTLY: {"rules":[{"allowed":{"origins":["http://localhost:3000"],"methods":["GET","PUT"],"headers":["Content-Type"]},"maxAgeSeconds":3600}]} — save.', - 'Back in R2 → "Manage R2 API Tokens" (or API → Manage API Tokens) → "Create API Token". Name: "{{PROJECT}} (create-iai)". Permission: "Object Read & Write" restricted to the created bucket. TTL: Forever.', + 'Back in R2 → "Manage R2 API Tokens" (or API → Manage API Tokens) → "Create API Token". Name: "{{PROJECT}} (impactus)". Permission: "Object Read & Write" restricted to the created bucket. TTL: Forever.', 'Create the token and copy the "Access Key ID" and the "Secret Access Key" (the Secret is shown only ONCE).', ], rules: ['Do not create tokens with Admin permission — only "Object Read & Write".'], diff --git a/src/lib/args.js b/src/lib/args.js index e9e1f2b..74b4d7d 100644 --- a/src/lib/args.js +++ b/src/lib/args.js @@ -1,6 +1,6 @@ -// Tiny dependency-free CLI flag parser for create-iai. +// Tiny dependency-free CLI flag parser for the impactus CLI. // -// Supported grammar: `create-iai [name] [options]`. Boolean flags may be +// Supported grammar: `impactus [name] [options]`. Boolean flags may be // negated with `--no-`. Value flags accept `--flag value` and // `--flag=value`. Unknown flags abort with a helpful message instead of being // silently ignored. @@ -273,7 +273,7 @@ Options: per-member roles and permissions, /admin with org management) --keys .env file with service keys (Stripe, Asaas, - Resend…) — the web UI (--ui) generates one in ~/.create-iai/keys/. + Resend…) — the web UI (--ui) generates one in ~/.impactus-cli/keys/. The keys stay on your machine only; the installer applies them to Convex/.env.local and creates the webhooks via API. @@ -313,7 +313,7 @@ Options: --login Authenticates this computer (opens the browser) and exits --logout Removes/revokes this computer's CLI token and exits --whoami Shows your subscription status and exits - --api Community API base (dev/testing; or CREATE_IAI_API) + --api Community API base (dev/testing; or IMPACTUS_API) --verify Audits an ALREADY-installed project (--dir points to the folder): orphan markers, missing envs, leftover manifest… diff --git a/src/lib/auth-client.js b/src/lib/auth-client.js index 3178a85..e6db95f 100644 --- a/src/lib/auth-client.js +++ b/src/lib/auth-client.js @@ -1,17 +1,19 @@ // Device flow client (RFC 8628) against the community + token storage. // -// The CLI token lives in ~/.create-iai/auth.json (permission 600) and is never -// exposed in the command/history. All calls are best-effort with a timeout; -// none of them throw — they return result objects for the auth step to decide. +// The CLI token lives in ~/.impactus-cli/auth.json (permission 600) and is +// never exposed in the command/history. All calls are best-effort with a +// timeout; none of them throw — they return result objects for the auth step +// to decide. import { existsSync } from 'node:fs'; import { chmod, mkdir, readFile, rm, writeFile } from 'node:fs/promises'; -import { homedir, hostname, platform } from 'node:os'; +import { hostname, platform } from 'node:os'; import { dirname, join } from 'node:path'; -import { COMMUNITY, AUTH_FILE, STATE_DIR } from '../config.js'; +import { COMMUNITY, AUTH_FILE } from '../config.js'; +import { stateDirPath } from './state-dir.js'; function authPath() { - return join(homedir(), STATE_DIR, AUTH_FILE); + return join(stateDirPath(), AUTH_FILE); } let warnedCustomApiBase = false; @@ -22,13 +24,13 @@ let warnedCustomApiBase = false; * the student should know they left the official API. */ export function resolveApiBase(flags = {}) { - const raw = flags.api || process.env.CREATE_IAI_API || COMMUNITY.apiBase; + const raw = flags.api || process.env.IMPACTUS_API || process.env.CREATE_IAI_API || COMMUNITY.apiBase; const base = String(raw).replace(/\/+$/, ''); if (base !== COMMUNITY.apiBase && !warnedCustomApiBase) { warnedCustomApiBase = true; console.error( `! Using a custom community API (${base}) — your CLI token will be sent to this host.\n` + - ' If you did not mean to, remove the --api flag (or unset CREATE_IAI_API) and run again.', + ' If you did not mean to, remove the --api flag (or unset IMPACTUS_API / CREATE_IAI_API) and run again.', ); } return base; diff --git a/src/lib/command.js b/src/lib/command.js index 234df93..09386e1 100644 --- a/src/lib/command.js +++ b/src/lib/command.js @@ -1,5 +1,5 @@ // Pure translation layer: a plain "choices" object (as posted by the web UI) -// → the argv/flags of the `create-iai` CLI. No I/O, fully unit-testable. +// → the argv/flags of the `impactus` CLI. No I/O, fully unit-testable. // // The web UI (src/steps/ui-server.js) only *builds a command* — the install // itself runs in the student's own terminal. So this module's whole job is: @@ -34,7 +34,7 @@ const GROUP_BY_FLAG = new Map(ADDON_GROUPS.map((g) => [g.flag, g])); * validates the token against the SAME backend the page used * (only shows up in dev/test; in production the flag does not exist). * @returns {{ argv: string[], command: string }} - * `argv` = tokens after `create-iai`; `command` = the copy-pasteable line. + * `argv` = tokens after `impactus`; `command` = the copy-pasteable line. */ export function buildCommand(choices = {}, opts = {}) { const argv = []; diff --git a/src/lib/keys.js b/src/lib/keys.js index 53abc66..6f9d852 100644 --- a/src/lib/keys.js +++ b/src/lib/keys.js @@ -1,5 +1,5 @@ // Local keys file (`--keys `): the web UI saves the keys the user -// pasted into ~/.create-iai/keys/.env (permission 600) and the +// pasted into ~/.impactus-cli/keys/.env (permission 600) and the // generated command only REFERENCES the path — the keys never appear in the // command, in the shell history, nor leave the machine. The CLI loads the file // and each step consumes what it needs (Clerk/Convex stay automatic). @@ -7,9 +7,9 @@ import { existsSync } from 'node:fs'; import { chmod, mkdir, readFile, rm, writeFile } from 'node:fs/promises'; import { randomBytes } from 'node:crypto'; -import { homedir } from 'node:os'; import { dirname, join } from 'node:path'; -import { KNOWN_SERVICE_KEYS, STATE_DIR } from '../config.js'; +import { KNOWN_SERVICE_KEYS } from '../config.js'; +import { stateDirPath } from './state-dir.js'; /** Default path of a project's keys file. */ export function defaultKeysPath(slug) { @@ -19,7 +19,7 @@ export function defaultKeysPath(slug) { .replace(/-+/g, '-') .replace(/^-|-$/g, '') .toLowerCase() || 'project'; - return join(homedir(), STATE_DIR, 'keys', `${safe}.env`); + return join(stateDirPath(), 'keys', `${safe}.env`); } /** @@ -57,7 +57,7 @@ export async function saveKeysFile(path, keys = {}) { } await mkdir(dirname(path), { recursive: true, mode: 0o700 }); const body = [ - '# Keys pasted in the create-iai UI — on this machine ONLY.', + '# Keys pasted in the IMPACTUS CLI web UI — on this machine ONLY.', '# Consumed by: npx impactus --keys . You may delete it after the install.', ...entries.map(([k, v]) => `${k}=${String(v).trim()}`), '', diff --git a/src/lib/log.js b/src/lib/log.js index 0c7cbf2..0cc5cca 100644 --- a/src/lib/log.js +++ b/src/lib/log.js @@ -3,13 +3,12 @@ // user's home directory. On fatal errors main.js prints the path so users can // attach it to bug reports. import { appendFileSync, mkdirSync, readdirSync, rmSync } from 'node:fs'; -import { homedir } from 'node:os'; import { join } from 'node:path'; -import { STATE_DIR } from '../config.js'; +import { stateDirPath } from './state-dir.js'; let logPath = null; -// How many run logs to keep in ~/.create-iai/logs — without a cap they +// How many run logs to keep in ~/.impactus-cli/logs — without a cap they // accumulate forever (one file per run). const KEEP_LOGS = 20; @@ -30,11 +29,11 @@ function pruneOldLogs(dirPath) { /** Create the per-run log file (best-effort; logging never breaks the CLI). */ export function initLog() { try { - const dirPath = join(homedir(), STATE_DIR, 'logs'); + const dirPath = join(stateDirPath(), 'logs'); mkdirSync(dirPath, { recursive: true }); const stamp = new Date().toISOString().replace(/[:.]/g, '-'); logPath = join(dirPath, `run-${stamp}.log`); - appendFileSync(logPath, `# create-iai — ${new Date().toISOString()}\n# node ${process.version} ${process.platform}/${process.arch}\n\n`); + appendFileSync(logPath, `# impactus — ${new Date().toISOString()}\n# node ${process.version} ${process.platform}/${process.arch}\n\n`); pruneOldLogs(dirPath); } catch { logPath = null; diff --git a/src/lib/native-dialog.js b/src/lib/native-dialog.js index f506d1b..7f872ea 100644 --- a/src/lib/native-dialog.js +++ b/src/lib/native-dialog.js @@ -12,7 +12,7 @@ import { spawn } from 'node:child_process'; import { homedir } from 'node:os'; import process from 'node:process'; -const PROMPT = 'create-iai — choose the project folder'; +const PROMPT = 'IMPACTUS CLI — choose the project folder'; /** * Candidate commands to open the native folder dialog, in try-order. diff --git a/src/lib/neon.js b/src/lib/neon.js index f733c5e..d221494 100644 --- a/src/lib/neon.js +++ b/src/lib/neon.js @@ -15,7 +15,7 @@ const LAUNCHPAD_API = 'https://neon.new/api/v1/database'; * @param {string} ref - integrator identifier (visible to Neon). * @returns {Promise<{databaseUrl: string, directUrl: string|null, claimUrl: string|null, expiresAt: string|null}>} */ -export async function createClaimableNeonDb(ref = 'create-iai') { +export async function createClaimableNeonDb(ref = 'impactus') { const res = await fetch(LAUNCHPAD_API, { method: 'POST', headers: { 'content-type': 'application/json' }, diff --git a/src/lib/state-dir.js b/src/lib/state-dir.js new file mode 100644 index 0000000..3208377 --- /dev/null +++ b/src/lib/state-dir.js @@ -0,0 +1,28 @@ +// The CLI's state folder in the user's home (~/.impactus-cli): the auth +// token, keys pasted in the web UI and the run logs. Installs made before the +// rebrand used ~/.create-iai — the first touch RENAMES that folder to the new +// name, so the student keeps the login, keys and logs with zero re-work. +// If the rename is denied (an open handle on Windows, permissions), the +// legacy folder keeps being used as-is: state must never split across two +// directories, and a naming nit must never break the CLI. +import { existsSync, renameSync } from 'node:fs'; +import { homedir } from 'node:os'; +import { join } from 'node:path'; +import { LEGACY_STATE_DIR, STATE_DIR } from '../config.js'; + +/** + * Absolute path of the state folder, migrating the pre-rebrand one when found. + * @param {string} [home] test seam; defaults to the real home directory. + */ +export function stateDirPath(home = homedir()) { + const current = join(home, STATE_DIR); + const legacy = join(home, LEGACY_STATE_DIR); + if (!existsSync(current) && existsSync(legacy)) { + try { + renameSync(legacy, current); + } catch { + return legacy; + } + } + return current; +} diff --git a/src/lib/template-fetch.js b/src/lib/template-fetch.js index 2c0c36d..dd21eb4 100644 --- a/src/lib/template-fetch.js +++ b/src/lib/template-fetch.js @@ -49,7 +49,7 @@ export async function extractDownloadedTarball(tgz, destDir, deps = {}) { * @returns {Promise<{ok: boolean, reason?: string}>} */ export async function fetchTemplateToDir(apiBase, token, name, destDir, ref) { - const tmpRoot = mkdtempSync(join(tmpdir(), 'create-iai-tgz-')); + const tmpRoot = mkdtempSync(join(tmpdir(), 'impactus-tgz-')); const tgz = join(tmpRoot, `${name}.tar.gz`); try { const dl = await downloadTemplate(apiBase, token, name, tgz, ref); diff --git a/src/steps/addons.js b/src/steps/addons.js index ba6bc64..d3f1d37 100644 --- a/src/steps/addons.js +++ b/src/steps/addons.js @@ -107,7 +107,7 @@ export async function applyAddons(ctx) { await mkdir(dirname(join(dir, ADDONS_CONFIG_FILE)), { recursive: true }); await writeFile( join(dir, ADDONS_CONFIG_FILE), - JSON.stringify({ createdWith: 'create-iai', addons: [...chosen].sort() }, null, 2) + '\n', + JSON.stringify({ createdWith: 'impactus', addons: [...chosen].sort() }, null, 2) + '\n', 'utf8', ); await rm(manifestPath, { force: true }); diff --git a/src/steps/auth.js b/src/steps/auth.js index fc01147..3c3c966 100644 --- a/src/steps/auth.js +++ b/src/steps/auth.js @@ -77,18 +77,19 @@ export async function ensureAuthenticated(ctx) { // CI / automation: token via env (non-interactive). An explicit token that // does not work fails LOUDLY — automation must never silently degrade to a // harness-only install. - const envToken = process.env.CREATE_IAI_TOKEN; + const envToken = process.env.IMPACTUS_TOKEN || process.env.CREATE_IAI_TOKEN; + const envTokenVar = process.env.IMPACTUS_TOKEN ? 'IMPACTUS_TOKEN' : 'CREATE_IAI_TOKEN'; if (envToken) { redactSecret(envToken); const v = await verifyToken(apiBase, envToken); const verdict = classifyVerify(v); if (verdict === 'ok') { accept(ctx, envToken, v.data); - ui.info(`Access confirmed via CREATE_IAI_TOKEN (${who(v.data)}).`); + ui.info(`Access confirmed via ${envTokenVar} (${who(v.data)}).`); return; } if (verdict === 'unavailable') unavailableAndExit(v); - denyAndExit(v, 'CREATE_IAI_TOKEN is invalid or the subscription is not active.'); + denyAndExit(v, `${envTokenVar} is invalid or the subscription is not active.`); } // Token saved from a previous login: valid → straight through, no questions. @@ -121,11 +122,11 @@ export async function ensureAuthenticated(ctx) { if (flags.login) return deviceLogin(ctx, apiBase); // --yes has no prompts: continue as guest. Full installs in automation use - // CREATE_IAI_TOKEN; flags that force the template error out in steps/mode.js. + // IMPACTUS_TOKEN; flags that force the template error out in steps/mode.js. if (flags.yes) { ui.warn( 'No active login and --yes is non-interactive — continuing WITHOUT sign-in (harness + agent only). ' + - 'For the full install set CREATE_IAI_TOKEN, or run once without --yes to sign in.', + 'For the full install set IMPACTUS_TOKEN, or run once without --yes to sign in.', ); return enterGuestMode(ctx); } diff --git a/src/steps/convex.js b/src/steps/convex.js index 9f7686c..e5289b5 100644 --- a/src/steps/convex.js +++ b/src/steps/convex.js @@ -50,7 +50,7 @@ export async function setupConvex(ctx) { ); } ui.step('Logging in to Convex…'); - await runInherit('npx', ['convex', 'login', '--device-name', 'create-iai installer'], { cwd: dir }); + await runInherit('npx', ['convex', 'login', '--device-name', 'impactus installer'], { cwd: dir }); // 2. Provision — or REUSE. A crashed previous run may already have created // the cloud project and written the env vars: running --configure=new diff --git a/src/steps/deploy.js b/src/steps/deploy.js index b771e1b..8ad48cc 100644 --- a/src/steps/deploy.js +++ b/src/steps/deploy.js @@ -141,7 +141,7 @@ export async function setupDeploy(ctx) { * Returns null when neither works — the caller then just tries to add. */ async function fetchVercelProdEnv(dir) { - const tmpFile = join(tmpdir(), `create-iai-vercel-env-${Date.now()}-${Math.random().toString(36).slice(2)}.env`); + const tmpFile = join(tmpdir(), `impactus-vercel-env-${Date.now()}-${Math.random().toString(36).slice(2)}.env`); try { const pull = await run( 'vercel', diff --git a/src/steps/finish.js b/src/steps/finish.js index 789e4f5..5e7318e 100644 --- a/src/steps/finish.js +++ b/src/steps/finish.js @@ -7,7 +7,7 @@ import { piCodexReady } from '../lib/pi-auth.js'; import { osKind } from '../lib/platform.js'; import { CLAUDE_INSTALL_HINT } from './preflight.js'; import { STACK_CATEGORIES, STACK_LATER } from '../stack-catalog.js'; -import { relToCwd, STATE_MARKER } from './project.js'; +import { relToCwd, LEGACY_STATE_MARKER, STATE_MARKER } from './project.js'; import * as ui from '../lib/ui.js'; /** @@ -124,6 +124,7 @@ export async function finish(ctx) { // the start of installTemplate) BEFORE the final commit, so a future run // doesn't mistake this folder for a half-finished installation. await rm(join(ctx.dir, STATE_MARKER), { force: true }).catch(() => {}); + await rm(join(ctx.dir, LEGACY_STATE_MARKER), { force: true }).catch(() => {}); // Preflight skips go into ai-docs/inbox.md BEFORE the final commit below — // writing them later would leave the tree dirty right after the install. @@ -138,7 +139,7 @@ export async function finish(ctx) { await run('git', ['add', '-A'], { cwd: ctx.dir }); const commit = await run( 'git', - ['commit', '-q', '-m', 'chore: stack, FIA and tooling (create-iai)'], + ['commit', '-q', '-m', 'chore: stack, FIA and tooling (impactus)'], { cwd: ctx.dir }, ); if (commit.ok) { diff --git a/src/steps/github.js b/src/steps/github.js index b2a3d4c..c985031 100644 --- a/src/steps/github.js +++ b/src/steps/github.js @@ -35,7 +35,7 @@ export async function setupGithub(ctx) { await run('git', ['add', '-A'], { cwd: dir }); const commit = await run( 'git', - ['commit', '-q', '-m', 'feat: project configured by create-iai (Convex + Clerk + shadcn/ui)'], + ['commit', '-q', '-m', 'feat: project configured by impactus (Convex + Clerk + shadcn/ui)'], { cwd: dir }, ); const commitOut = commit.stdout + commit.stderr; diff --git a/src/steps/harness.js b/src/steps/harness.js index 26c7a2c..8be320a 100644 --- a/src/steps/harness.js +++ b/src/steps/harness.js @@ -69,7 +69,7 @@ export async function setupHarness(ctx) { // out anonymously: harness + FIA are the free tier. The TEMPLATES remain // token-gated (steps/project.js) and steps/mode.js never lets a guest reach // them. There is no direct-clone fallback in either case. - const tmpRoot = mkdtempSync(join(tmpdir(), 'create-iai-harness-')); + const tmpRoot = mkdtempSync(join(tmpdir(), 'impactus-harness-')); const tmpClone = join(tmpRoot, 'repo'); try { await ui.spin( @@ -169,7 +169,7 @@ export async function setupHarness(ctx) { await run('git', ['init', '-q'], { cwd: dir }); } await run('git', ['add', '-A'], { cwd: dir }); - const commit = await run('git', ['commit', '-q', '-m', 'feat: agent workflow harness (create-iai)'], { + const commit = await run('git', ['commit', '-q', '-m', 'feat: agent workflow harness (impactus)'], { cwd: dir, }); if (commit.ok) { diff --git a/src/steps/mode.js b/src/steps/mode.js index d178fd3..3c406a4 100644 --- a/src/steps/mode.js +++ b/src/steps/mode.js @@ -3,7 +3,7 @@ import { join } from 'node:path'; import * as ui from '../lib/ui.js'; import { presentAgentFiles } from '../lib/agent-backup.js'; import { parseStackFlag } from '../lib/stack.js'; -import { STATE_MARKER } from './project.js'; +import { hasStateMarker } from './project.js'; /** * First real choice of the installer (right after name/folder): how to start. @@ -37,7 +37,7 @@ export async function selectInstallMode(ctx) { // midway — that is a resume, never an "existing project": steering it into // brownfield/harness-only would leave the half-copied template as-is, and a // later fresh full run would duplicate the cloud resources (Convex/Clerk). - ctx.resumingInstall = Boolean(ctx.dir) && existsSync(join(ctx.dir, STATE_MARKER)); + ctx.resumingInstall = Boolean(ctx.dir) && hasStateMarker(ctx.dir); // Existing project: a folder the CLI didn't create that already has // package.json or git. Changes the default (harness only) and the final @@ -55,7 +55,7 @@ export async function selectInstallMode(ctx) { const needsSignIn = (what) => new Error( `${what} needs the community sign-in — run the installer again and choose "Sign in" ` + - '(or authenticate first with `npx impactus --login`; automation uses CREATE_IAI_TOKEN).', + '(or authenticate first with `npx impactus --login`; automation uses IMPACTUS_TOKEN).', ); if (guest && ctx.resumingInstall) { // The leftover marker means a half-finished FULL install: only the gated diff --git a/src/steps/project.js b/src/steps/project.js index 28568da..8e4d718 100644 --- a/src/steps/project.js +++ b/src/steps/project.js @@ -17,7 +17,15 @@ import * as ui from '../lib/ui.js'; // half-finished installation — NOT an "existing project" (steps/mode.js would // otherwise steer recovery into brownfield/harness-only and a fresh full run // would duplicate the cloud resources). -export const STATE_MARKER = '.create-iai-state.json'; +export const STATE_MARKER = '.impactus-cli-state.json'; +// Installs started by a pre-rebrand CLI wrote this name — readers accept +// both, and the finish step removes both. +export const LEGACY_STATE_MARKER = '.create-iai-state.json'; + +/** True when `dir` carries a half-finished-install marker (either brand). */ +export function hasStateMarker(dir) { + return existsSync(join(dir, STATE_MARKER)) || existsSync(join(dir, LEGACY_STATE_MARKER)); +} // ── Prompt: target folder (its name IS the project name) ──────────────────── @@ -96,7 +104,7 @@ export async function promptProject(flags = {}) { throw new Error(`The folder ${targetDir} is not empty. With --yes I never overwrite; pick another one with --dir.`); } ui.info(`Existing project detected in ${targetDir} — the harness only adds what's missing (nothing is overwritten).`); - } else if (existsSync(resolve(targetDir, STATE_MARKER))) { + } else if (hasStateMarker(resolve(targetDir))) { // Half-finished previous install (see STATE_MARKER above): continuing // in the same folder is the recovery path, not a conflict. ui.warn( @@ -147,7 +155,7 @@ export async function fetchTemplate(ctx) { // Download into a temp dir; `installTemplate` copies it into the target // later. This also makes installing into the current/non-empty folder work. - const tmpRoot = mkdtempSync(join(tmpdir(), 'create-iai-')); + const tmpRoot = mkdtempSync(join(tmpdir(), 'impactus-template-')); const tmpClone = join(tmpRoot, 'repo'); ctx.templateTmpRoot = tmpRoot; ctx.templateTmp = tmpClone; @@ -236,7 +244,7 @@ export async function installTemplate(ctx) { const envExample = join(dir, ENV_EXAMPLE); if (!existsSync(envLocal)) { if (existsSync(envExample)) await copyFile(envExample, envLocal); - else await writeFile(envLocal, '# Generated by create-iai\n', 'utf8'); + else await writeFile(envLocal, '# Generated by impactus\n', 'utf8'); } for (const [key, value] of Object.entries(CLERK_ROUTING_DEFAULTS)) { await upsertEnvVar(envLocal, key, value); @@ -248,7 +256,7 @@ export async function installTemplate(ctx) { // customization. Best-effort: a missing git identity must not abort. await run('git', ['init', '-q'], { cwd: dir }); await run('git', ['add', '-A'], { cwd: dir }); - await run('git', ['commit', '-q', '-m', 'chore: initial template (create-iai)'], { cwd: dir }); + await run('git', ['commit', '-q', '-m', 'chore: initial template (impactus)'], { cwd: dir }); // Install dependencies with live output. ui.step('Installing dependencies (npm install)…'); diff --git a/src/steps/service-keys.js b/src/steps/service-keys.js index 9ee7004..d932d1d 100644 --- a/src/steps/service-keys.js +++ b/src/steps/service-keys.js @@ -290,7 +290,7 @@ async function ensureStripeWebhook(ctx, values) { if (values.STRIPE_WEBHOOK_SECRET) return 'webhook already provided'; const url = `${site}${PAYMENT_WEBHOOKS.stripe.route}`; - const body = new URLSearchParams({ url, description: 'create-iai (dev)' }); + const body = new URLSearchParams({ url, description: 'impactus (dev)' }); for (const ev of PAYMENT_WEBHOOKS.stripe.events) body.append('enabled_events[]', ev); const apiVersion = await projectStripeApiVersion(ctx.dir); if (apiVersion) body.append('api_version', apiVersion); @@ -370,7 +370,7 @@ async function ensureAsaasWebhook(ctx, values) { method: 'POST', headers: { access_token: apiKey, 'Content-Type': 'application/json' }, body: JSON.stringify({ - name: 'create-iai', + name: 'impactus', url, email, enabled: true, diff --git a/src/steps/stack-docs.js b/src/steps/stack-docs.js index f1780f6..d89c936 100644 --- a/src/steps/stack-docs.js +++ b/src/steps/stack-docs.js @@ -168,7 +168,7 @@ async function maybeProvisionNeon(ctx, info) { try { const db = await ui.spin( 'Creating the database on Neon Launchpad (no account)…', - () => createClaimableNeonDb('create-iai'), + () => createClaimableNeonDb('impactus'), 'Development Postgres database created.', ); redactSecret(db.databaseUrl); // the connection string carries a password — never in the log diff --git a/src/steps/storage.js b/src/steps/storage.js index 6a8829b..273fa71 100644 --- a/src/steps/storage.js +++ b/src/steps/storage.js @@ -363,7 +363,7 @@ async function applyCors(accountId, bucket, ctx) { ], }; - const dirTmp = await mkdtemp(join(tmpdir(), 'create-iai-r2-')); + const dirTmp = await mkdtemp(join(tmpdir(), 'impactus-r2-')); const corsFile = join(dirTmp, 'cors.json'); await writeFile(corsFile, JSON.stringify(policy, null, 2), 'utf8'); diff --git a/src/steps/ui-server.js b/src/steps/ui-server.js index 533fb96..483e756 100644 --- a/src/steps/ui-server.js +++ b/src/steps/ui-server.js @@ -40,7 +40,7 @@ const MAX_PORT_TRIES = 20; // 4599..4618 before giving up * GET /api/browse?path=… → subfolders (disk picker, fallback) * GET /api/dir-info?path=… → { exists, empty, name } (full-folder warning) * POST /api/pick-folder → opens the system's NATIVE folder dialog - * POST /api/keys → writes the keys to ~/.create-iai/keys/.env (600) + * POST /api/keys → writes the keys to ~/.impactus-cli/keys/.env (600) * POST /api/command → { …choices } → { command, argv } (to copy) * GET /api/auth → student access status * POST /api/login → device flow (NDJSON: pending → ok/error) @@ -59,7 +59,7 @@ export async function runUiServer(flags = {}) { const port = await listenWithFallback(server, requestedPort); const url = `http://localhost:${port}`; - ui.intro('create-iai'); + ui.intro('IMPACTUS CLI'); ui.success(`Open in your browser: ${url}`); ui.info('Assemble the install by clicking; at the end, copy the command and run it in your terminal.'); ui.info('Tip: `npx impactus` (without --ui) asks everything right here in the terminal.'); @@ -211,7 +211,7 @@ function handle(req, res, catalog, runState) { .catch((err) => sendJson(res, 400, { error: String(err?.message || err) })); } if (method === 'POST' && url === '/api/keys') { - // Keys pasted on the page → LOCAL file in ~/.create-iai/keys/ with + // Keys pasted on the page → LOCAL file in ~/.impactus-cli/keys/ with // permission 600. NONE of this leaves the machine: the generated command // references the path and the installer reads from it. An empty/cleared body deletes the file. return readBody(req) @@ -319,7 +319,7 @@ async function authStatus(flags) { /** * The whole device flow server-side, streaming the code and link to the page. - * The token is saved to ~/.create-iai/auth.json — the child installer reads it + * The token is saved to ~/.impactus-cli/auth.json — the child installer reads it * from there, so the secret never travels to the browser. */ async function loginFlow(flags, emit) { diff --git a/src/steps/verify.js b/src/steps/verify.js index 6b9ce2d..f2ec6b3 100644 --- a/src/steps/verify.js +++ b/src/steps/verify.js @@ -301,7 +301,7 @@ export async function runVerify(flags = {}) { return report.ok; } - ui.intro('create-iai --verify'); + ui.intro('impactus --verify'); ui.message(`Auditing the project at ${dir}…`); const findings = await collectFindings(dir); diff --git a/src/ui/page.js b/src/ui/page.js index ec88bfb..1796ad3 100644 --- a/src/ui/page.js +++ b/src/ui/page.js @@ -9,7 +9,7 @@ export function renderPage() { -create-iai — assemble your install +IMPACTUS CLI — assemble your install