robodev 0.47.0 → 0.47.2

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "robodev",
3
- "version": "0.47.0",
3
+ "version": "0.47.2",
4
4
  "description": "CLI for Robodev Starbase — create, auth, link, deploy, load, and run a project locally with robodev dev",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -26,8 +26,8 @@
26
26
  "embedded-postgres": "17.10.0-beta.17",
27
27
  "fastify": "^5.12.1",
28
28
  "tsx": "^4.20.5",
29
- "@robodev-ai/sdk": "0.13.0",
30
- "@robodev-ai/runtime": "0.7.1"
29
+ "@robodev-ai/runtime": "0.7.1",
30
+ "@robodev-ai/sdk": "0.13.0"
31
31
  },
32
32
  "devDependencies": {
33
33
  "@types/node": "^24.3.0",
@@ -3,7 +3,13 @@ import { mkdir, mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
3
3
  import { tmpdir } from "node:os";
4
4
  import { join } from "node:path";
5
5
  import { test } from "node:test";
6
- import { defaultFrontendConfig, readLink, writeLink, writeViteApiUrl } from "./config.js";
6
+ import {
7
+ defaultFrontendConfig,
8
+ readLink,
9
+ resolveDeployKey,
10
+ writeLink,
11
+ writeViteApiUrl,
12
+ } from "./config.js";
7
13
 
8
14
  test("writeViteApiUrl sets Vite and Povio public API URLs", async () => {
9
15
  const root = await mkdtemp(join(tmpdir(), "rd-env-"));
@@ -159,6 +165,98 @@ test("writeLink preserves frontend false opt-out", async () => {
159
165
  }
160
166
  });
161
167
 
168
+ test("writeLink keeps an existing deployToken when rewriting other fields", async () => {
169
+ const root = await mkdtemp(join(tmpdir(), "rd-link-token-"));
170
+ try {
171
+ await mkdir(join(root, "robodev"), { recursive: true });
172
+ await writeFile(
173
+ join(root, "robodev/.robodev"),
174
+ `${JSON.stringify(
175
+ {
176
+ projectId: "prj-1",
177
+ apiUrl: "https://a.example",
178
+ projectApiUrl: "https://prj-1.example",
179
+ deployToken: "rdb_keep",
180
+ },
181
+ null,
182
+ 2,
183
+ )}\n`,
184
+ );
185
+ await writeLink({ projectId: "prj-1", apiUrl: "https://b.example" }, root);
186
+ const raw = JSON.parse(await readFile(join(root, "robodev/.robodev"), "utf8")) as {
187
+ deployToken?: string;
188
+ apiUrl: string;
189
+ };
190
+ assert.equal(raw.deployToken, "rdb_keep");
191
+ assert.equal(raw.apiUrl, "https://b.example");
192
+ const link = await readLink(root);
193
+ assert.equal(link && "deployToken" in link, false);
194
+ } finally {
195
+ await rm(root, { recursive: true, force: true });
196
+ }
197
+ });
198
+
199
+ test("resolveDeployKey prefers ROBODEV_DEPLOY_KEY over a file token", async () => {
200
+ const root = await mkdtemp(join(tmpdir(), "rd-key-env-"));
201
+ const previous = process.env.ROBODEV_DEPLOY_KEY;
202
+ try {
203
+ await mkdir(join(root, "robodev"), { recursive: true });
204
+ await writeFile(
205
+ join(root, "robodev/.robodev"),
206
+ `${JSON.stringify({ projectId: "prj-1", apiUrl: "https://a.example", deployToken: "rdb_file" }, null, 2)}\n`,
207
+ );
208
+ process.env.ROBODEV_DEPLOY_KEY = " rdb_env ";
209
+ assert.equal(await resolveDeployKey(root), "rdb_env");
210
+ } finally {
211
+ if (previous === undefined) delete process.env.ROBODEV_DEPLOY_KEY;
212
+ else process.env.ROBODEV_DEPLOY_KEY = previous;
213
+ await rm(root, { recursive: true, force: true });
214
+ }
215
+ });
216
+
217
+ test("resolveDeployKey reads deployToken from robodev/.robodev then legacy .robodev", async () => {
218
+ const root = await mkdtemp(join(tmpdir(), "rd-key-file-"));
219
+ const previous = process.env.ROBODEV_DEPLOY_KEY;
220
+ try {
221
+ delete process.env.ROBODEV_DEPLOY_KEY;
222
+ await writeFile(
223
+ join(root, ".robodev"),
224
+ `${JSON.stringify({ projectId: "legacy", apiUrl: "https://legacy.example", deployToken: "rdb_legacy" }, null, 2)}\n`,
225
+ );
226
+ assert.equal(await resolveDeployKey(root), "rdb_legacy");
227
+ await mkdir(join(root, "robodev"), { recursive: true });
228
+ await writeFile(
229
+ join(root, "robodev/.robodev"),
230
+ `${JSON.stringify({ projectId: "canonical", apiUrl: "https://canonical.example", deployToken: "rdb_canonical" }, null, 2)}\n`,
231
+ );
232
+ assert.equal(await resolveDeployKey(root), "rdb_canonical");
233
+ } finally {
234
+ if (previous === undefined) delete process.env.ROBODEV_DEPLOY_KEY;
235
+ else process.env.ROBODEV_DEPLOY_KEY = previous;
236
+ await rm(root, { recursive: true, force: true });
237
+ }
238
+ });
239
+
240
+ test("resolveDeployKey ignores blank env and file tokens", async () => {
241
+ const root = await mkdtemp(join(tmpdir(), "rd-key-blank-"));
242
+ const previous = process.env.ROBODEV_DEPLOY_KEY;
243
+ try {
244
+ process.env.ROBODEV_DEPLOY_KEY = " ";
245
+ await mkdir(join(root, "robodev"), { recursive: true });
246
+ await writeFile(
247
+ join(root, "robodev/.robodev"),
248
+ `${JSON.stringify({ projectId: "prj-1", apiUrl: "https://a.example", deployToken: " " }, null, 2)}\n`,
249
+ );
250
+ assert.equal(await resolveDeployKey(root), null);
251
+ delete process.env.ROBODEV_DEPLOY_KEY;
252
+ assert.equal(await resolveDeployKey(root), null);
253
+ } finally {
254
+ if (previous === undefined) delete process.env.ROBODEV_DEPLOY_KEY;
255
+ else process.env.ROBODEV_DEPLOY_KEY = previous;
256
+ await rm(root, { recursive: true, force: true });
257
+ }
258
+ });
259
+
162
260
  test("readLink prefers robodev/.robodev over a legacy root file", async () => {
163
261
  const root = await mkdtemp(join(tmpdir(), "rd-link-pref-"));
164
262
  try {
package/src/config.ts CHANGED
@@ -145,6 +145,23 @@ export async function readLink(cwd = process.cwd()): Promise<ProjectLink | null>
145
145
  return link;
146
146
  }
147
147
 
148
+ function trimNonEmpty(value: unknown): string | null {
149
+ if (typeof value !== "string") return null;
150
+ const trimmed = value.trim();
151
+ return trimmed.length > 0 ? trimmed : null;
152
+ }
153
+
154
+ /**
155
+ * Headless deploy credential. `ROBODEV_DEPLOY_KEY` wins over `deployToken` in the link file.
156
+ * Does not read or write `~/.robodev/credentials.json`. Never persist the env value.
157
+ */
158
+ export async function resolveDeployKey(cwd = process.cwd()): Promise<string | null> {
159
+ const fromEnv = trimNonEmpty(process.env.ROBODEV_DEPLOY_KEY);
160
+ if (fromEnv) return fromEnv;
161
+ const raw = await readLinkJson(cwd);
162
+ return trimNonEmpty(raw?.deployToken);
163
+ }
164
+
148
165
  export async function writeLink(link: ProjectLink, cwd = process.cwd()): Promise<void> {
149
166
  const existing = (await readLinkJson(cwd)) ?? {};
150
167
  const next: Record<string, unknown> = {
@@ -149,6 +149,7 @@ test("collectFiles for empty starter includes hosted Welcome source", async () =
149
149
  assert.ok(paths.has("robodev/api/health.ts"));
150
150
  assert.ok(paths.has("index.html"));
151
151
  assert.ok(paths.has("src/main.tsx"));
152
+ assert.ok(paths.has("ROBODEV.md"));
152
153
  assert.ok(!paths.has("robodev/api/planets.ts"));
153
154
  assert.ok([...paths].every((path) => !path.startsWith("apps/")));
154
155
  assert.ok(files.length < 200);
@@ -178,6 +179,8 @@ test("emitStarter shadcn includes hosted Welcome plus orval and shadcn files", a
178
179
  const notes = await readFile(join(dest, "ROBODEV.md"), "utf8");
179
180
  assert.match(notes, /react-router-dom/);
180
181
  assert.match(notes, /src\/pages\/Home\.tsx/);
182
+ assert.match(notes, /robodev-email templates list/);
183
+ assert.match(notes, /Do not write `robodev\/emails\/\*\.html`/);
181
184
  const endpoints = await readFile(join(dest, "src/api/generated/endpoints.ts"), "utf8");
182
185
  assert.match(endpoints, /export const getHealth/);
183
186
  assert.doesNotMatch(endpoints, /axios|@tanstack\/react-query/);
@@ -230,6 +233,7 @@ test("collectFiles for space starter is Tiny APIs without apps/fe", async () =>
230
233
  assert.ok(paths.has("robodev/api/planets.ts"));
231
234
  assert.ok(paths.has("robodev/api/planets/paginate.ts"));
232
235
  assert.ok(paths.has("robodev/api/aliens/labels.ts"));
236
+ assert.ok(paths.has("ROBODEV.md"));
233
237
  assert.ok(!paths.has("robodev/api/files/upload-request.ts"));
234
238
  assert.ok(!paths.has("robodev/api/files/upload.ts"));
235
239
  assert.ok(!paths.has("index.html"));
package/src/index.ts CHANGED
@@ -6,7 +6,7 @@ import { stdin, stdout } from "node:process";
6
6
  import { basename, join, relative, resolve } from "node:path";
7
7
  import { execFile, spawn } from "node:child_process";
8
8
  import { promisify } from "node:util";
9
- import { ApiError, authed, publicRequest } from "./api.js";
9
+ import { ApiError, authed, deployKeyRequest, publicRequest } from "./api.js";
10
10
  import {
11
11
  clearCredentials,
12
12
  defaultFrontendConfig,
@@ -16,6 +16,7 @@ import {
16
16
  type ProjectLink,
17
17
  readLink,
18
18
  readLinkJson,
19
+ resolveDeployKey,
19
20
  starbaseUrl,
20
21
  writeCredentials,
21
22
  writeLink,
@@ -45,6 +46,7 @@ import {
45
46
  import { parseCreateArgs } from "./parse-create-args.js";
46
47
  import { parseDeployArgs } from "./parse-deploy-args.js";
47
48
  import { ensureDeployLink } from "./ensure-deploy-link.js";
49
+ import { DEPLOY_NOT_AUTHENTICATED_MESSAGE, resolveDeployAuth } from "./resolve-deploy-auth.js";
48
50
  import { runLoad } from "./load.js";
49
51
  import { runMcp } from "./mcp.js";
50
52
  import { printLiveUrl } from "./print-live-url.js";
@@ -63,7 +65,7 @@ Commands:
63
65
  projects List your projects
64
66
  create [path] [--template <id>] [--starter <id>] [--empty] [--org <id>] Create a project, scaffold a starter, and deploy
65
67
  link [projectId] Write robodev/.robodev in the current folder
66
- deploy [--force] [--name <name>] [--org <id>] Deploy schema, APIs, jobs, and frontend (creates and links if unlinked)
68
+ deploy [--force] [--name <name>] [--org <id>] Deploy schema, APIs, jobs, and frontend (JWT, ROBODEV_DEPLOY_KEY, or file deployToken)
67
69
  load Download the project source onto this folder
68
70
  dev [options] Run this project's APIs locally on an embedded Postgres, no Starbase needed
69
71
  mcp [--project <id>] Serve this project's backend over MCP on stdio, for an external coding agent
@@ -236,6 +238,8 @@ type DeployOptions = {
236
238
  root?: string;
237
239
  frontendFiles?: CollectedFile[];
238
240
  skipFrontendBuild?: boolean;
241
+ /** `robodev create` stays JWT-only even if a leftover CI key is in the environment. */
242
+ auth?: "auto" | "jwt";
239
243
  };
240
244
 
241
245
  async function collectDeployPayload(
@@ -266,6 +270,21 @@ async function collectDeployPayload(
266
270
  return { files: mergeCollectedFiles(host, frontendFiles), source };
267
271
  }
268
272
 
273
+ async function deployAuthed<T>(path: string, init?: RequestInit): Promise<T> {
274
+ try {
275
+ return await authed<T>(path, init);
276
+ } catch (error) {
277
+ if (error instanceof Error && error.message.startsWith("Not authenticated.")) {
278
+ throw new Error(DEPLOY_NOT_AUTHENTICATED_MESSAGE);
279
+ }
280
+ throw error;
281
+ }
282
+ }
283
+
284
+ async function ensureJwtForDeploy(): Promise<void> {
285
+ await deployAuthed("/v1/auth/me");
286
+ }
287
+
269
288
  /** Uploads schema, APIs, and frontend. Destructive schema changes need confirmation or `--force`. */
270
289
  async function runDeploy(
271
290
  forceFlag: boolean,
@@ -273,34 +292,43 @@ async function runDeploy(
273
292
  flags: { name?: string; orgId?: string } = {},
274
293
  ): Promise<string | undefined> {
275
294
  const root = options.root ?? process.cwd();
276
- const link = await ensureDeployLink(
277
- { root, name: flags.name, orgId: flags.orgId, isTty: Boolean(stdin.isTTY) },
278
- {
279
- readLink,
280
- collectFiles,
281
- ensureAuth: async () => {
282
- await authed("/v1/auth/me");
283
- },
284
- resolveOrganization,
285
- createProject: (body) =>
286
- authed<Project>("/v1/projects", {
287
- method: "POST",
288
- body: JSON.stringify(body),
289
- }),
290
- writeLink,
291
- writeViteApiUrl,
292
- hasAppsFePackageJson,
293
- starbaseUrl,
294
- projectNameFromPath,
295
- promptName: (defaultName) => prompt(`Project name [${defaultName}]: `),
296
- },
297
- );
295
+ const auth =
296
+ options.auth === "jwt"
297
+ ? { kind: "jwt" as const }
298
+ : await resolveDeployAuth(root, { resolveDeployKey, readLink });
299
+ const link =
300
+ auth.kind === "key"
301
+ ? auth.link
302
+ : await ensureDeployLink(
303
+ { root, name: flags.name, orgId: flags.orgId, isTty: Boolean(stdin.isTTY) },
304
+ {
305
+ readLink,
306
+ collectFiles,
307
+ ensureAuth: ensureJwtForDeploy,
308
+ resolveOrganization,
309
+ createProject: (body) =>
310
+ authed<Project>("/v1/projects", {
311
+ method: "POST",
312
+ body: JSON.stringify(body),
313
+ }),
314
+ writeLink,
315
+ writeViteApiUrl,
316
+ hasAppsFePackageJson,
317
+ starbaseUrl,
318
+ projectNameFromPath,
319
+ promptName: (defaultName) => prompt(`Project name [${defaultName}]: `),
320
+ },
321
+ );
322
+ const request =
323
+ auth.kind === "key"
324
+ ? <T>(path: string, init?: RequestInit) => deployKeyRequest<T>(path, auth.key, init)
325
+ : deployAuthed;
298
326
  const { files, source } = await collectDeployPayload(root, link, options);
299
327
  let force = forceFlag;
300
328
 
301
329
  for (;;) {
302
330
  try {
303
- const result = await authed<{
331
+ const result = await request<{
304
332
  deploymentId: string;
305
333
  databaseName: string;
306
334
  databaseCreated?: boolean;
@@ -637,7 +665,12 @@ async function runCreate(args: string[]): Promise<void> {
637
665
  skipFrontendBuild = true;
638
666
  }
639
667
  }
640
- const liveUrl = await runDeploy(false, { root, frontendFiles, skipFrontendBuild });
668
+ const liveUrl = await runDeploy(false, {
669
+ root,
670
+ frontendFiles,
671
+ skipFrontendBuild,
672
+ auth: "jwt",
673
+ });
641
674
 
642
675
  try {
643
676
  await pnpmInstall(root);
@@ -0,0 +1,65 @@
1
+ import assert from "node:assert/strict";
2
+ import { test } from "node:test";
3
+ import type { ProjectLink } from "./config.js";
4
+ import {
5
+ DEPLOY_NOT_AUTHENTICATED_MESSAGE,
6
+ resolveDeployAuth,
7
+ UNLINKED_DEPLOY_KEY_MESSAGE,
8
+ } from "./resolve-deploy-auth.js";
9
+
10
+ const link: ProjectLink = {
11
+ projectId: "proj_1",
12
+ apiUrl: "https://robodev.povio.dev",
13
+ projectApiUrl: "https://proj_1.robodev.povio.dev",
14
+ };
15
+
16
+ test("resolveDeployAuth uses a key and existing link without creating", async () => {
17
+ const auth = await resolveDeployAuth("/tmp/app", {
18
+ resolveDeployKey: async () => "rdb_secret",
19
+ readLink: async () => link,
20
+ });
21
+ assert.deepEqual(auth, { kind: "key", key: "rdb_secret", link });
22
+ });
23
+
24
+ test("resolveDeployAuth refuses an unlinked key deploy", async () => {
25
+ await assert.rejects(
26
+ () =>
27
+ resolveDeployAuth("/tmp/unlinked", {
28
+ resolveDeployKey: async () => "rdb_secret",
29
+ readLink: async () => null,
30
+ }),
31
+ (error: unknown) => {
32
+ assert.ok(error instanceof Error);
33
+ assert.equal(error.message, UNLINKED_DEPLOY_KEY_MESSAGE);
34
+ assert.match(error.message, /cannot create projects/);
35
+ assert.match(error.message, /robodev\/\.robodev/);
36
+ return true;
37
+ },
38
+ );
39
+ });
40
+
41
+ test("resolveDeployAuth falls through to JWT when no key is resolved", async () => {
42
+ const auth = await resolveDeployAuth("/tmp/app", {
43
+ resolveDeployKey: async () => null,
44
+ readLink: async () => null,
45
+ });
46
+ assert.deepEqual(auth, { kind: "jwt" });
47
+ });
48
+
49
+ test("resolveDeployAuth prefers a key over an existing link that a JWT could also use", async () => {
50
+ const auth = await resolveDeployAuth("/tmp/app", {
51
+ resolveDeployKey: async () => "rdb_env",
52
+ readLink: async () => link,
53
+ });
54
+ assert.equal(auth.kind, "key");
55
+ if (auth.kind === "key") {
56
+ assert.equal(auth.key, "rdb_env");
57
+ }
58
+ });
59
+
60
+ test("deploy not-authenticated copy mentions auth, env, and file token — not load", () => {
61
+ assert.match(DEPLOY_NOT_AUTHENTICATED_MESSAGE, /robodev auth/);
62
+ assert.match(DEPLOY_NOT_AUTHENTICATED_MESSAGE, /ROBODEV_DEPLOY_KEY/);
63
+ assert.match(DEPLOY_NOT_AUTHENTICATED_MESSAGE, /deployToken/);
64
+ assert.equal(DEPLOY_NOT_AUTHENTICATED_MESSAGE.includes("load"), false);
65
+ });
@@ -0,0 +1,31 @@
1
+ import type { ProjectLink } from "./config.js";
2
+
3
+ export const UNLINKED_DEPLOY_KEY_MESSAGE =
4
+ "A deploy key cannot create projects. Link first (`robodev auth` + `robodev link` / `create`) or provide robodev/.robodev with projectId and apiUrl (no token required when ROBODEV_DEPLOY_KEY is set).";
5
+
6
+ export const DEPLOY_NOT_AUTHENTICATED_MESSAGE =
7
+ "Not authenticated. Run `robodev auth`, set ROBODEV_DEPLOY_KEY, or add deployToken to robodev/.robodev.";
8
+
9
+ export type DeployAuth = { kind: "key"; key: string; link: ProjectLink } | { kind: "jwt" };
10
+
11
+ export type ResolveDeployAuthDeps = {
12
+ resolveDeployKey: (cwd: string) => Promise<string | null>;
13
+ readLink: (cwd: string) => Promise<ProjectLink | null>;
14
+ };
15
+
16
+ /**
17
+ * Key deploy (env or file token) requires an existing link and never creates a project.
18
+ * No key → JWT path, including unlinked create-and-link.
19
+ */
20
+ export async function resolveDeployAuth(
21
+ root: string,
22
+ deps: ResolveDeployAuthDeps,
23
+ ): Promise<DeployAuth> {
24
+ const key = await deps.resolveDeployKey(root);
25
+ if (!key) return { kind: "jwt" };
26
+ const link = await deps.readLink(root);
27
+ if (!link) {
28
+ throw new Error(UNLINKED_DEPLOY_KEY_MESSAGE);
29
+ }
30
+ return { kind: "key", key, link };
31
+ }
@@ -1,6 +1,6 @@
1
1
  # {{NAME}}
2
2
 
3
- Starter from `robodev create --starter auth-chat`: Tiny chat APIs (`chat` database, `messages` table), authenticated invite email, and a Povio UI in `apps/fe`.
3
+ Starter from `robodev create --starter auth-chat`: Tiny chat APIs (`chat` database, `messages` table), authenticated invite email, and a Povio UI in `apps/fe`. Agents: read `ROBODEV.md`.
4
4
 
5
5
  After a CLI create or deploy with `.robodev` `frontend` set (the default for this starter), the project host serves the built Tiny SPA at `GET /`. APIs and Swagger still win over static files.
6
6
 
@@ -0,0 +1,75 @@
1
+ # Robodev
2
+
3
+ Use this file for Robodev and SDK facts. Do not web-search the SDK.
4
+
5
+ ## defineApi (copy-paste)
6
+
7
+ `db`, `email`, `storage`, and `env` are injected. Never `import { db } from "@robodev-ai/sdk"`.
8
+ The SDK is not installed in the runner. Do not `import("@robodev-ai/sdk")`. Do not search `/opt`, `node_modules`, or grok/gemini home for it.
9
+
10
+ ```ts
11
+ import { defineApi, z } from "@robodev-ai/sdk";
12
+
13
+ export const get = defineApi({
14
+ response: z.object({ ok: z.literal(true) }),
15
+ handler: async ({ db, email, storage, env }) => {
16
+ void db;
17
+ void email;
18
+ void storage;
19
+ void env;
20
+ return { ok: true as const };
21
+ },
22
+ });
23
+ ```
24
+
25
+ ## Hard rules
26
+
27
+ - Do not run npm, pnpm, or yarn install.
28
+ - Do not start npm run dev, vite, or any local server.
29
+ - Do not run tsc (node_modules is not installed in this runner).
30
+ - Do not port-scan or curl/fetch localhost.
31
+ - Preview is the WebContainer. Live APIs exist only after `robodev-publish be`.
32
+ - Publish backend after a batch of file edits, not after every file.
33
+ - Do not publish the frontend.
34
+
35
+ ## Auth and product data
36
+
37
+ Persist primary product data with `defineApi` and `robodev-publish be`. Do not use localStorage as the product store unless the user asked for a public, static, or no-account page. Auth is on by default: add login and register and protect routes unless the product is public or unauthenticated. Use `auth: "required"` and `ctx.user.id`. Call reserved `/api/user/auth/*` — do not implement `api/user/**`. Tiny: reuse seed login/register and AuthGuard. Shadcn, Empty, and legacy: create login/register with `@robodev-ai/client` tokens. A missing `.rulesync` skill is not a reason to skip APIs. Do not require `robodev-auth set` every run.
38
+
39
+ ## Backend
40
+
41
+ - Auth: robodev-auth get / set / users / users-delete. Do not invent control-plane HTTP.
42
+ - Secrets: robodev-secret list / set / delete. Use env.NAME in defineApi. List names only.
43
+ - SQL: psql or robodev-sql for DML, SELECT, and seed only. Never print DATABASE_URL. Never run DDL.
44
+ - Publish backend only with robodev-publish be. Do not publish the frontend.
45
+ - Email settings: robodev-email get / set / validate / logs.
46
+
47
+ ## Email templates
48
+
49
+ Hosted mail is the Email page. Changes apply immediately through these helpers — no Code save or deploy.
50
+
51
+ - `robodev-email templates list`
52
+ - `robodev-email templates get --slug <slug>`
53
+ - `robodev-email templates set --slug <slug> [--subject] [--html] [--text]`
54
+
55
+ `set` edits an existing slug. If the slug is missing and is a custom slug (`^[a-z][a-z0-9-]{0,62}$`, not `auth.*`), `set` creates it.
56
+
57
+ System slugs: `auth.welcome` (`{{email}}`, `{{name}}`), `auth.magic-link` (`{{link}}`, `{{email}}`, `{{name}}`, `{{appUrl}}`), `auth.forgot-password` (`{{link}}`, `{{code}}`, `{{email}}`, `{{name}}`, `{{appUrl}}`). Keep placeholders exact. Do not delete system templates. Do not invent `auth.*` slugs.
58
+
59
+ Send with `ctx.email.send({ template: "<slug>", to, vars })`.
60
+
61
+ Do not write `robodev/emails/*.html` or `robodev/emails/*.json` for hosted templates. Those files do not update the live project.
62
+
63
+ Never write `.env` or secrets. Never write `.robodev`.
64
+
65
+ ## Layout
66
+
67
+ CLI Tiny-family: `robodev/database.ts` + `robodev/api/**`, Povio UI in `apps/fe`. Keep that layout.
68
+
69
+ - Schema: `robodev/database.ts` (`defineDatabase` from `@robodev-ai/sdk`)
70
+ - HTTP handlers: `robodev/api/**/*.ts`
71
+ - Optional jobs / sockets / hooks under `robodev/`
72
+ - Frontend: `apps/fe` (Povio UI)
73
+ - Auth is reserved at `/api/user/*`. Do not add `robodev/api/user/**`.
74
+
75
+ Allowed paths: `robodev/database.ts`, `robodev/api/**/*.ts`, `robodev/jobs/**/*.ts`, `robodev/sockets/**/*.ts`, `robodev/hooks/**/*.ts`, `robodev/package.json`, `apps/fe/**`, `.rulesync/**`, `openapi.json`, `package.json`, `README.md`, `ROBODEV.md`, `.gitignore`.
@@ -12,7 +12,7 @@
12
12
  "@robodev-ai/sdk": "^0.13.0"
13
13
  },
14
14
  "devDependencies": {
15
- "robodev": "^0.47.0",
15
+ "robodev": "^0.47.2",
16
16
  "rulesync": "^8.18.0",
17
17
  "typescript": "^5.9.2"
18
18
  }
@@ -1,6 +1,6 @@
1
1
  # {{NAME}}
2
2
 
3
- Tiny Template planets, aliens, and media APIs — no Vite UI. For Lovable, Bolt, or v0.
3
+ Tiny Template planets, aliens, and media APIs — no Vite UI. For Lovable, Bolt, or v0. Agents: read `ROBODEV.md`.
4
4
 
5
5
  Deploy `robodev/database.ts` and `robodev/api/` from a real terminal:
6
6
 
@@ -0,0 +1,73 @@
1
+ # Robodev
2
+
3
+ Use this file for Robodev and SDK facts. Do not web-search the SDK.
4
+
5
+ ## defineApi (copy-paste)
6
+
7
+ `db`, `email`, `storage`, and `env` are injected. Never `import { db } from "@robodev-ai/sdk"`.
8
+ The SDK is not installed in the runner. Do not `import("@robodev-ai/sdk")`. Do not search `/opt`, `node_modules`, or grok/gemini home for it.
9
+
10
+ ```ts
11
+ import { defineApi, z } from "@robodev-ai/sdk";
12
+
13
+ export const get = defineApi({
14
+ response: z.object({ ok: z.literal(true) }),
15
+ handler: async ({ db, email, storage, env }) => {
16
+ void db;
17
+ void email;
18
+ void storage;
19
+ void env;
20
+ return { ok: true as const };
21
+ },
22
+ });
23
+ ```
24
+
25
+ ## Hard rules
26
+
27
+ - Do not run npm, pnpm, or yarn install.
28
+ - Do not start npm run dev, vite, or any local server.
29
+ - Do not run tsc (node_modules is not installed in this runner).
30
+ - Do not port-scan or curl/fetch localhost.
31
+ - Preview is the WebContainer. Live APIs exist only after `robodev-publish be`.
32
+ - Publish backend after a batch of file edits, not after every file.
33
+ - Do not publish the frontend.
34
+
35
+ ## Auth and product data
36
+
37
+ Persist primary product data with `defineApi` and `robodev-publish be`. Do not use localStorage as the product store unless the user asked for a public, static, or no-account page. Auth is on by default: add login and register and protect routes unless the product is public or unauthenticated. Use `auth: "required"` and `ctx.user.id`. Call reserved `/api/user/auth/*` — do not implement `api/user/**`. Tiny: reuse seed login/register and AuthGuard. Shadcn, Empty, and legacy: create login/register with `@robodev-ai/client` tokens. A missing `.rulesync` skill is not a reason to skip APIs. Do not require `robodev-auth set` every run.
38
+
39
+ ## Backend
40
+
41
+ - Auth: robodev-auth get / set / users / users-delete. Do not invent control-plane HTTP.
42
+ - Secrets: robodev-secret list / set / delete. Use env.NAME in defineApi. List names only.
43
+ - SQL: psql or robodev-sql for DML, SELECT, and seed only. Never print DATABASE_URL. Never run DDL.
44
+ - Publish backend only with robodev-publish be. Do not publish the frontend.
45
+ - Email settings: robodev-email get / set / validate / logs.
46
+
47
+ ## Email templates
48
+
49
+ Hosted mail is the Email page. Changes apply immediately through these helpers — no Code save or deploy.
50
+
51
+ - `robodev-email templates list`
52
+ - `robodev-email templates get --slug <slug>`
53
+ - `robodev-email templates set --slug <slug> [--subject] [--html] [--text]`
54
+
55
+ `set` edits an existing slug. If the slug is missing and is a custom slug (`^[a-z][a-z0-9-]{0,62}$`, not `auth.*`), `set` creates it.
56
+
57
+ System slugs: `auth.welcome` (`{{email}}`, `{{name}}`), `auth.magic-link` (`{{link}}`, `{{email}}`, `{{name}}`, `{{appUrl}}`), `auth.forgot-password` (`{{link}}`, `{{code}}`, `{{email}}`, `{{name}}`, `{{appUrl}}`). Keep placeholders exact. Do not delete system templates. Do not invent `auth.*` slugs.
58
+
59
+ Send with `ctx.email.send({ template: "<slug>", to, vars })`.
60
+
61
+ Do not write `robodev/emails/*.html` or `robodev/emails/*.json` for hosted templates. Those files do not update the live project.
62
+
63
+ Never write `.env` or secrets. Never write `.robodev`.
64
+
65
+ ## Layout
66
+
67
+ APIs only: `robodev/`. No Vite UI.
68
+
69
+ - Schema: `robodev/database.ts`
70
+ - HTTP handlers: `robodev/api/**/*.ts`
71
+ - Auth is reserved at `/api/user/*`. Do not add `robodev/api/user/**`.
72
+
73
+ Allowed paths: `robodev/database.ts`, `robodev/api/**/*.ts`, `robodev/jobs/**/*.ts`, `robodev/sockets/**/*.ts`, `robodev/hooks/**/*.ts`, `robodev/package.json`, `package.json`, `README.md`, `ROBODEV.md`, `.gitignore`.
@@ -11,7 +11,7 @@
11
11
  "@robodev-ai/sdk": "^0.13.0"
12
12
  },
13
13
  "devDependencies": {
14
- "robodev": "^0.47.0",
14
+ "robodev": "^0.47.2",
15
15
  "typescript": "^5.9.2"
16
16
  }
17
17
  }
@@ -2,7 +2,7 @@
2
2
 
3
3
  Blank `robodev/database.ts` and `GET /api/health` plus a hosted Welcome page. No sample schema.
4
4
 
5
- The project App URL serves the hosted Welcome page. Edit `index.html` and `src/`, then `robodev deploy` (or Code + Save).
5
+ The project App URL serves the hosted Welcome page. Edit `index.html` and `src/`, then `robodev deploy` (or Code + Save). Agents: read `ROBODEV.md`. Unless the product is a public page, add login/register via reserved `/api/user/auth/*` and protect routes.
6
6
 
7
7
  ```bash
8
8
  robodev create my-app
@@ -0,0 +1,76 @@
1
+ # Robodev
2
+
3
+ Use this file for Robodev and SDK facts. Do not web-search the SDK.
4
+
5
+ ## defineApi (copy-paste)
6
+
7
+ `db`, `email`, `storage`, and `env` are injected. Never `import { db } from "@robodev-ai/sdk"`.
8
+ The SDK is not installed in the runner. Do not `import("@robodev-ai/sdk")`. Do not search `/opt`, `node_modules`, or grok/gemini home for it.
9
+
10
+ ```ts
11
+ import { defineApi, z } from "@robodev-ai/sdk";
12
+
13
+ export const get = defineApi({
14
+ response: z.object({ ok: z.literal(true) }),
15
+ handler: async ({ db, email, storage, env }) => {
16
+ void db;
17
+ void email;
18
+ void storage;
19
+ void env;
20
+ return { ok: true as const };
21
+ },
22
+ });
23
+ ```
24
+
25
+ ## Hard rules
26
+
27
+ - Do not run npm, pnpm, or yarn install.
28
+ - Do not start npm run dev, vite, or any local server.
29
+ - Do not run tsc (node_modules is not installed in this runner).
30
+ - Do not port-scan or curl/fetch localhost.
31
+ - Preview is the WebContainer. Live APIs exist only after `robodev-publish be`.
32
+ - Publish backend after a batch of file edits, not after every file.
33
+ - Do not publish the frontend.
34
+
35
+ ## Auth and product data
36
+
37
+ Persist primary product data with `defineApi` and `robodev-publish be`. Do not use localStorage as the product store unless the user asked for a public, static, or no-account page. Auth is on by default: add login and register and protect routes unless the product is public or unauthenticated. Use `auth: "required"` and `ctx.user.id`. Call reserved `/api/user/auth/*` — do not implement `api/user/**`. Tiny: reuse seed login/register and AuthGuard. Shadcn, Empty, and legacy: create login/register with `@robodev-ai/client` tokens. A missing `.rulesync` skill is not a reason to skip APIs. Do not require `robodev-auth set` every run.
38
+
39
+ ## Backend
40
+
41
+ - Auth: robodev-auth get / set / users / users-delete. Do not invent control-plane HTTP.
42
+ - Secrets: robodev-secret list / set / delete. Use env.NAME in defineApi. List names only.
43
+ - SQL: psql or robodev-sql for DML, SELECT, and seed only. Never print DATABASE_URL. Never run DDL.
44
+ - Publish backend only with robodev-publish be. Do not publish the frontend.
45
+ - Email settings: robodev-email get / set / validate / logs.
46
+
47
+ ## Email templates
48
+
49
+ Hosted mail is the Email page. Changes apply immediately through these helpers — no Code save or deploy.
50
+
51
+ - `robodev-email templates list`
52
+ - `robodev-email templates get --slug <slug>`
53
+ - `robodev-email templates set --slug <slug> [--subject] [--html] [--text]`
54
+
55
+ `set` edits an existing slug. If the slug is missing and is a custom slug (`^[a-z][a-z0-9-]{0,62}$`, not `auth.*`), `set` creates it.
56
+
57
+ System slugs: `auth.welcome` (`{{email}}`, `{{name}}`), `auth.magic-link` (`{{link}}`, `{{email}}`, `{{name}}`, `{{appUrl}}`), `auth.forgot-password` (`{{link}}`, `{{code}}`, `{{email}}`, `{{name}}`, `{{appUrl}}`). Keep placeholders exact. Do not delete system templates. Do not invent `auth.*` slugs.
58
+
59
+ Send with `ctx.email.send({ template: "<slug>", to, vars })`.
60
+
61
+ Do not write `robodev/emails/*.html` or `robodev/emails/*.json` for hosted templates. Those files do not update the live project.
62
+
63
+ Never write `.env` or secrets. Never write `.robodev`.
64
+
65
+ ## Layout
66
+
67
+ This is the hosted Empty layout. Keep it.
68
+
69
+ - Schema: `robodev/database.ts`
70
+ - HTTP handlers: `robodev/api/**/*.ts`
71
+ - Frontend: root `index.html` + `src/**`
72
+ - Auth is reserved at `/api/user/*`. Do not add `robodev/api/user/**`.
73
+
74
+ Do not write a `frontend` block in `robodev/.robodev`. Root `index.html` + `src/` deploy as hosted React.
75
+
76
+ Allowed paths: `robodev/database.ts`, `robodev/api/**/*.ts`, `robodev/jobs/**/*.ts`, `robodev/sockets/**/*.ts`, `robodev/hooks/**/*.ts`, `robodev/package.json`, `index.html`, `src/**`, `package.json`, `README.md`, `ROBODEV.md`, `.gitignore`.
@@ -16,7 +16,7 @@
16
16
  "react-dom": "^18.3.1"
17
17
  },
18
18
  "devDependencies": {
19
- "robodev": "^0.47.0",
19
+ "robodev": "^0.47.2",
20
20
  "typescript": "^5.9.2",
21
21
  "vite": "^6.0.0"
22
22
  }
@@ -1,6 +1,6 @@
1
1
  # {{NAME}}
2
2
 
3
- Starter from `robodev create --starter marketplace`: Marketplace C2C marketplace schema (`categories`, `listings`, `cart_items`, `orders`, `watches`, `reviews`, `media`), file-based APIs in `robodev/api/`, and a Povio UI in `apps/fe`.
3
+ Starter from `robodev create --starter marketplace`: Marketplace C2C marketplace schema (`categories`, `listings`, `cart_items`, `orders`, `watches`, `reviews`, `media`), file-based APIs in `robodev/api/`, and a Povio UI in `apps/fe`. Agents: read `ROBODEV.md`.
4
4
 
5
5
  After a CLI create or deploy with `.robodev` `frontend` set (the default for this starter), the project host serves the built Marketplace SPA at `GET /`. APIs and Swagger still win over static files.
6
6
 
@@ -0,0 +1,75 @@
1
+ # Robodev
2
+
3
+ Use this file for Robodev and SDK facts. Do not web-search the SDK.
4
+
5
+ ## defineApi (copy-paste)
6
+
7
+ `db`, `email`, `storage`, and `env` are injected. Never `import { db } from "@robodev-ai/sdk"`.
8
+ The SDK is not installed in the runner. Do not `import("@robodev-ai/sdk")`. Do not search `/opt`, `node_modules`, or grok/gemini home for it.
9
+
10
+ ```ts
11
+ import { defineApi, z } from "@robodev-ai/sdk";
12
+
13
+ export const get = defineApi({
14
+ response: z.object({ ok: z.literal(true) }),
15
+ handler: async ({ db, email, storage, env }) => {
16
+ void db;
17
+ void email;
18
+ void storage;
19
+ void env;
20
+ return { ok: true as const };
21
+ },
22
+ });
23
+ ```
24
+
25
+ ## Hard rules
26
+
27
+ - Do not run npm, pnpm, or yarn install.
28
+ - Do not start npm run dev, vite, or any local server.
29
+ - Do not run tsc (node_modules is not installed in this runner).
30
+ - Do not port-scan or curl/fetch localhost.
31
+ - Preview is the WebContainer. Live APIs exist only after `robodev-publish be`.
32
+ - Publish backend after a batch of file edits, not after every file.
33
+ - Do not publish the frontend.
34
+
35
+ ## Auth and product data
36
+
37
+ Persist primary product data with `defineApi` and `robodev-publish be`. Do not use localStorage as the product store unless the user asked for a public, static, or no-account page. Auth is on by default: add login and register and protect routes unless the product is public or unauthenticated. Use `auth: "required"` and `ctx.user.id`. Call reserved `/api/user/auth/*` — do not implement `api/user/**`. Tiny: reuse seed login/register and AuthGuard. Shadcn, Empty, and legacy: create login/register with `@robodev-ai/client` tokens. A missing `.rulesync` skill is not a reason to skip APIs. Do not require `robodev-auth set` every run.
38
+
39
+ ## Backend
40
+
41
+ - Auth: robodev-auth get / set / users / users-delete. Do not invent control-plane HTTP.
42
+ - Secrets: robodev-secret list / set / delete. Use env.NAME in defineApi. List names only.
43
+ - SQL: psql or robodev-sql for DML, SELECT, and seed only. Never print DATABASE_URL. Never run DDL.
44
+ - Publish backend only with robodev-publish be. Do not publish the frontend.
45
+ - Email settings: robodev-email get / set / validate / logs.
46
+
47
+ ## Email templates
48
+
49
+ Hosted mail is the Email page. Changes apply immediately through these helpers — no Code save or deploy.
50
+
51
+ - `robodev-email templates list`
52
+ - `robodev-email templates get --slug <slug>`
53
+ - `robodev-email templates set --slug <slug> [--subject] [--html] [--text]`
54
+
55
+ `set` edits an existing slug. If the slug is missing and is a custom slug (`^[a-z][a-z0-9-]{0,62}$`, not `auth.*`), `set` creates it.
56
+
57
+ System slugs: `auth.welcome` (`{{email}}`, `{{name}}`), `auth.magic-link` (`{{link}}`, `{{email}}`, `{{name}}`, `{{appUrl}}`), `auth.forgot-password` (`{{link}}`, `{{code}}`, `{{email}}`, `{{name}}`, `{{appUrl}}`). Keep placeholders exact. Do not delete system templates. Do not invent `auth.*` slugs.
58
+
59
+ Send with `ctx.email.send({ template: "<slug>", to, vars })`.
60
+
61
+ Do not write `robodev/emails/*.html` or `robodev/emails/*.json` for hosted templates. Those files do not update the live project.
62
+
63
+ Never write `.env` or secrets. Never write `.robodev`.
64
+
65
+ ## Layout
66
+
67
+ CLI Tiny-family: `robodev/database.ts` + `robodev/api/**`, Povio UI in `apps/fe`. Keep that layout.
68
+
69
+ - Schema: `robodev/database.ts` (`defineDatabase` from `@robodev-ai/sdk`)
70
+ - HTTP handlers: `robodev/api/**/*.ts`
71
+ - Optional jobs / sockets / hooks under `robodev/`
72
+ - Frontend: `apps/fe` (Povio UI)
73
+ - Auth is reserved at `/api/user/*`. Do not add `robodev/api/user/**`.
74
+
75
+ Allowed paths: `robodev/database.ts`, `robodev/api/**/*.ts`, `robodev/jobs/**/*.ts`, `robodev/sockets/**/*.ts`, `robodev/hooks/**/*.ts`, `robodev/package.json`, `apps/fe/**`, `.rulesync/**`, `openapi.json`, `package.json`, `README.md`, `ROBODEV.md`, `.gitignore`.
@@ -12,7 +12,7 @@
12
12
  "@robodev-ai/sdk": "^0.13.0"
13
13
  },
14
14
  "devDependencies": {
15
- "robodev": "^0.47.0",
15
+ "robodev": "^0.47.2",
16
16
  "rulesync": "^8.18.0",
17
17
  "typescript": "^5.9.2"
18
18
  }
@@ -2,7 +2,7 @@
2
2
 
3
3
  Blank `robodev/database.ts` and `GET /api/health` plus a hosted Welcome page with shadcn and an orval client. No sample schema.
4
4
 
5
- The project App URL serves the hosted Welcome page (`/` via React Router in `src/pages/Home.tsx`). Edit `index.html` and `src/`, then `robodev deploy` (or Code + Save). Agents: read `ROBODEV.md`.
5
+ The project App URL serves the hosted Welcome page (`/` via React Router in `src/pages/Home.tsx`). Edit `index.html` and `src/`, then `robodev deploy` (or Code + Save). Agents: read `ROBODEV.md`. Unless the product is a public page, add login/register via reserved `/api/user/auth/*` and protect routes.
6
6
 
7
7
  ```bash
8
8
  robodev create my-app --template shadcn
@@ -1,18 +1,6 @@
1
1
  # Robodev
2
2
 
3
- Agent notes for this Shadcn workspace. Use this file for Robodev and SDK facts. Do not web-search the SDK.
4
-
5
- This is the hosted Shadcn layout. Keep it. Do not convert this tree to Tiny (`packages/be` + `apps/fe`).
6
-
7
- ## Layout
8
-
9
- - Schema: `robodev/database.ts` (`defineDatabase` from `@robodev-ai/sdk`)
10
- - HTTP handlers: `robodev/api/**/*.ts` (`defineApi` from `@robodev-ai/sdk`)
11
- - Optional jobs: `robodev/jobs/**/*.ts`. Optional sockets: `robodev/sockets/**/*.ts` (`defineSocket`). Optional runtime packages go in `robodev/package.json` (allowlisted exact semver only). Do not npm install in this runner — Starbase installs on publish.
12
- - Frontend: root `index.html` + `src/**` (React, React Router, shadcn, generated orval client)
13
- - Auth is reserved at `/api/user/*`. Do not add `robodev/api/user/**`.
14
-
15
- Do not write a `frontend` block in `robodev/.robodev` — that block is for Tiny `apps/fe`. Root `index.html` + `src/` deploy as hosted React.
3
+ Use this file for Robodev and SDK facts. Do not web-search the SDK.
16
4
 
17
5
  ## defineApi (copy-paste)
18
6
 
@@ -44,24 +32,51 @@ export const get = defineApi({
44
32
  - Publish backend after a batch of file edits, not after every file.
45
33
  - Do not publish the frontend.
46
34
 
35
+ ## Auth and product data
36
+
37
+ Persist primary product data with `defineApi` and `robodev-publish be`. Do not use localStorage as the product store unless the user asked for a public, static, or no-account page. Auth is on by default: add login and register and protect routes unless the product is public or unauthenticated. Use `auth: "required"` and `ctx.user.id`. Call reserved `/api/user/auth/*` — do not implement `api/user/**`. Tiny: reuse seed login/register and AuthGuard. Shadcn, Empty, and legacy: create login/register with `@robodev-ai/client` tokens. A missing `.rulesync` skill is not a reason to skip APIs. Do not require `robodev-auth set` every run.
38
+
47
39
  ## Backend
48
40
 
49
- - Layout is `robodev/`. `robodev-publish be` maps `robodev/database.ts` → `database.ts` and `robodev/api/**` → `api/**`.
50
41
  - Auth: robodev-auth get / set / users / users-delete. Do not invent control-plane HTTP.
51
42
  - Secrets: robodev-secret list / set / delete. Use env.NAME in defineApi. List names only.
52
43
  - SQL: psql or robodev-sql for DML, SELECT, and seed only. Never print DATABASE_URL. Never run DDL.
53
44
  - Publish backend only with robodev-publish be. Do not publish the frontend.
45
+ - Email settings: robodev-email get / set / validate / logs.
46
+
47
+ ## Email templates
48
+
49
+ Hosted mail is the Email page. Changes apply immediately through these helpers — no Code save or deploy.
50
+
51
+ - `robodev-email templates list`
52
+ - `robodev-email templates get --slug <slug>`
53
+ - `robodev-email templates set --slug <slug> [--subject] [--html] [--text]`
54
+
55
+ `set` edits an existing slug. If the slug is missing and is a custom slug (`^[a-z][a-z0-9-]{0,62}$`, not `auth.*`), `set` creates it.
56
+
57
+ System slugs: `auth.welcome` (`{{email}}`, `{{name}}`), `auth.magic-link` (`{{link}}`, `{{email}}`, `{{name}}`, `{{appUrl}}`), `auth.forgot-password` (`{{link}}`, `{{code}}`, `{{email}}`, `{{name}}`, `{{appUrl}}`). Keep placeholders exact. Do not delete system templates. Do not invent `auth.*` slugs.
54
58
 
55
- ## Frontend
59
+ Send with `ctx.email.send({ template: "<slug>", to, vars })`.
60
+
61
+ Do not write `robodev/emails/*.html` or `robodev/emails/*.json` for hosted templates. Those files do not update the live project.
62
+
63
+ Never write `.env` or secrets. Never write `.robodev`.
64
+
65
+ ## Layout
66
+
67
+ This is the hosted Shadcn layout. Keep it. Do not convert this tree to Tiny (`packages/be` + `apps/fe`).
68
+
69
+ - Schema: `robodev/database.ts` (`defineDatabase` from `@robodev-ai/sdk`)
70
+ - HTTP handlers: `robodev/api/**/*.ts` (`defineApi` from `@robodev-ai/sdk`)
71
+ - Optional jobs: `robodev/jobs/**/*.ts`. Optional sockets: `robodev/sockets/**/*.ts` (`defineSocket`). Optional runtime packages go in `robodev/package.json` (allowlisted exact semver only). Do not npm install in this runner — Starbase installs on publish.
72
+ - Frontend: root `index.html` + `src/**` (React, React Router, shadcn, generated orval client)
73
+ - Auth is reserved at `/api/user/*`. Do not add `robodev/api/user/**`.
74
+
75
+ Do not write a `frontend` block in `robodev/.robodev` — that block is for Tiny `apps/fe`. Root `index.html` + `src/` deploy as hosted React.
56
76
 
57
77
  - Route with React Router in `src/`. `src/pages/Home.tsx` is `/`. Add a page by adding a component and a `<Route>` in `src/App.tsx`.
58
78
  - Call APIs through the committed orval client under `src/api/generated`. After API changes, update `src/api/openapi.json` and regenerate.
59
79
  - UI: shadcn under `src/components/ui`. Hosted compile does not run Tailwind. Keep hosted CSS in `src/app.css`. `tailwind.config.ts` and `postcss.config.js` are for local `shadcn add` / Vite only.
60
80
  - Hosted FE packages: `react`, `react-dom`, `react-router-dom`, `@robodev-ai/client`. Do not import other npm packages in `src/` — hosted compile will fail.
61
- - Preview runs `pnpm install` / `vite --host` from the project root. Do not run those in this runner.
62
-
63
- ## Allowed paths
64
81
 
65
- `robodev/database.ts`, `robodev/api/**/*.ts`, `robodev/jobs/**/*.ts`, `robodev/sockets/**/*.ts`, `robodev/hooks/**/*.ts`, `robodev/package.json`, `index.html`, `src/**`, `package.json`, `README.md`, `ROBODEV.md`, `.gitignore`.
66
-
67
- Never write `.env` or secrets. Never write `.robodev`.
82
+ Allowed paths: `robodev/database.ts`, `robodev/api/**/*.ts`, `robodev/jobs/**/*.ts`, `robodev/sockets/**/*.ts`, `robodev/hooks/**/*.ts`, `robodev/package.json`, `index.html`, `src/**`, `package.json`, `README.md`, `ROBODEV.md`, `.gitignore`.
@@ -21,7 +21,7 @@
21
21
  "autoprefixer": "^10.4.21",
22
22
  "orval": "^7.11.2",
23
23
  "postcss": "^8.5.6",
24
- "robodev": "^0.47.0",
24
+ "robodev": "^0.47.2",
25
25
  "shadcn": "^2.10.0",
26
26
  "tailwindcss": "^3.4.17",
27
27
  "typescript": "^5.9.2",
@@ -1,6 +1,6 @@
1
1
  # {{NAME}}
2
2
 
3
- Starter from `robodev create`: Tiny Template schema (`planets`, `aliens`, `media`, `planet_likes`), file-based APIs in `robodev/api/`, and a Povio UI in `apps/fe`.
3
+ Starter from `robodev create`: Tiny Template schema (`planets`, `aliens`, `media`, `planet_likes`), file-based APIs in `robodev/api/`, and a Povio UI in `apps/fe`. Agents: read `ROBODEV.md`.
4
4
 
5
5
  After a CLI create or deploy with `.robodev` `frontend` set (the default for this starter), the project host serves the built Tiny SPA at `GET /`. APIs and Swagger still win over static files.
6
6
 
@@ -0,0 +1,75 @@
1
+ # Robodev
2
+
3
+ Use this file for Robodev and SDK facts. Do not web-search the SDK.
4
+
5
+ ## defineApi (copy-paste)
6
+
7
+ `db`, `email`, `storage`, and `env` are injected. Never `import { db } from "@robodev-ai/sdk"`.
8
+ The SDK is not installed in the runner. Do not `import("@robodev-ai/sdk")`. Do not search `/opt`, `node_modules`, or grok/gemini home for it.
9
+
10
+ ```ts
11
+ import { defineApi, z } from "@robodev-ai/sdk";
12
+
13
+ export const get = defineApi({
14
+ response: z.object({ ok: z.literal(true) }),
15
+ handler: async ({ db, email, storage, env }) => {
16
+ void db;
17
+ void email;
18
+ void storage;
19
+ void env;
20
+ return { ok: true as const };
21
+ },
22
+ });
23
+ ```
24
+
25
+ ## Hard rules
26
+
27
+ - Do not run npm, pnpm, or yarn install.
28
+ - Do not start npm run dev, vite, or any local server.
29
+ - Do not run tsc (node_modules is not installed in this runner).
30
+ - Do not port-scan or curl/fetch localhost.
31
+ - Preview is the WebContainer. Live APIs exist only after `robodev-publish be`.
32
+ - Publish backend after a batch of file edits, not after every file.
33
+ - Do not publish the frontend.
34
+
35
+ ## Auth and product data
36
+
37
+ Persist primary product data with `defineApi` and `robodev-publish be`. Do not use localStorage as the product store unless the user asked for a public, static, or no-account page. Auth is on by default: add login and register and protect routes unless the product is public or unauthenticated. Use `auth: "required"` and `ctx.user.id`. Call reserved `/api/user/auth/*` — do not implement `api/user/**`. Tiny: reuse seed login/register and AuthGuard. Shadcn, Empty, and legacy: create login/register with `@robodev-ai/client` tokens. A missing `.rulesync` skill is not a reason to skip APIs. Do not require `robodev-auth set` every run.
38
+
39
+ ## Backend
40
+
41
+ - Auth: robodev-auth get / set / users / users-delete. Do not invent control-plane HTTP.
42
+ - Secrets: robodev-secret list / set / delete. Use env.NAME in defineApi. List names only.
43
+ - SQL: psql or robodev-sql for DML, SELECT, and seed only. Never print DATABASE_URL. Never run DDL.
44
+ - Publish backend only with robodev-publish be. Do not publish the frontend.
45
+ - Email settings: robodev-email get / set / validate / logs.
46
+
47
+ ## Email templates
48
+
49
+ Hosted mail is the Email page. Changes apply immediately through these helpers — no Code save or deploy.
50
+
51
+ - `robodev-email templates list`
52
+ - `robodev-email templates get --slug <slug>`
53
+ - `robodev-email templates set --slug <slug> [--subject] [--html] [--text]`
54
+
55
+ `set` edits an existing slug. If the slug is missing and is a custom slug (`^[a-z][a-z0-9-]{0,62}$`, not `auth.*`), `set` creates it.
56
+
57
+ System slugs: `auth.welcome` (`{{email}}`, `{{name}}`), `auth.magic-link` (`{{link}}`, `{{email}}`, `{{name}}`, `{{appUrl}}`), `auth.forgot-password` (`{{link}}`, `{{code}}`, `{{email}}`, `{{name}}`, `{{appUrl}}`). Keep placeholders exact. Do not delete system templates. Do not invent `auth.*` slugs.
58
+
59
+ Send with `ctx.email.send({ template: "<slug>", to, vars })`.
60
+
61
+ Do not write `robodev/emails/*.html` or `robodev/emails/*.json` for hosted templates. Those files do not update the live project.
62
+
63
+ Never write `.env` or secrets. Never write `.robodev`.
64
+
65
+ ## Layout
66
+
67
+ CLI Tiny-family: `robodev/database.ts` + `robodev/api/**`, Povio UI in `apps/fe`. Keep that layout.
68
+
69
+ - Schema: `robodev/database.ts` (`defineDatabase` from `@robodev-ai/sdk`)
70
+ - HTTP handlers: `robodev/api/**/*.ts`
71
+ - Optional jobs / sockets / hooks under `robodev/`
72
+ - Frontend: `apps/fe` (Povio UI)
73
+ - Auth is reserved at `/api/user/*`. Do not add `robodev/api/user/**`.
74
+
75
+ Allowed paths: `robodev/database.ts`, `robodev/api/**/*.ts`, `robodev/jobs/**/*.ts`, `robodev/sockets/**/*.ts`, `robodev/hooks/**/*.ts`, `robodev/package.json`, `apps/fe/**`, `.rulesync/**`, `openapi.json`, `package.json`, `README.md`, `ROBODEV.md`, `.gitignore`.
@@ -12,7 +12,7 @@
12
12
  "@robodev-ai/sdk": "^0.13.0"
13
13
  },
14
14
  "devDependencies": {
15
- "robodev": "^0.47.0",
15
+ "robodev": "^0.47.2",
16
16
  "rulesync": "^8.18.0",
17
17
  "typescript": "^5.9.2"
18
18
  }