nerdsystem 0.0.5 → 0.0.6
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 +1 -1
- package/dist/api.js +2 -1
- package/dist/commands/check.d.ts +6 -1
- package/dist/commands/check.js +27 -10
- package/dist/commands/deploy.js +3 -1
- package/dist/commands/dev.js +2 -1
- package/dist/commands/github.d.ts +1 -0
- package/dist/commands/github.js +2 -2
- package/dist/commands/init.js +34 -4
- package/dist/commands/login.js +1 -1
- package/dist/commands/ops.d.ts +1 -0
- package/dist/commands/ops.js +3 -3
- package/dist/index.js +11 -16
- package/dist/util.d.ts +4 -0
- package/dist/util.js +11 -0
- package/package.json +1 -1
- package/template/.claude/skills/nerdsystem/SKILL.md +6 -4
- package/template/.cursor/rules/nerdsystem.mdc +7 -5
- package/template/.github/copilot-instructions.md +7 -5
- package/template/AGENTS.md +7 -5
- package/template/GEMINI.md +7 -5
- package/template/src/client/auth.tsx +1 -1
package/README.md
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
npx nerdsystem init my-app
|
|
7
7
|
cd my-app && pnpm install
|
|
8
8
|
nerd dev # run locally with real sign-in
|
|
9
|
-
nerd
|
|
9
|
+
nerd check # every gate a deploy runs: secrets, dependencies, policy, PII, types (alias: nerd verify)
|
|
10
10
|
nerd deploy # checks, builds, migrates, ships
|
|
11
11
|
```
|
|
12
12
|
|
package/dist/api.js
CHANGED
|
@@ -37,7 +37,8 @@ export class ApiClient {
|
|
|
37
37
|
if (!res.ok) {
|
|
38
38
|
const err = json?.error;
|
|
39
39
|
const details = err?.details ? `\n${JSON.stringify(err.details, null, 2)}` : "";
|
|
40
|
-
|
|
40
|
+
// The message leads; the code and status follow for people who need them.
|
|
41
|
+
throw new CliError(`${err?.message ?? "request failed"} [${err?.code ?? "error"} ${res.status}]${details}`);
|
|
41
42
|
}
|
|
42
43
|
return json;
|
|
43
44
|
}
|
package/dist/commands/check.d.ts
CHANGED
|
@@ -13,8 +13,13 @@ export declare function runChecks(root: string, opts: {
|
|
|
13
13
|
remote: boolean;
|
|
14
14
|
typecheck: boolean;
|
|
15
15
|
}): Promise<CheckResult>;
|
|
16
|
+
/**
|
|
17
|
+
* `nerd check` (and its alias `nerd verify`): every gate a deploy runs, plus a check that the linked
|
|
18
|
+
* project still exists. One command, one output shape, so agents learn one thing.
|
|
19
|
+
*/
|
|
16
20
|
export declare function check(opts: {
|
|
17
21
|
json: boolean;
|
|
18
|
-
"no-
|
|
22
|
+
"no-build": boolean;
|
|
19
23
|
"no-remote": boolean;
|
|
24
|
+
rule?: string | undefined;
|
|
20
25
|
}): Promise<void>;
|
package/dist/commands/check.js
CHANGED
|
@@ -126,20 +126,37 @@ export async function runChecks(root, opts) {
|
|
|
126
126
|
}
|
|
127
127
|
return { ok: problems.length === 0, problems };
|
|
128
128
|
}
|
|
129
|
+
/**
|
|
130
|
+
* `nerd check` (and its alias `nerd verify`): every gate a deploy runs, plus a check that the linked
|
|
131
|
+
* project still exists. One command, one output shape, so agents learn one thing.
|
|
132
|
+
*/
|
|
129
133
|
export async function check(opts) {
|
|
130
134
|
const root = findProjectRoot();
|
|
131
|
-
const
|
|
132
|
-
if (opts.
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
135
|
+
const problems = [];
|
|
136
|
+
if (!opts["no-remote"] && !opts.rule) {
|
|
137
|
+
const nerd = readNerdJson(root);
|
|
138
|
+
try {
|
|
139
|
+
await ApiClient.fromConfig().get(projectPath(nerd));
|
|
140
|
+
}
|
|
141
|
+
catch (err) {
|
|
142
|
+
problems.push(`The linked project ${nerd.org}/${nerd.project} is not reachable: ${err.message}`);
|
|
143
|
+
}
|
|
137
144
|
}
|
|
145
|
+
// Loaded here, not at the top: the gates import staticChecks from this file.
|
|
146
|
+
const { runVerify } = await import("./verify.js");
|
|
147
|
+
const out = await runVerify(root, { withBuild: !opts["no-build"], stopOnFail: false, quiet: opts.json, onlyRules: opts.rule ? [opts.rule] : undefined });
|
|
148
|
+
const failed = out.results.filter((r) => r.status === "fail").length;
|
|
149
|
+
if (opts.json)
|
|
150
|
+
process.stdout.write(`${JSON.stringify({ ok: out.ok && problems.length === 0, failures: out.failures, stop: out.stop, problems }, null, 2)}\n`);
|
|
138
151
|
else {
|
|
139
|
-
for (const p of
|
|
152
|
+
for (const p of problems)
|
|
140
153
|
log.warn(p);
|
|
154
|
+
if (out.ok && problems.length === 0)
|
|
155
|
+
log.ok("All gates passed");
|
|
156
|
+
const human = out.failures.filter((f) => f.audience === "human" && f.severity === "block");
|
|
157
|
+
if (human.length)
|
|
158
|
+
log.warn(`${human.length} finding(s) need a person, not the agent: ${human.map((f) => f.rule_id).join(", ")}`);
|
|
141
159
|
}
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
throw new CliError(`${blocking.length} problem(s) found`, 2);
|
|
160
|
+
if (failed || problems.length)
|
|
161
|
+
throw new CliError(failed ? `${failed} gate(s) failed. Fix what is listed above, then run nerd check again.` : problems[0], 2);
|
|
145
162
|
}
|
package/dist/commands/deploy.js
CHANGED
|
@@ -2,7 +2,7 @@ import { existsSync, readdirSync, readFileSync, statSync } from "node:fs";
|
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { ApiClient } from "../api.js";
|
|
4
4
|
import { findProjectRoot, projectPath, readNerdJson } from "../project.js";
|
|
5
|
-
import { CliError, log, printJson, runOrThrow, table } from "../util.js";
|
|
5
|
+
import { CliError, log, printJson, runOrThrow, table, dashboardUrl, ensureDeps } from "../util.js";
|
|
6
6
|
import { runChecks } from "./check.js";
|
|
7
7
|
import { BUILD_GATES, knownSecretsFrom, runGates, SOURCE_GATES } from "../gates/index.js";
|
|
8
8
|
import { readDevVars } from "../project.js";
|
|
@@ -76,6 +76,7 @@ export async function deploy(opts) {
|
|
|
76
76
|
}
|
|
77
77
|
if (!opts["skip-build"]) {
|
|
78
78
|
log.step("Building with vite");
|
|
79
|
+
ensureDeps(root);
|
|
79
80
|
await runOrThrow("npx", ["vite", "build"], { cwd: root });
|
|
80
81
|
}
|
|
81
82
|
const build = readBuildOutput(root);
|
|
@@ -109,6 +110,7 @@ export async function deploy(opts) {
|
|
|
109
110
|
if (opts.json)
|
|
110
111
|
return printJson({ deployment, url: project.url, migrations: mig });
|
|
111
112
|
log.ok(`Deployed ${deployment.id} (${deployment.status}) → ${project.url}`);
|
|
113
|
+
log.info(`First time? Sign up on your app at ${project.url}, then make yourself admin in the dashboard's Auth tab: ${dashboardUrl(api.apiUrl)}/app/${nerd.org}/projects/${nerd.project}/auth`);
|
|
112
114
|
}
|
|
113
115
|
export async function deployments(opts) {
|
|
114
116
|
const nerd = readNerdJson(findProjectRoot());
|
package/dist/commands/dev.js
CHANGED
|
@@ -2,7 +2,7 @@ import { existsSync } from "node:fs";
|
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { ApiClient } from "../api.js";
|
|
4
4
|
import { findProjectRoot, projectPath, readDevVars, readNerdJson, writeDevVars } from "../project.js";
|
|
5
|
-
import { log, run, runOrThrow } from "../util.js";
|
|
5
|
+
import { log, run, runOrThrow, ensureDeps } from "../util.js";
|
|
6
6
|
/** Makes sure .dev.vars carries the platform bindings for local dev, minting a dev tenant secret once. */
|
|
7
7
|
export async function ensureDevVars(root) {
|
|
8
8
|
const nerd = readNerdJson(root);
|
|
@@ -33,6 +33,7 @@ export async function dev(extra) {
|
|
|
33
33
|
await runOrThrow("npx", ["wrangler", "d1", "migrations", "apply", "DB", "--local"], { cwd: root });
|
|
34
34
|
}
|
|
35
35
|
log.step("Starting vite dev");
|
|
36
|
+
ensureDeps(root);
|
|
36
37
|
const code = await run("npx", ["vite", ...extra], { cwd: root });
|
|
37
38
|
process.exitCode = code;
|
|
38
39
|
}
|
package/dist/commands/github.js
CHANGED
|
@@ -10,9 +10,9 @@ export async function githubConnect(opts) {
|
|
|
10
10
|
}
|
|
11
11
|
export async function githubLink(fullName, opts) {
|
|
12
12
|
if (!fullName)
|
|
13
|
-
throw new CliError("Usage: nerd github link owner/repo [--branch main] [--root-dir apps/web] [--no-previews] [--require-pr]");
|
|
13
|
+
throw new CliError("Usage: nerd github link owner/repo [--branch main] [--root-dir apps/web] [--no-previews] [--require-pr] [--agent @claude]");
|
|
14
14
|
const nerd = readNerdJson(findProjectRoot());
|
|
15
|
-
const body = { fullName, previews: !opts["no-previews"], rootDir: opts["root-dir"] ?? "", requirePr: opts["require-pr"] };
|
|
15
|
+
const body = { fullName, previews: !opts["no-previews"], rootDir: opts["root-dir"] ?? "", requirePr: opts["require-pr"], ...(opts.agent ? { agentMention: opts.agent } : {}) };
|
|
16
16
|
if (opts.branch)
|
|
17
17
|
body.productionBranch = opts.branch;
|
|
18
18
|
if (opts.installation)
|
package/dist/commands/init.js
CHANGED
|
@@ -77,8 +77,19 @@ export async function init(dir, args) {
|
|
|
77
77
|
if (!SLUG_RE.test(slug))
|
|
78
78
|
throw new CliError(`"${slug}" is not a valid project slug (lowercase letters, digits, hyphens). Pass --slug.`);
|
|
79
79
|
const name = args.name ?? slug;
|
|
80
|
-
if (existsSync(root) && readdirSync(root).length > 0)
|
|
81
|
-
throw new CliError(`${root} is not empty.`);
|
|
80
|
+
if (existsSync(root) && readdirSync(root).length > 0) {
|
|
81
|
+
throw new CliError(`${root} is not empty. If an earlier \`nerd init\` stopped partway, run \`nerd link --slug ${slug}\` inside it instead; otherwise pick a new folder name.`);
|
|
82
|
+
}
|
|
83
|
+
// Everything that can fail remotely is checked before a single file is written, so a failure never
|
|
84
|
+
// leaves a half-made folder behind.
|
|
85
|
+
if (!args["no-create"]) {
|
|
86
|
+
const api = ApiClient.fromConfig(); // throws "Not logged in" with the fix
|
|
87
|
+
const me = await api.get("/v1/me");
|
|
88
|
+
if (me.orgs.length === 0)
|
|
89
|
+
throw new CliError("Your account has no organization yet. Sign in to the dashboard once, or ask the person who invited you for the link.");
|
|
90
|
+
if (args.org && !me.orgs.some((o) => o.slug === args.org))
|
|
91
|
+
throw new CliError(`You are not in an org called "${args.org}". Yours: ${me.orgs.map((o) => o.slug).join(", ")}`);
|
|
92
|
+
}
|
|
82
93
|
const src = templateDir();
|
|
83
94
|
log.step(`Copying template into ${root}`);
|
|
84
95
|
mkdirSync(root, { recursive: true });
|
|
@@ -100,8 +111,27 @@ export async function init(dir, args) {
|
|
|
100
111
|
}
|
|
101
112
|
const description = args.describe ?? (await askDescription());
|
|
102
113
|
seedDescription(root, name, description);
|
|
103
|
-
|
|
114
|
+
let project;
|
|
115
|
+
try {
|
|
116
|
+
project = await linkProject(root, { ...(args.org !== undefined ? { org: args.org } : {}), slug, name, create: !args["no-create"] });
|
|
117
|
+
}
|
|
118
|
+
catch (err) {
|
|
119
|
+
const reason = err instanceof Error ? err.message : String(err);
|
|
120
|
+
throw new CliError(`The folder is ready but the project was not created: ${reason}\nFix that, then run: cd ${dir} && nerd link --slug ${slug}${args.org ? ` --org ${args.org}` : ""}`);
|
|
121
|
+
}
|
|
122
|
+
// A repository from the first minute: deploy-on-push and the agent's commits both expect one.
|
|
123
|
+
try {
|
|
124
|
+
const { execFileSync } = await import("node:child_process");
|
|
125
|
+
if (!existsSync(path.join(root, ".git"))) {
|
|
126
|
+
execFileSync("git", ["init", "-q"], { cwd: root, stdio: "ignore" });
|
|
127
|
+
execFileSync("git", ["add", "-A"], { cwd: root, stdio: "ignore" });
|
|
128
|
+
execFileSync("git", ["commit", "-q", "-m", "Start from the nerdsystem template"], { cwd: root, stdio: "ignore" });
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
catch {
|
|
132
|
+
// git missing or not configured: fine, the person can init later
|
|
133
|
+
}
|
|
104
134
|
log.ok(`Ready. Next:\n cd ${dir}\n pnpm install\n nerd dev\n\nThen open the folder in your coding agent and ask it to build the first feature; the nerdsystem skill walks it through.\nDeploys go to ${project.url}`);
|
|
105
135
|
}
|
|
106
136
|
// Kept in sync with packages/sdk/package.json by the release script.
|
|
107
|
-
const SDK_VERSION = "0.0.
|
|
137
|
+
const SDK_VERSION = "0.0.6";
|
package/dist/commands/login.js
CHANGED
|
@@ -28,7 +28,7 @@ export async function login(args) {
|
|
|
28
28
|
else {
|
|
29
29
|
log.info(`Open this link in your browser and approve code ${start.userCode}:\n\n ${start.verificationUrl}\n`);
|
|
30
30
|
if (args.email)
|
|
31
|
-
log.info(`An email
|
|
31
|
+
log.info(`An email went to ${args.email}: one click signs you in (creating your account if you are new) and approves.`);
|
|
32
32
|
log.info("Waiting for approval…");
|
|
33
33
|
}
|
|
34
34
|
}
|
package/dist/commands/ops.d.ts
CHANGED
|
@@ -23,6 +23,7 @@ export declare function secretList(opts: {
|
|
|
23
23
|
/** Value from the argument, or from stdin when omitted, so keys stay out of shell history. */
|
|
24
24
|
export declare function secretSet(name: string | undefined, value: string | undefined, opts: {
|
|
25
25
|
json: boolean;
|
|
26
|
+
previews?: boolean;
|
|
26
27
|
}): Promise<void>;
|
|
27
28
|
export declare function secretRemove(name: string | undefined, opts: {
|
|
28
29
|
json: boolean;
|
package/dist/commands/ops.js
CHANGED
|
@@ -81,13 +81,13 @@ export async function secretList(opts) {
|
|
|
81
81
|
return printJson(r);
|
|
82
82
|
if (!r.secrets.length)
|
|
83
83
|
return log.info("No app secrets yet. Set one with: nerd secret set STRIPE_SECRET_KEY");
|
|
84
|
-
table(r.secrets.map((s) => ({ name: s.name, updated: s.updatedAt })), ["name", "updated"]);
|
|
84
|
+
table(r.secrets.map((s) => ({ name: s.name, previews: s.scope === "all" ? "yes" : "no", updated: s.updatedAt })), ["name", "previews", "updated"]);
|
|
85
85
|
log.info(`\n${r.note}`);
|
|
86
86
|
}
|
|
87
87
|
/** Value from the argument, or from stdin when omitted, so keys stay out of shell history. */
|
|
88
88
|
export async function secretSet(name, value, opts) {
|
|
89
89
|
if (!name)
|
|
90
|
-
throw new CliError("Usage: nerd secret set NAME [value] (omit the value to read it from stdin)");
|
|
90
|
+
throw new CliError("Usage: nerd secret set NAME [value] [--previews] (omit the value to read it from stdin; --previews also gives it to pull request previews)");
|
|
91
91
|
let v = value;
|
|
92
92
|
if (v === undefined) {
|
|
93
93
|
if (process.stdin.isTTY)
|
|
@@ -97,7 +97,7 @@ export async function secretSet(name, value, opts) {
|
|
|
97
97
|
if (!v)
|
|
98
98
|
throw new CliError("Empty value.");
|
|
99
99
|
const nerd = readNerdJson(findProjectRoot());
|
|
100
|
-
const r = await ApiClient.fromConfig().put(projectPath(nerd, `/secrets/${name}`), { value: v });
|
|
100
|
+
const r = await ApiClient.fromConfig().put(projectPath(nerd, `/secrets/${name}`), { value: v, scope: opts.previews ? "all" : "production" });
|
|
101
101
|
if (opts.json)
|
|
102
102
|
return printJson(r);
|
|
103
103
|
log.ok(`Set ${r.secret.name}. ${r.note} For local dev, add ${name}=... to .dev.vars (never commit it).`);
|
package/dist/index.js
CHANGED
|
@@ -7,7 +7,6 @@ import { deploy, deployments } from "./commands/deploy.js";
|
|
|
7
7
|
import { dev } from "./commands/dev.js";
|
|
8
8
|
import { doctor } from "./commands/doctor.js";
|
|
9
9
|
import { cron, health, rollback, secretList, secretRemove, secretSet, transfer, waitlist } from "./commands/ops.js";
|
|
10
|
-
import { verify } from "./commands/verify.js";
|
|
11
10
|
import { agentsSync } from "./commands/agents.js";
|
|
12
11
|
import { billingPortal, billingShow, billingUpgrade } from "./commands/billing.js";
|
|
13
12
|
import { auditExport, clientsCreate, clientsList, clientsRelease, domainsAdd, domainsList, domainsRemove, logs, membersInvite, membersList, membersRemove } from "./commands/org.js";
|
|
@@ -19,14 +18,13 @@ import { findProjectRoot } from "./project.js";
|
|
|
19
18
|
import { CliError, log } from "./util.js";
|
|
20
19
|
const HELP = `nerd — deploy apps to nerdsystem
|
|
21
20
|
|
|
22
|
-
nerd login [--email you@x.com] Sign in: prints a link to approve in the browser (
|
|
21
|
+
nerd login [--email you@x.com] Sign in or sign up: prints a link to approve in the browser (--email sends it; --token nsk_... for scripts)
|
|
23
22
|
nerd whoami Show the current user and orgs
|
|
24
23
|
nerd doctor Check Node, pnpm, git, login, and the project
|
|
25
24
|
nerd init <dir> [--org] [--name] [--describe "what it does"] Create an app and a project for it
|
|
26
25
|
nerd link --slug <project> [--org] Link this directory to an existing or new project
|
|
27
26
|
nerd dev [vite args] Run locally (writes .dev.vars, applies local migrations)
|
|
28
|
-
nerd check
|
|
29
|
-
nerd verify [--no-build] [--rule id] [--json] Run the security gates; --json gives failures the agent can fix
|
|
27
|
+
nerd check [--no-build] [--rule id] [--json] Run every gate a deploy runs; --json gives failures the agent can fix (alias: nerd verify)
|
|
30
28
|
nerd deploy [--note "what changed"] Check, build, migrate, upload. Prints the URL
|
|
31
29
|
nerd rollback [deploymentId] Re-publish the previous production bundle
|
|
32
30
|
nerd cron [off|hourly|daily] Scheduled job: the platform calls POST /api/cron
|
|
@@ -42,7 +40,7 @@ const HELP = `nerd — deploy apps to nerdsystem
|
|
|
42
40
|
nerd secret set NAME [value] App secret for production (c.env.NAME); value from stdin when omitted
|
|
43
41
|
nerd secret list | rm NAME Names only; values are never shown
|
|
44
42
|
nerd github connect Install the GitHub App for this org (prints a link)
|
|
45
|
-
nerd github link owner/repo [--branch main] [--root-dir dir] [--no-previews] [--require-pr]
|
|
43
|
+
nerd github link owner/repo [--branch main] [--root-dir dir] [--no-previews] [--require-pr] [--agent @claude]
|
|
46
44
|
nerd github status | unlink
|
|
47
45
|
nerd builds [--limit n] List git-triggered builds
|
|
48
46
|
nerd builds show <id> Build details and log
|
|
@@ -101,13 +99,10 @@ async function main(argv) {
|
|
|
101
99
|
}
|
|
102
100
|
case "dev":
|
|
103
101
|
return dev(rest);
|
|
104
|
-
case "check":
|
|
105
|
-
const { values } = parse({ "no-typecheck": BOOL, "no-remote": BOOL });
|
|
106
|
-
return check(values);
|
|
107
|
-
}
|
|
102
|
+
case "check":
|
|
108
103
|
case "verify": {
|
|
109
|
-
const { values } = parse({ "no-build": BOOL, rule: STR });
|
|
110
|
-
return
|
|
104
|
+
const { values } = parse({ "no-build": BOOL, "no-remote": BOOL, "no-typecheck": BOOL, rule: STR });
|
|
105
|
+
return check({ json: Boolean(values.json), "no-build": Boolean(values["no-build"]), "no-remote": Boolean(values["no-remote"]), ...(values.rule ? { rule: values.rule } : {}) });
|
|
111
106
|
}
|
|
112
107
|
case "deploy": {
|
|
113
108
|
const { values } = parse({ "skip-check": BOOL, "skip-build": BOOL, note: STR });
|
|
@@ -193,15 +188,15 @@ async function main(argv) {
|
|
|
193
188
|
}
|
|
194
189
|
case "secret": {
|
|
195
190
|
const [sub, ...subRest] = rest;
|
|
196
|
-
const { values, positionals } = parseArgs({ args: subRest, options: { json: BOOL }, allowPositionals: true });
|
|
191
|
+
const { values, positionals } = parseArgs({ args: subRest, options: { json: BOOL, previews: BOOL }, allowPositionals: true });
|
|
197
192
|
const json = Boolean(values.json);
|
|
198
193
|
if (sub === "set")
|
|
199
|
-
return secretSet(positionals[0], positionals[1], { json });
|
|
194
|
+
return secretSet(positionals[0], positionals[1], { json, previews: Boolean(values.previews) });
|
|
200
195
|
if (sub === "rm" || sub === "remove" || sub === "delete")
|
|
201
196
|
return secretRemove(positionals[0], { json });
|
|
202
197
|
if (sub === "list" || sub === "ls" || sub === undefined)
|
|
203
198
|
return secretList({ json });
|
|
204
|
-
throw new CliError("Usage: nerd secret set NAME [value] | list | rm NAME");
|
|
199
|
+
throw new CliError("Usage: nerd secret set NAME [value] [--previews] | list | rm NAME");
|
|
205
200
|
}
|
|
206
201
|
case "domains": {
|
|
207
202
|
const [sub, ...subRest] = rest;
|
|
@@ -231,12 +226,12 @@ async function main(argv) {
|
|
|
231
226
|
}
|
|
232
227
|
case "github": {
|
|
233
228
|
const [sub, ...subRest] = rest;
|
|
234
|
-
const { values, positionals } = parseArgs({ args: subRest, options: { json: BOOL, branch: STR, "root-dir": STR, "no-previews": BOOL, "require-pr": BOOL, installation: STR }, allowPositionals: true });
|
|
229
|
+
const { values, positionals } = parseArgs({ args: subRest, options: { json: BOOL, branch: STR, "root-dir": STR, "no-previews": BOOL, "require-pr": BOOL, installation: STR, agent: STR }, allowPositionals: true });
|
|
235
230
|
const json = Boolean(values.json);
|
|
236
231
|
if (sub === "connect")
|
|
237
232
|
return githubConnect({ json });
|
|
238
233
|
if (sub === "link")
|
|
239
|
-
return githubLink(positionals[0], { json, "no-previews": Boolean(values["no-previews"]), "require-pr": Boolean(values["require-pr"]), ...(values.branch ? { branch: values.branch } : {}), ...(values["root-dir"] ? { "root-dir": values["root-dir"] } : {}), ...(values.installation ? { installation: values.installation } : {}) });
|
|
234
|
+
return githubLink(positionals[0], { json, "no-previews": Boolean(values["no-previews"]), "require-pr": Boolean(values["require-pr"]), ...(values.agent ? { agent: String(values.agent) } : {}), ...(values.branch ? { branch: values.branch } : {}), ...(values["root-dir"] ? { "root-dir": values["root-dir"] } : {}), ...(values.installation ? { installation: values.installation } : {}) });
|
|
240
235
|
if (sub === "unlink")
|
|
241
236
|
return githubUnlink({ json });
|
|
242
237
|
if (sub === "status" || sub === undefined)
|
package/dist/util.d.ts
CHANGED
|
@@ -19,3 +19,7 @@ export declare function runOrThrow(cmd: string, args: string[], opts?: {
|
|
|
19
19
|
}): Promise<void>;
|
|
20
20
|
export declare function printJson(value: unknown): void;
|
|
21
21
|
export declare function table(rows: Record<string, unknown>[], columns: string[]): void;
|
|
22
|
+
/** The dashboard that goes with an API origin (https://api.nerdsystem.com -> https://nerdsystem.com). */
|
|
23
|
+
export declare function dashboardUrl(apiUrl: string): string;
|
|
24
|
+
/** Stops with one sentence instead of npx noise when `pnpm install` has not been run. */
|
|
25
|
+
export declare function ensureDeps(root: string): void;
|
package/dist/util.js
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { spawn } from "node:child_process";
|
|
2
|
+
import { existsSync } from "node:fs";
|
|
3
|
+
import path from "node:path";
|
|
2
4
|
export class CliError extends Error {
|
|
3
5
|
exitCode;
|
|
4
6
|
constructor(message, exitCode = 1) {
|
|
@@ -37,3 +39,12 @@ export function table(rows, columns) {
|
|
|
37
39
|
for (const r of rows)
|
|
38
40
|
process.stdout.write(`${line(columns.map((c) => String(r[c] ?? "")))}\n`);
|
|
39
41
|
}
|
|
42
|
+
/** The dashboard that goes with an API origin (https://api.nerdsystem.com -> https://nerdsystem.com). */
|
|
43
|
+
export function dashboardUrl(apiUrl) {
|
|
44
|
+
return apiUrl.replace(/\/$/, "").replace(/^https?:\/\/api\./, "https://");
|
|
45
|
+
}
|
|
46
|
+
/** Stops with one sentence instead of npx noise when `pnpm install` has not been run. */
|
|
47
|
+
export function ensureDeps(root) {
|
|
48
|
+
if (!existsSync(path.join(root, "node_modules")))
|
|
49
|
+
throw new CliError(`Dependencies are not installed. Run \`pnpm install\` in ${root} first (install pnpm with \`npm install -g pnpm\` if it is missing).`);
|
|
50
|
+
}
|
package/package.json
CHANGED
|
@@ -15,7 +15,7 @@ If this project still has the template's todo app (the `todos` table and `src/se
|
|
|
15
15
|
2. Say what you will build first, in plain words: the one table and the one screen that make the idea real. Keep the first feature small enough to deploy within the session.
|
|
16
16
|
3. Replace the todo example with that feature: schema, migration (`pnpm db:generate`), a router under `/api/...`, and the client screen. The template is a normal website: a public homepage at `/`, auth pages at `/login`, `/signup`, `/forgot-password`, `/reset-password`, and a signed-in area. Reshape it to the idea (a public landing page with sign-in in the header, a members-only area, an admin section) rather than forcing a login wall; the forms already work.
|
|
17
17
|
4. Run `nerd check`, then `nerd dev`, and tell the person to open the local URL, create an account, and try it. Fix what they report.
|
|
18
|
-
5. Run `nerd deploy` and give them the live URL. Explain in two lines that every deploy is checked for leaked secrets and vulnerable dependencies, and that they can run `nerd
|
|
18
|
+
5. Run `nerd deploy` and give them the live URL. Tell them to sign up on their own app first, then make themselves admin in the dashboard's Auth tab (the URL is printed after the deploy). Explain in two lines that every deploy is checked for leaked secrets and vulnerable dependencies, and that they can run `nerd check` any time.
|
|
19
19
|
6. Offer the next steps once, briefly: link the GitHub repo for previews on pull requests (`nerd github connect`), add a custom domain, or invite a teammate from the dashboard.
|
|
20
20
|
|
|
21
21
|
Throughout, show commands rather than describing them, and never ask the person to edit config files; the platform owns `wrangler.jsonc` and `nerd.json`.
|
|
@@ -32,7 +32,7 @@ Run `nerd check --json` when you want a machine-readable list of problems.
|
|
|
32
32
|
|
|
33
33
|
## Deploying from GitHub
|
|
34
34
|
|
|
35
|
-
Once the repo is linked (`nerd github connect`, then `nerd github link owner/repo`), every push to the production branch builds and deploys automatically, and every pull request gets a preview at its own URL, posted as a PR comment. `nerd builds` lists builds; `nerd builds show <id>` prints the log when one fails. A failing build reports the same `nerd check` problems you would see locally, so fix and push again.
|
|
35
|
+
Once the repo is linked (`nerd github connect`, then `nerd github link owner/repo`), every push to the production branch builds and deploys automatically, and every pull request gets a preview at its own URL, posted as a PR comment. `nerd builds` lists builds; `nerd builds show <id>` prints the log when one fails. A failing build reports the same `nerd check` problems you would see locally, so fix and push again. Every preview gets its own database and bucket, and pending migrations are applied to it on each preview build, so a PR that changes the schema can be tried end to end before it merges.
|
|
36
36
|
|
|
37
37
|
## Where things go
|
|
38
38
|
|
|
@@ -99,7 +99,9 @@ The loop: run `run_gates`, fix every failure with `audience: "agent"`, run `run_
|
|
|
99
99
|
- `audience: "human"` means a person must act: a secret that was committed must be rotated at the provider before you remove it; an ignore comment, ignore file, or gate setting you added is a suppression that only a person can approve in the dashboard. Never add `nosemgrep`, `gitleaks:allow`, `nerd-ignore`, or a `gates` key in `nerd.json` to make a finding go away.
|
|
100
100
|
- When `stop` is non-empty, the same rule has failed three runs in a row. Stop, summarize what you tried in plain words, and hand it to the person.
|
|
101
101
|
|
|
102
|
-
|
|
102
|
+
When a git build fails after a push, the same findings are posted back to the repository for you: as a comment on the pull request, or as a GitHub issue titled "nerdsystem refused <sha>" for a push to the production branch (`nerd builds show <id>` prints them too). Treat that text as the task: apply each fix, run the gates until clean, commit naming the rule ids, push. The issue closes itself when the next deploy succeeds. If the repository is linked with `--agent @claude` (or another handle), the post starts with that mention so a cloud agent is summoned without anyone pasting anything.
|
|
103
|
+
|
|
104
|
+
Without MCP, `nerd check --json` returns the same shape, and `nerd check --rule <rule_id>` runs one rule (`nerd verify` is the same command). Secrets are reported masked (`sk_live_****4f2a`); never print a credential yourself. Keep secrets in `.dev.vars` locally and in platform settings in production; never prefix a secret with `VITE_`, because those values are compiled into the public client bundle.
|
|
103
105
|
|
|
104
106
|
## What `nerd check` rejects
|
|
105
107
|
|
|
@@ -112,7 +114,7 @@ Without MCP, `nerd verify --json` returns the same shape, and `nerd verify --rul
|
|
|
112
114
|
## Scheduled jobs and email
|
|
113
115
|
|
|
114
116
|
- **Scheduled jobs**: put the work in the `POST /api/cron` handler (already in `src/server/index.ts`, guarded by `verifyCron()`), then turn the schedule on with `nerd cron hourly` or `nerd cron daily`. The platform calls it; the app never needs its own timer.
|
|
115
|
-
- **Secrets** (Stripe keys, API tokens): never in code or in the client. Locally they live in `.dev.vars` (gitignored). For production, ask the person to run `nerd secret set NAME` and paste the value; it reaches the app as `c.env.NAME` on the next deploy. `nerd secret list` shows names only. Do not put a real secret value in any command you run yourself.
|
|
117
|
+
- **Secrets** (Stripe keys, API tokens): never in code or in the client. Locally they live in `.dev.vars` (gitignored). For production, ask the person to run `nerd secret set NAME` and paste the value; it reaches the app as `c.env.NAME` on the next deploy. Pull request previews do not get it unless it was set with `--previews` (use that only for keys that are safe in a preview, such as a test-mode key). `nerd secret list` shows names only. Do not put a real secret value in any command you run yourself.
|
|
116
118
|
- **Email**: `sendEmail(c.env, { to, subject, text, html? })` from `@nerdsystem/sdk` sends through the platform. It is limited per day by plan (50 on Free, 2,000 on Pro at $39 a month, 10,000 on Studio), so use it for transactional mail such as receipts and notifications, not bulk. Plans can be paid monthly or yearly at ten times the monthly price.
|
|
117
119
|
|
|
118
120
|
## Debugging production
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
description: How to build, run, and deploy this app on nerdsystem. Applies to server, database, auth, and deploy work.
|
|
3
3
|
alwaysApply: true
|
|
4
4
|
---
|
|
5
|
-
<!-- generated from .claude/skills/nerdsystem/SKILL.md (
|
|
5
|
+
<!-- generated from .claude/skills/nerdsystem/SKILL.md (b1d847104ce0); edit the skill, then run: nerd agents sync -->
|
|
6
6
|
|
|
7
7
|
# Building on nerdsystem
|
|
8
8
|
|
|
@@ -16,7 +16,7 @@ If this project still has the template's todo app (the `todos` table and `src/se
|
|
|
16
16
|
2. Say what you will build first, in plain words: the one table and the one screen that make the idea real. Keep the first feature small enough to deploy within the session.
|
|
17
17
|
3. Replace the todo example with that feature: schema, migration (`pnpm db:generate`), a router under `/api/...`, and the client screen. The template is a normal website: a public homepage at `/`, auth pages at `/login`, `/signup`, `/forgot-password`, `/reset-password`, and a signed-in area. Reshape it to the idea (a public landing page with sign-in in the header, a members-only area, an admin section) rather than forcing a login wall; the forms already work.
|
|
18
18
|
4. Run `nerd check`, then `nerd dev`, and tell the person to open the local URL, create an account, and try it. Fix what they report.
|
|
19
|
-
5. Run `nerd deploy` and give them the live URL. Explain in two lines that every deploy is checked for leaked secrets and vulnerable dependencies, and that they can run `nerd
|
|
19
|
+
5. Run `nerd deploy` and give them the live URL. Tell them to sign up on their own app first, then make themselves admin in the dashboard's Auth tab (the URL is printed after the deploy). Explain in two lines that every deploy is checked for leaked secrets and vulnerable dependencies, and that they can run `nerd check` any time.
|
|
20
20
|
6. Offer the next steps once, briefly: link the GitHub repo for previews on pull requests (`nerd github connect`), add a custom domain, or invite a teammate from the dashboard.
|
|
21
21
|
|
|
22
22
|
Throughout, show commands rather than describing them, and never ask the person to edit config files; the platform owns `wrangler.jsonc` and `nerd.json`.
|
|
@@ -33,7 +33,7 @@ Run `nerd check --json` when you want a machine-readable list of problems.
|
|
|
33
33
|
|
|
34
34
|
## Deploying from GitHub
|
|
35
35
|
|
|
36
|
-
Once the repo is linked (`nerd github connect`, then `nerd github link owner/repo`), every push to the production branch builds and deploys automatically, and every pull request gets a preview at its own URL, posted as a PR comment. `nerd builds` lists builds; `nerd builds show <id>` prints the log when one fails. A failing build reports the same `nerd check` problems you would see locally, so fix and push again.
|
|
36
|
+
Once the repo is linked (`nerd github connect`, then `nerd github link owner/repo`), every push to the production branch builds and deploys automatically, and every pull request gets a preview at its own URL, posted as a PR comment. `nerd builds` lists builds; `nerd builds show <id>` prints the log when one fails. A failing build reports the same `nerd check` problems you would see locally, so fix and push again. Every preview gets its own database and bucket, and pending migrations are applied to it on each preview build, so a PR that changes the schema can be tried end to end before it merges.
|
|
37
37
|
|
|
38
38
|
## Where things go
|
|
39
39
|
|
|
@@ -100,7 +100,9 @@ The loop: run `run_gates`, fix every failure with `audience: "agent"`, run `run_
|
|
|
100
100
|
- `audience: "human"` means a person must act: a secret that was committed must be rotated at the provider before you remove it; an ignore comment, ignore file, or gate setting you added is a suppression that only a person can approve in the dashboard. Never add `nosemgrep`, `gitleaks:allow`, `nerd-ignore`, or a `gates` key in `nerd.json` to make a finding go away.
|
|
101
101
|
- When `stop` is non-empty, the same rule has failed three runs in a row. Stop, summarize what you tried in plain words, and hand it to the person.
|
|
102
102
|
|
|
103
|
-
|
|
103
|
+
When a git build fails after a push, the same findings are posted back to the repository for you: as a comment on the pull request, or as a GitHub issue titled "nerdsystem refused <sha>" for a push to the production branch (`nerd builds show <id>` prints them too). Treat that text as the task: apply each fix, run the gates until clean, commit naming the rule ids, push. The issue closes itself when the next deploy succeeds. If the repository is linked with `--agent @claude` (or another handle), the post starts with that mention so a cloud agent is summoned without anyone pasting anything.
|
|
104
|
+
|
|
105
|
+
Without MCP, `nerd check --json` returns the same shape, and `nerd check --rule <rule_id>` runs one rule (`nerd verify` is the same command). Secrets are reported masked (`sk_live_****4f2a`); never print a credential yourself. Keep secrets in `.dev.vars` locally and in platform settings in production; never prefix a secret with `VITE_`, because those values are compiled into the public client bundle.
|
|
104
106
|
|
|
105
107
|
## What `nerd check` rejects
|
|
106
108
|
|
|
@@ -113,7 +115,7 @@ Without MCP, `nerd verify --json` returns the same shape, and `nerd verify --rul
|
|
|
113
115
|
## Scheduled jobs and email
|
|
114
116
|
|
|
115
117
|
- **Scheduled jobs**: put the work in the `POST /api/cron` handler (already in `src/server/index.ts`, guarded by `verifyCron()`), then turn the schedule on with `nerd cron hourly` or `nerd cron daily`. The platform calls it; the app never needs its own timer.
|
|
116
|
-
- **Secrets** (Stripe keys, API tokens): never in code or in the client. Locally they live in `.dev.vars` (gitignored). For production, ask the person to run `nerd secret set NAME` and paste the value; it reaches the app as `c.env.NAME` on the next deploy. `nerd secret list` shows names only. Do not put a real secret value in any command you run yourself.
|
|
118
|
+
- **Secrets** (Stripe keys, API tokens): never in code or in the client. Locally they live in `.dev.vars` (gitignored). For production, ask the person to run `nerd secret set NAME` and paste the value; it reaches the app as `c.env.NAME` on the next deploy. Pull request previews do not get it unless it was set with `--previews` (use that only for keys that are safe in a preview, such as a test-mode key). `nerd secret list` shows names only. Do not put a real secret value in any command you run yourself.
|
|
117
119
|
- **Email**: `sendEmail(c.env, { to, subject, text, html? })` from `@nerdsystem/sdk` sends through the platform. It is limited per day by plan (50 on Free, 2,000 on Pro at $39 a month, 10,000 on Studio), so use it for transactional mail such as receipts and notifications, not bulk. Plans can be paid monthly or yearly at ten times the monthly price.
|
|
118
120
|
|
|
119
121
|
## Debugging production
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
<!-- generated from .claude/skills/nerdsystem/SKILL.md (
|
|
1
|
+
<!-- generated from .claude/skills/nerdsystem/SKILL.md (b1d847104ce0); edit the skill, then run: nerd agents sync -->
|
|
2
2
|
|
|
3
3
|
# Building on nerdsystem
|
|
4
4
|
|
|
@@ -12,7 +12,7 @@ If this project still has the template's todo app (the `todos` table and `src/se
|
|
|
12
12
|
2. Say what you will build first, in plain words: the one table and the one screen that make the idea real. Keep the first feature small enough to deploy within the session.
|
|
13
13
|
3. Replace the todo example with that feature: schema, migration (`pnpm db:generate`), a router under `/api/...`, and the client screen. The template is a normal website: a public homepage at `/`, auth pages at `/login`, `/signup`, `/forgot-password`, `/reset-password`, and a signed-in area. Reshape it to the idea (a public landing page with sign-in in the header, a members-only area, an admin section) rather than forcing a login wall; the forms already work.
|
|
14
14
|
4. Run `nerd check`, then `nerd dev`, and tell the person to open the local URL, create an account, and try it. Fix what they report.
|
|
15
|
-
5. Run `nerd deploy` and give them the live URL. Explain in two lines that every deploy is checked for leaked secrets and vulnerable dependencies, and that they can run `nerd
|
|
15
|
+
5. Run `nerd deploy` and give them the live URL. Tell them to sign up on their own app first, then make themselves admin in the dashboard's Auth tab (the URL is printed after the deploy). Explain in two lines that every deploy is checked for leaked secrets and vulnerable dependencies, and that they can run `nerd check` any time.
|
|
16
16
|
6. Offer the next steps once, briefly: link the GitHub repo for previews on pull requests (`nerd github connect`), add a custom domain, or invite a teammate from the dashboard.
|
|
17
17
|
|
|
18
18
|
Throughout, show commands rather than describing them, and never ask the person to edit config files; the platform owns `wrangler.jsonc` and `nerd.json`.
|
|
@@ -29,7 +29,7 @@ Run `nerd check --json` when you want a machine-readable list of problems.
|
|
|
29
29
|
|
|
30
30
|
## Deploying from GitHub
|
|
31
31
|
|
|
32
|
-
Once the repo is linked (`nerd github connect`, then `nerd github link owner/repo`), every push to the production branch builds and deploys automatically, and every pull request gets a preview at its own URL, posted as a PR comment. `nerd builds` lists builds; `nerd builds show <id>` prints the log when one fails. A failing build reports the same `nerd check` problems you would see locally, so fix and push again.
|
|
32
|
+
Once the repo is linked (`nerd github connect`, then `nerd github link owner/repo`), every push to the production branch builds and deploys automatically, and every pull request gets a preview at its own URL, posted as a PR comment. `nerd builds` lists builds; `nerd builds show <id>` prints the log when one fails. A failing build reports the same `nerd check` problems you would see locally, so fix and push again. Every preview gets its own database and bucket, and pending migrations are applied to it on each preview build, so a PR that changes the schema can be tried end to end before it merges.
|
|
33
33
|
|
|
34
34
|
## Where things go
|
|
35
35
|
|
|
@@ -96,7 +96,9 @@ The loop: run `run_gates`, fix every failure with `audience: "agent"`, run `run_
|
|
|
96
96
|
- `audience: "human"` means a person must act: a secret that was committed must be rotated at the provider before you remove it; an ignore comment, ignore file, or gate setting you added is a suppression that only a person can approve in the dashboard. Never add `nosemgrep`, `gitleaks:allow`, `nerd-ignore`, or a `gates` key in `nerd.json` to make a finding go away.
|
|
97
97
|
- When `stop` is non-empty, the same rule has failed three runs in a row. Stop, summarize what you tried in plain words, and hand it to the person.
|
|
98
98
|
|
|
99
|
-
|
|
99
|
+
When a git build fails after a push, the same findings are posted back to the repository for you: as a comment on the pull request, or as a GitHub issue titled "nerdsystem refused <sha>" for a push to the production branch (`nerd builds show <id>` prints them too). Treat that text as the task: apply each fix, run the gates until clean, commit naming the rule ids, push. The issue closes itself when the next deploy succeeds. If the repository is linked with `--agent @claude` (or another handle), the post starts with that mention so a cloud agent is summoned without anyone pasting anything.
|
|
100
|
+
|
|
101
|
+
Without MCP, `nerd check --json` returns the same shape, and `nerd check --rule <rule_id>` runs one rule (`nerd verify` is the same command). Secrets are reported masked (`sk_live_****4f2a`); never print a credential yourself. Keep secrets in `.dev.vars` locally and in platform settings in production; never prefix a secret with `VITE_`, because those values are compiled into the public client bundle.
|
|
100
102
|
|
|
101
103
|
## What `nerd check` rejects
|
|
102
104
|
|
|
@@ -109,7 +111,7 @@ Without MCP, `nerd verify --json` returns the same shape, and `nerd verify --rul
|
|
|
109
111
|
## Scheduled jobs and email
|
|
110
112
|
|
|
111
113
|
- **Scheduled jobs**: put the work in the `POST /api/cron` handler (already in `src/server/index.ts`, guarded by `verifyCron()`), then turn the schedule on with `nerd cron hourly` or `nerd cron daily`. The platform calls it; the app never needs its own timer.
|
|
112
|
-
- **Secrets** (Stripe keys, API tokens): never in code or in the client. Locally they live in `.dev.vars` (gitignored). For production, ask the person to run `nerd secret set NAME` and paste the value; it reaches the app as `c.env.NAME` on the next deploy. `nerd secret list` shows names only. Do not put a real secret value in any command you run yourself.
|
|
114
|
+
- **Secrets** (Stripe keys, API tokens): never in code or in the client. Locally they live in `.dev.vars` (gitignored). For production, ask the person to run `nerd secret set NAME` and paste the value; it reaches the app as `c.env.NAME` on the next deploy. Pull request previews do not get it unless it was set with `--previews` (use that only for keys that are safe in a preview, such as a test-mode key). `nerd secret list` shows names only. Do not put a real secret value in any command you run yourself.
|
|
113
115
|
- **Email**: `sendEmail(c.env, { to, subject, text, html? })` from `@nerdsystem/sdk` sends through the platform. It is limited per day by plan (50 on Free, 2,000 on Pro at $39 a month, 10,000 on Studio), so use it for transactional mail such as receipts and notifications, not bulk. Plans can be paid monthly or yearly at ten times the monthly price.
|
|
114
116
|
|
|
115
117
|
## Debugging production
|
package/template/AGENTS.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
<!-- generated from .claude/skills/nerdsystem/SKILL.md (
|
|
1
|
+
<!-- generated from .claude/skills/nerdsystem/SKILL.md (b1d847104ce0); edit the skill, then run: nerd agents sync -->
|
|
2
2
|
|
|
3
3
|
# Building on nerdsystem
|
|
4
4
|
|
|
@@ -12,7 +12,7 @@ If this project still has the template's todo app (the `todos` table and `src/se
|
|
|
12
12
|
2. Say what you will build first, in plain words: the one table and the one screen that make the idea real. Keep the first feature small enough to deploy within the session.
|
|
13
13
|
3. Replace the todo example with that feature: schema, migration (`pnpm db:generate`), a router under `/api/...`, and the client screen. The template is a normal website: a public homepage at `/`, auth pages at `/login`, `/signup`, `/forgot-password`, `/reset-password`, and a signed-in area. Reshape it to the idea (a public landing page with sign-in in the header, a members-only area, an admin section) rather than forcing a login wall; the forms already work.
|
|
14
14
|
4. Run `nerd check`, then `nerd dev`, and tell the person to open the local URL, create an account, and try it. Fix what they report.
|
|
15
|
-
5. Run `nerd deploy` and give them the live URL. Explain in two lines that every deploy is checked for leaked secrets and vulnerable dependencies, and that they can run `nerd
|
|
15
|
+
5. Run `nerd deploy` and give them the live URL. Tell them to sign up on their own app first, then make themselves admin in the dashboard's Auth tab (the URL is printed after the deploy). Explain in two lines that every deploy is checked for leaked secrets and vulnerable dependencies, and that they can run `nerd check` any time.
|
|
16
16
|
6. Offer the next steps once, briefly: link the GitHub repo for previews on pull requests (`nerd github connect`), add a custom domain, or invite a teammate from the dashboard.
|
|
17
17
|
|
|
18
18
|
Throughout, show commands rather than describing them, and never ask the person to edit config files; the platform owns `wrangler.jsonc` and `nerd.json`.
|
|
@@ -29,7 +29,7 @@ Run `nerd check --json` when you want a machine-readable list of problems.
|
|
|
29
29
|
|
|
30
30
|
## Deploying from GitHub
|
|
31
31
|
|
|
32
|
-
Once the repo is linked (`nerd github connect`, then `nerd github link owner/repo`), every push to the production branch builds and deploys automatically, and every pull request gets a preview at its own URL, posted as a PR comment. `nerd builds` lists builds; `nerd builds show <id>` prints the log when one fails. A failing build reports the same `nerd check` problems you would see locally, so fix and push again.
|
|
32
|
+
Once the repo is linked (`nerd github connect`, then `nerd github link owner/repo`), every push to the production branch builds and deploys automatically, and every pull request gets a preview at its own URL, posted as a PR comment. `nerd builds` lists builds; `nerd builds show <id>` prints the log when one fails. A failing build reports the same `nerd check` problems you would see locally, so fix and push again. Every preview gets its own database and bucket, and pending migrations are applied to it on each preview build, so a PR that changes the schema can be tried end to end before it merges.
|
|
33
33
|
|
|
34
34
|
## Where things go
|
|
35
35
|
|
|
@@ -96,7 +96,9 @@ The loop: run `run_gates`, fix every failure with `audience: "agent"`, run `run_
|
|
|
96
96
|
- `audience: "human"` means a person must act: a secret that was committed must be rotated at the provider before you remove it; an ignore comment, ignore file, or gate setting you added is a suppression that only a person can approve in the dashboard. Never add `nosemgrep`, `gitleaks:allow`, `nerd-ignore`, or a `gates` key in `nerd.json` to make a finding go away.
|
|
97
97
|
- When `stop` is non-empty, the same rule has failed three runs in a row. Stop, summarize what you tried in plain words, and hand it to the person.
|
|
98
98
|
|
|
99
|
-
|
|
99
|
+
When a git build fails after a push, the same findings are posted back to the repository for you: as a comment on the pull request, or as a GitHub issue titled "nerdsystem refused <sha>" for a push to the production branch (`nerd builds show <id>` prints them too). Treat that text as the task: apply each fix, run the gates until clean, commit naming the rule ids, push. The issue closes itself when the next deploy succeeds. If the repository is linked with `--agent @claude` (or another handle), the post starts with that mention so a cloud agent is summoned without anyone pasting anything.
|
|
100
|
+
|
|
101
|
+
Without MCP, `nerd check --json` returns the same shape, and `nerd check --rule <rule_id>` runs one rule (`nerd verify` is the same command). Secrets are reported masked (`sk_live_****4f2a`); never print a credential yourself. Keep secrets in `.dev.vars` locally and in platform settings in production; never prefix a secret with `VITE_`, because those values are compiled into the public client bundle.
|
|
100
102
|
|
|
101
103
|
## What `nerd check` rejects
|
|
102
104
|
|
|
@@ -109,7 +111,7 @@ Without MCP, `nerd verify --json` returns the same shape, and `nerd verify --rul
|
|
|
109
111
|
## Scheduled jobs and email
|
|
110
112
|
|
|
111
113
|
- **Scheduled jobs**: put the work in the `POST /api/cron` handler (already in `src/server/index.ts`, guarded by `verifyCron()`), then turn the schedule on with `nerd cron hourly` or `nerd cron daily`. The platform calls it; the app never needs its own timer.
|
|
112
|
-
- **Secrets** (Stripe keys, API tokens): never in code or in the client. Locally they live in `.dev.vars` (gitignored). For production, ask the person to run `nerd secret set NAME` and paste the value; it reaches the app as `c.env.NAME` on the next deploy. `nerd secret list` shows names only. Do not put a real secret value in any command you run yourself.
|
|
114
|
+
- **Secrets** (Stripe keys, API tokens): never in code or in the client. Locally they live in `.dev.vars` (gitignored). For production, ask the person to run `nerd secret set NAME` and paste the value; it reaches the app as `c.env.NAME` on the next deploy. Pull request previews do not get it unless it was set with `--previews` (use that only for keys that are safe in a preview, such as a test-mode key). `nerd secret list` shows names only. Do not put a real secret value in any command you run yourself.
|
|
113
115
|
- **Email**: `sendEmail(c.env, { to, subject, text, html? })` from `@nerdsystem/sdk` sends through the platform. It is limited per day by plan (50 on Free, 2,000 on Pro at $39 a month, 10,000 on Studio), so use it for transactional mail such as receipts and notifications, not bulk. Plans can be paid monthly or yearly at ten times the monthly price.
|
|
114
116
|
|
|
115
117
|
## Debugging production
|
package/template/GEMINI.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
<!-- generated from .claude/skills/nerdsystem/SKILL.md (
|
|
1
|
+
<!-- generated from .claude/skills/nerdsystem/SKILL.md (b1d847104ce0); edit the skill, then run: nerd agents sync -->
|
|
2
2
|
|
|
3
3
|
# Building on nerdsystem
|
|
4
4
|
|
|
@@ -12,7 +12,7 @@ If this project still has the template's todo app (the `todos` table and `src/se
|
|
|
12
12
|
2. Say what you will build first, in plain words: the one table and the one screen that make the idea real. Keep the first feature small enough to deploy within the session.
|
|
13
13
|
3. Replace the todo example with that feature: schema, migration (`pnpm db:generate`), a router under `/api/...`, and the client screen. The template is a normal website: a public homepage at `/`, auth pages at `/login`, `/signup`, `/forgot-password`, `/reset-password`, and a signed-in area. Reshape it to the idea (a public landing page with sign-in in the header, a members-only area, an admin section) rather than forcing a login wall; the forms already work.
|
|
14
14
|
4. Run `nerd check`, then `nerd dev`, and tell the person to open the local URL, create an account, and try it. Fix what they report.
|
|
15
|
-
5. Run `nerd deploy` and give them the live URL. Explain in two lines that every deploy is checked for leaked secrets and vulnerable dependencies, and that they can run `nerd
|
|
15
|
+
5. Run `nerd deploy` and give them the live URL. Tell them to sign up on their own app first, then make themselves admin in the dashboard's Auth tab (the URL is printed after the deploy). Explain in two lines that every deploy is checked for leaked secrets and vulnerable dependencies, and that they can run `nerd check` any time.
|
|
16
16
|
6. Offer the next steps once, briefly: link the GitHub repo for previews on pull requests (`nerd github connect`), add a custom domain, or invite a teammate from the dashboard.
|
|
17
17
|
|
|
18
18
|
Throughout, show commands rather than describing them, and never ask the person to edit config files; the platform owns `wrangler.jsonc` and `nerd.json`.
|
|
@@ -29,7 +29,7 @@ Run `nerd check --json` when you want a machine-readable list of problems.
|
|
|
29
29
|
|
|
30
30
|
## Deploying from GitHub
|
|
31
31
|
|
|
32
|
-
Once the repo is linked (`nerd github connect`, then `nerd github link owner/repo`), every push to the production branch builds and deploys automatically, and every pull request gets a preview at its own URL, posted as a PR comment. `nerd builds` lists builds; `nerd builds show <id>` prints the log when one fails. A failing build reports the same `nerd check` problems you would see locally, so fix and push again.
|
|
32
|
+
Once the repo is linked (`nerd github connect`, then `nerd github link owner/repo`), every push to the production branch builds and deploys automatically, and every pull request gets a preview at its own URL, posted as a PR comment. `nerd builds` lists builds; `nerd builds show <id>` prints the log when one fails. A failing build reports the same `nerd check` problems you would see locally, so fix and push again. Every preview gets its own database and bucket, and pending migrations are applied to it on each preview build, so a PR that changes the schema can be tried end to end before it merges.
|
|
33
33
|
|
|
34
34
|
## Where things go
|
|
35
35
|
|
|
@@ -96,7 +96,9 @@ The loop: run `run_gates`, fix every failure with `audience: "agent"`, run `run_
|
|
|
96
96
|
- `audience: "human"` means a person must act: a secret that was committed must be rotated at the provider before you remove it; an ignore comment, ignore file, or gate setting you added is a suppression that only a person can approve in the dashboard. Never add `nosemgrep`, `gitleaks:allow`, `nerd-ignore`, or a `gates` key in `nerd.json` to make a finding go away.
|
|
97
97
|
- When `stop` is non-empty, the same rule has failed three runs in a row. Stop, summarize what you tried in plain words, and hand it to the person.
|
|
98
98
|
|
|
99
|
-
|
|
99
|
+
When a git build fails after a push, the same findings are posted back to the repository for you: as a comment on the pull request, or as a GitHub issue titled "nerdsystem refused <sha>" for a push to the production branch (`nerd builds show <id>` prints them too). Treat that text as the task: apply each fix, run the gates until clean, commit naming the rule ids, push. The issue closes itself when the next deploy succeeds. If the repository is linked with `--agent @claude` (or another handle), the post starts with that mention so a cloud agent is summoned without anyone pasting anything.
|
|
100
|
+
|
|
101
|
+
Without MCP, `nerd check --json` returns the same shape, and `nerd check --rule <rule_id>` runs one rule (`nerd verify` is the same command). Secrets are reported masked (`sk_live_****4f2a`); never print a credential yourself. Keep secrets in `.dev.vars` locally and in platform settings in production; never prefix a secret with `VITE_`, because those values are compiled into the public client bundle.
|
|
100
102
|
|
|
101
103
|
## What `nerd check` rejects
|
|
102
104
|
|
|
@@ -109,7 +111,7 @@ Without MCP, `nerd verify --json` returns the same shape, and `nerd verify --rul
|
|
|
109
111
|
## Scheduled jobs and email
|
|
110
112
|
|
|
111
113
|
- **Scheduled jobs**: put the work in the `POST /api/cron` handler (already in `src/server/index.ts`, guarded by `verifyCron()`), then turn the schedule on with `nerd cron hourly` or `nerd cron daily`. The platform calls it; the app never needs its own timer.
|
|
112
|
-
- **Secrets** (Stripe keys, API tokens): never in code or in the client. Locally they live in `.dev.vars` (gitignored). For production, ask the person to run `nerd secret set NAME` and paste the value; it reaches the app as `c.env.NAME` on the next deploy. `nerd secret list` shows names only. Do not put a real secret value in any command you run yourself.
|
|
114
|
+
- **Secrets** (Stripe keys, API tokens): never in code or in the client. Locally they live in `.dev.vars` (gitignored). For production, ask the person to run `nerd secret set NAME` and paste the value; it reaches the app as `c.env.NAME` on the next deploy. Pull request previews do not get it unless it was set with `--previews` (use that only for keys that are safe in a preview, such as a test-mode key). `nerd secret list` shows names only. Do not put a real secret value in any command you run yourself.
|
|
113
115
|
- **Email**: `sendEmail(c.env, { to, subject, text, html? })` from `@nerdsystem/sdk` sends through the platform. It is limited per day by plan (50 on Free, 2,000 on Pro at $39 a month, 10,000 on Studio), so use it for transactional mail such as receipts and notifications, not bulk. Plans can be paid monthly or yearly at ten times the monthly price.
|
|
114
116
|
|
|
115
117
|
## Debugging production
|
|
@@ -7,7 +7,7 @@ import { auth, ApiError, type User } from "./api";
|
|
|
7
7
|
import { navigate } from "./router";
|
|
8
8
|
|
|
9
9
|
// Optional bot protection on signup: set VITE_TURNSTILE_SITE_KEY (client) and NERD_TURNSTILE_SECRET
|
|
10
|
-
// (server, via `nerd
|
|
10
|
+
// (server, via `nerd secret set`) from a Cloudflare Turnstile widget. Without them, signup is open.
|
|
11
11
|
const TURNSTILE_SITE_KEY = import.meta.env.VITE_TURNSTILE_SITE_KEY as string | undefined;
|
|
12
12
|
|
|
13
13
|
function Turnstile({ onToken }: { onToken: (t: string) => void }) {
|