robodev 0.44.0 → 0.45.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "robodev",
3
- "version": "0.44.0",
3
+ "version": "0.45.1",
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,7 +26,7 @@
26
26
  "embedded-postgres": "17.10.0-beta.17",
27
27
  "fastify": "^5.12.1",
28
28
  "tsx": "^4.20.5",
29
- "@robodev-ai/runtime": "0.6.0",
29
+ "@robodev-ai/runtime": "0.7.0",
30
30
  "@robodev-ai/sdk": "0.13.0"
31
31
  },
32
32
  "devDependencies": {
@@ -4,7 +4,13 @@ import { mkdtemp, rm } from "node:fs/promises";
4
4
  import { tmpdir } from "node:os";
5
5
  import { join } from "node:path";
6
6
  import { test } from "node:test";
7
- import { collectFiles, mergeCollectedFiles, shouldCollectPath } from "./collect-files.js";
7
+ import {
8
+ collectFiles,
9
+ collectSourceFiles,
10
+ mergeCollectedFiles,
11
+ shouldCollectPath,
12
+ shouldCollectSourcePath,
13
+ } from "./collect-files.js";
8
14
 
9
15
  test("collectFiles includes hosted sources and skips denied paths", async () => {
10
16
  const root = await mkdtemp(join(tmpdir(), "rd-cli-"));
@@ -163,6 +169,50 @@ test("collectFiles rejects mixed backend layouts", async () => {
163
169
  }
164
170
  });
165
171
 
172
+ test("collectSourceFiles includes Tiny apps/fe when CLI database is absent", async () => {
173
+ const root = await mkdtemp(join(tmpdir(), "rd-cli-src-tiny-"));
174
+ try {
175
+ await mkdir(join(root, "packages/be/api"), { recursive: true });
176
+ await mkdir(join(root, "apps/fe/src"), { recursive: true });
177
+ await writeFile(join(root, "packages/be/database.ts"), "export default {}");
178
+ await writeFile(join(root, "packages/be/api/health.ts"), "export {}");
179
+ await writeFile(join(root, "apps/fe/package.json"), "{}");
180
+ await writeFile(join(root, "apps/fe/index.html"), "<html></html>");
181
+ await writeFile(join(root, "apps/fe/src/main.tsx"), "export {}");
182
+ await writeFile(join(root, ".env"), "SECRET=1");
183
+ const files = await collectSourceFiles(root);
184
+ const paths = new Set(files.map((file) => file.path));
185
+ assert.ok(paths.has("packages/be/database.ts"));
186
+ assert.ok(paths.has("apps/fe/src/main.tsx"));
187
+ assert.ok(!paths.has(".env"));
188
+ assert.equal(shouldCollectSourcePath("apps/fe/src/main.tsx"), true);
189
+ } finally {
190
+ await rm(root, { recursive: true, force: true });
191
+ }
192
+ });
193
+
194
+ test("collectSourceFiles CLI database wins and drops leftover Tiny", async () => {
195
+ const root = await mkdtemp(join(tmpdir(), "rd-cli-src-win-"));
196
+ try {
197
+ await mkdir(join(root, "robodev"), { recursive: true });
198
+ await mkdir(join(root, "apps/fe/src"), { recursive: true });
199
+ await mkdir(join(root, "packages/be"), { recursive: true });
200
+ await mkdir(join(root, "src"), { recursive: true });
201
+ await writeFile(join(root, "robodev/database.ts"), "export default {}");
202
+ await writeFile(join(root, "apps/fe/src/main.tsx"), "export {}");
203
+ await writeFile(join(root, "packages/be/database.ts"), "export default {}");
204
+ await writeFile(join(root, "src/App.tsx"), "export {}");
205
+ const files = await collectSourceFiles(root);
206
+ const paths = new Set(files.map((file) => file.path));
207
+ assert.ok(paths.has("robodev/database.ts"));
208
+ assert.ok(paths.has("src/App.tsx"));
209
+ assert.ok(!paths.has("apps/fe/src/main.tsx"));
210
+ assert.ok(!paths.has("packages/be/database.ts"));
211
+ } finally {
212
+ await rm(root, { recursive: true, force: true });
213
+ }
214
+ });
215
+
166
216
  test("backend tests are not collected for deploy", () => {
167
217
  assert.equal(shouldCollectPath("robodev/api/_lib.seed.test.ts"), false);
168
218
  assert.equal(shouldCollectPath("robodev/jobs/resize.spec.ts"), false);
@@ -1,5 +1,10 @@
1
1
  import { readdir, readFile, stat } from "node:fs/promises";
2
2
  import { join, relative } from "node:path";
3
+ import {
4
+ applySourceLayoutWin,
5
+ isAllowedWorkspacePath,
6
+ mapWorkspaceToDeployFiles,
7
+ } from "@robodev-ai/runtime";
3
8
 
4
9
  export const MAX_FILE_COUNT = 400;
5
10
  export const MAX_FILE_BYTES = 1024 * 1024;
@@ -185,6 +190,16 @@ export function shouldCollectPath(path: string): boolean {
185
190
  return !isUnsafe(normalized) && !isDenied(normalized) && isAllowed(normalized);
186
191
  }
187
192
 
193
+ export function shouldCollectSourcePath(path: string): boolean {
194
+ const normalized = normalize(path);
195
+ if (isUnsafe(normalized)) return false;
196
+ if (normalized === ".env" || normalized.startsWith(".env.")) return false;
197
+ const top = normalized.split("/")[0];
198
+ if (SKIP_DIRS.has(top)) return false;
199
+ if (isRobodevLockfile(normalized)) return false;
200
+ return isAllowedWorkspacePath(normalized);
201
+ }
202
+
188
203
  export function isStaticCollectedPath(path: string): boolean {
189
204
  const normalized = normalize(path);
190
205
  if (normalized === "index.html") return true;
@@ -259,6 +274,80 @@ export function mergeCollectedFiles(
259
274
  return files;
260
275
  }
261
276
 
277
+ export async function collectSourceFiles(root: string): Promise<CollectedFile[]> {
278
+ const hasNew = await fileExists(join(root, "robodev/database.ts"));
279
+ const hasLegacy = await fileExists(join(root, "database.ts"));
280
+ const hasTiny = await fileExists(join(root, "packages/be/database.ts"));
281
+ if (!hasNew && !hasLegacy && !hasTiny) {
282
+ throw new Error(
283
+ "database.ts not found (looked in robodev/database.ts, ./database.ts, and packages/be/database.ts)",
284
+ );
285
+ }
286
+
287
+ const files: CollectedFile[] = [];
288
+ let totalBytes = 0;
289
+
290
+ async function walk(dir: string): Promise<void> {
291
+ let entries;
292
+ try {
293
+ entries = await readdir(dir, { withFileTypes: true });
294
+ } catch {
295
+ return;
296
+ }
297
+ for (const entry of entries) {
298
+ const abs = join(dir, entry.name);
299
+ if (entry.isDirectory()) {
300
+ if (SKIP_DIRS.has(entry.name)) continue;
301
+ await walk(abs);
302
+ continue;
303
+ }
304
+ if (!entry.isFile()) continue;
305
+ const path = normalize(relative(root, abs));
306
+ if (!shouldCollectSourcePath(path)) continue;
307
+ if (path.length > MAX_PATH_LENGTH || path.split("/").length > MAX_PATH_DEPTH) {
308
+ throw new Error(`Invalid file path: ${path}`);
309
+ }
310
+ const content = await readFile(abs, "utf8");
311
+ const bytes = Buffer.byteLength(content, "utf8");
312
+ if (bytes > MAX_FILE_BYTES) {
313
+ throw new Error(`File must be under ${formatMaxBytes(MAX_FILE_BYTES)}: ${path}`);
314
+ }
315
+ totalBytes += bytes;
316
+ if (totalBytes > MAX_TREE_BYTES) {
317
+ throw new Error(`Source tree must be under ${formatMaxBytes(MAX_TREE_BYTES)}`);
318
+ }
319
+ files.push({ path, content });
320
+ if (files.length > 2000) {
321
+ throw new Error("Source may include at most 2000 files");
322
+ }
323
+ }
324
+ }
325
+
326
+ await walk(root);
327
+ const won = applySourceLayoutWin(files);
328
+ const mixed = mixedBackendLayoutError(won.map((file) => file.path));
329
+ if (mixed) {
330
+ throw new Error(mixed);
331
+ }
332
+ if (
333
+ !won.some(
334
+ (file) =>
335
+ file.path === "robodev/database.ts" ||
336
+ file.path === "database.ts" ||
337
+ file.path === "packages/be/database.ts",
338
+ )
339
+ ) {
340
+ throw new Error(
341
+ "database.ts not found (looked in robodev/database.ts, ./database.ts, and packages/be/database.ts)",
342
+ );
343
+ }
344
+ return won;
345
+ }
346
+
347
+ export function hostFilesFromSource(source: readonly CollectedFile[]): CollectedFile[] {
348
+ return mapWorkspaceToDeployFiles(source);
349
+ }
350
+
262
351
  export async function collectFiles(root: string): Promise<CollectedFile[]> {
263
352
  const hasNew = await fileExists(join(root, "robodev/database.ts"));
264
353
  const hasLegacy = await fileExists(join(root, "database.ts"));
@@ -9,11 +9,12 @@ import { collectFrontendSource, shouldCollectFrontendSource } from "./frontend-s
9
9
 
10
10
  const here = dirname(fileURLToPath(import.meta.url));
11
11
 
12
- test("deploy posts frontendSource beside files", () => {
12
+ test("deploy posts replace-all source beside host files", () => {
13
13
  const source = readFileSync(join(here, "index.ts"), "utf8");
14
- assert.match(source, /collectFrontendSource/);
14
+ assert.match(source, /collectSourceFiles/);
15
15
  assert.match(source, /collectDeployPayload/);
16
- assert.match(source, /frontendSource\.length > 0 \? \{ frontendSource \}/);
16
+ assert.match(source, /source,/);
17
+ assert.equal(source.includes("frontendSource.length > 0 ? { frontendSource }"), false);
17
18
  });
18
19
 
19
20
  test("shouldCollectFrontendSource when .robodev has frontend or apps/fe exists", () => {
package/src/index.ts CHANGED
@@ -22,13 +22,19 @@ import {
22
22
  writeViteApiUrl,
23
23
  } from "./config.js";
24
24
  import { waitUntilLive } from "./wait-until-live.js";
25
- import { collectFiles, mergeCollectedFiles, type CollectedFile } from "./collect-files.js";
25
+ import {
26
+ collectFiles,
27
+ collectSourceFiles,
28
+ hostFilesFromSource,
29
+ mergeCollectedFiles,
30
+ type CollectedFile,
31
+ } from "./collect-files.js";
26
32
  import {
27
33
  buildAndCollectFrontend,
28
34
  commandAvailable,
29
35
  frontendOutIndexExists,
30
36
  } from "./frontend-build.js";
31
- import { collectFrontendSource, shouldCollectFrontendSource } from "./frontend-source.js";
37
+ import { shouldCollectFrontendSource } from "./frontend-source.js";
32
38
  import {
33
39
  emitStarter,
34
40
  loadCatalog,
@@ -57,7 +63,7 @@ Commands:
57
63
  create [path] [--template <id>] [--starter <id>] [--empty] [--org <id>] Create a project, scaffold a starter, and deploy
58
64
  link [projectId] Write robodev/.robodev in the current folder
59
65
  deploy [--force] [--name <name>] [--org <id>] Deploy schema, APIs, jobs, and frontend (creates and links if unlinked)
60
- load Download the latest deploy onto this folder
66
+ load Download the project source onto this folder
61
67
  dev [options] Run this project's APIs locally on an embedded Postgres, no Starbase needed
62
68
 
63
69
  dev options:
@@ -234,28 +240,28 @@ async function collectDeployPayload(
234
240
  root: string,
235
241
  link: ProjectLink,
236
242
  options: DeployOptions,
237
- ): Promise<{ files: CollectedFile[]; frontendSource: CollectedFile[] }> {
238
- const collected = await collectFiles(root);
243
+ ): Promise<{ files: CollectedFile[]; source: CollectedFile[] }> {
244
+ const source = await collectSourceFiles(root);
245
+ const host = hostFilesFromSource(source);
239
246
  const hasAppsFe = await hasAppsFePackageJson(root);
240
- const frontendSource = shouldCollectFrontendSource({
241
- hasFrontendConfig: isFrontendConfig(link.frontend),
242
- hasAppsFePackageJson: hasAppsFe,
243
- })
244
- ? await collectFrontendSource(
245
- root,
246
- isFrontendConfig(link.frontend) ? link.frontend.root : "apps/fe",
247
- )
248
- : [];
249
247
  if (options.frontendFiles) {
250
- return { files: mergeCollectedFiles(collected, options.frontendFiles), frontendSource };
248
+ return { files: mergeCollectedFiles(host, options.frontendFiles), source };
251
249
  }
252
- if (options.skipFrontendBuild || !isFrontendConfig(link.frontend)) {
253
- return { files: collected, frontendSource };
250
+ const shouldBuild =
251
+ !options.skipFrontendBuild &&
252
+ (isFrontendConfig(link.frontend) ||
253
+ shouldCollectFrontendSource({
254
+ hasFrontendConfig: isFrontendConfig(link.frontend),
255
+ hasAppsFePackageJson: hasAppsFe,
256
+ }));
257
+ if (!shouldBuild) {
258
+ return { files: host.length > 0 ? host : await collectFiles(root), source };
254
259
  }
255
- const frontendFiles = await buildAndCollectFrontend(root, link.frontend, {
260
+ const frontendRoot = isFrontendConfig(link.frontend) ? link.frontend : defaultFrontendConfig();
261
+ const frontendFiles = await buildAndCollectFrontend(root, frontendRoot, {
256
262
  install: "if-needed",
257
263
  });
258
- return { files: mergeCollectedFiles(collected, frontendFiles), frontendSource };
264
+ return { files: mergeCollectedFiles(host, frontendFiles), source };
259
265
  }
260
266
 
261
267
  /** Uploads schema, APIs, and frontend. Destructive schema changes need confirmation or `--force`. */
@@ -287,7 +293,7 @@ async function runDeploy(
287
293
  promptName: (defaultName) => prompt(`Project name [${defaultName}]: `),
288
294
  },
289
295
  );
290
- const { files, frontendSource } = await collectDeployPayload(root, link, options);
296
+ const { files, source } = await collectDeployPayload(root, link, options);
291
297
  let force = forceFlag;
292
298
 
293
299
  for (;;) {
@@ -306,7 +312,7 @@ async function runDeploy(
306
312
  body: JSON.stringify({
307
313
  force,
308
314
  files,
309
- ...(frontendSource.length > 0 ? { frontendSource } : {}),
315
+ source,
310
316
  }),
311
317
  });
312
318
  await waitUntilLive(result.apiUrl);
package/src/load.test.ts CHANGED
@@ -80,7 +80,7 @@ test("runLoad fails without a link and does not fetch or write", async () => {
80
80
  test("runLoad fails on an empty deployment and does not write", async () => {
81
81
  const deps = stubRun({ files: [] });
82
82
  await assert.rejects(() => runLoad([], deps), new Error(NO_DEPLOYMENT_MESSAGE));
83
- assert.deepEqual(deps.authedPaths, ["/v1/projects/proj_load/deployment/files"]);
83
+ assert.deepEqual(deps.authedPaths, ["/v1/projects/proj_load/workspace"]);
84
84
  assert.deepEqual(deps.applied, []);
85
85
  });
86
86
 
@@ -93,7 +93,7 @@ test("runLoad rejects unknown flags before reading the link", async () => {
93
93
  await assert.rejects(() => runLoad(["--workspace"], deps), /Unknown flag --workspace/);
94
94
  });
95
95
 
96
- test("runLoad fetches deployment files and prints a summary", async () => {
96
+ test("runLoad fetches workspace source and prints a summary", async () => {
97
97
  const logs: string[] = [];
98
98
  const files = [{ path: "robodev/database.ts", content: "export default {}" }];
99
99
  const deps = stubRun({
@@ -102,7 +102,7 @@ test("runLoad fetches deployment files and prints a summary", async () => {
102
102
  log: (message) => logs.push(message),
103
103
  });
104
104
  await runLoad([], deps);
105
- assert.deepEqual(deps.authedPaths, ["/v1/projects/proj_load/deployment/files"]);
105
+ assert.deepEqual(deps.authedPaths, ["/v1/projects/proj_load/workspace"]);
106
106
  assert.deepEqual(logs, ["Loaded 1 files from proj_load."]);
107
107
  });
108
108
 
@@ -153,12 +153,13 @@ test("applyLoadFiles skips protected and denied paths", async () => {
153
153
  { path: "apps/fe/src/App.tsx", content: "payload-fe" },
154
154
  { path: "apps/fe/package.json", content: "{}" },
155
155
  ]);
156
- assert.equal(result.written, 1);
156
+ assert.equal(result.written, 3);
157
157
  assert.equal(await readFile(join(root, "robodev/database.ts"), "utf8"), "export default {}");
158
158
  assert.equal(await readFile(join(root, ".env"), "utf8"), "SECRET=1");
159
159
  assert.equal(await readFile(join(root, ".env.local"), "utf8"), "LOCAL=1");
160
160
  assert.equal(await readFile(join(root, "robodev/.robodev"), "utf8"), '{"projectId":"keep"}');
161
- assert.equal(await readFile(join(root, "apps/fe/src/App.tsx"), "utf8"), "local-fe");
161
+ assert.equal(await readFile(join(root, "apps/fe/src/App.tsx"), "utf8"), "payload-fe");
162
+ assert.equal(await readFile(join(root, "apps/fe/package.json"), "utf8"), "{}");
162
163
  });
163
164
  });
164
165
 
@@ -169,7 +170,7 @@ test("applyLoadFiles treats an all-skipped payload as empty", async () => {
169
170
  () =>
170
171
  applyLoadFiles(root, [
171
172
  { path: ".env", content: "nope" },
172
- { path: "apps/fe/src/App.tsx", content: "nope" },
173
+ { path: "robodev/.robodev", content: "nope" },
173
174
  ]),
174
175
  new Error(NO_DEPLOYMENT_MESSAGE),
175
176
  );
@@ -193,7 +194,7 @@ test("applyLoadFiles overwrites existing allowed files", async () => {
193
194
  });
194
195
  });
195
196
 
196
- test("applyLoadFiles prunes stale collectable files and leaves apps/fe and .env", async () => {
197
+ test("applyLoadFiles prunes stale collectables including leftover apps/fe", async () => {
197
198
  await withTemp(async (root) => {
198
199
  await mkdir(join(root, "robodev/api"), { recursive: true });
199
200
  await mkdir(join(root, "apps/fe/src"), { recursive: true });
@@ -207,25 +208,27 @@ test("applyLoadFiles prunes stale collectable files and leaves apps/fe and .env"
207
208
  { path: "robodev/api/keep.ts", content: "new-keep" },
208
209
  ]);
209
210
  assert.equal(result.written, 2);
210
- assert.equal(result.removed, 1);
211
+ assert.equal(result.removed, 2);
211
212
  assert.equal(await readFile(join(root, "robodev/database.ts"), "utf8"), "new-db");
212
213
  assert.equal(await readFile(join(root, "robodev/api/keep.ts"), "utf8"), "new-keep");
213
214
  await assert.rejects(() => readFile(join(root, "robodev/api/gone.ts")), /ENOENT/);
215
+ await assert.rejects(() => readFile(join(root, "apps/fe/src/App.tsx")), /ENOENT/);
214
216
  assert.equal(await readFile(join(root, ".env"), "utf8"), "SECRET=1");
215
- assert.equal(await readFile(join(root, "apps/fe/src/App.tsx"), "utf8"), "local-fe");
216
217
  });
217
218
  });
218
219
 
219
- test("applyLoadFiles decodes public binaries from base64", async () => {
220
+ test("applyLoadFiles writes source frontend files and skips host dist binaries", async () => {
220
221
  await withTemp(async (root) => {
221
222
  const bytes = Buffer.from([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]);
222
223
  const result = await applyLoadFiles(root, [
223
224
  { path: "index.html", content: "<html></html>" },
225
+ { path: "apps/fe/public/favicon.svg", content: "<svg></svg>" },
224
226
  { path: "public/icon.png", content: bytes.toString("base64") },
225
227
  ]);
226
228
  assert.equal(result.written, 2);
227
- assert.deepEqual(await readFile(join(root, "public/icon.png")), bytes);
228
229
  assert.equal(await readFile(join(root, "index.html"), "utf8"), "<html></html>");
230
+ assert.equal(await readFile(join(root, "apps/fe/public/favicon.svg"), "utf8"), "<svg></svg>");
231
+ await assert.rejects(() => readFile(join(root, "public/icon.png")), /ENOENT/);
229
232
  });
230
233
  });
231
234
 
package/src/load.ts CHANGED
@@ -1,15 +1,16 @@
1
1
  import { mkdir, readdir, unlink, writeFile } from "node:fs/promises";
2
2
  import { dirname, join, relative, resolve, sep } from "node:path";
3
3
  import { authed } from "./api.js";
4
- import { isPublicBinaryCollectedPath, shouldCollectPath } from "./collect-files.js";
4
+ import { isPublicBinaryCollectedPath, shouldCollectSourcePath } from "./collect-files.js";
5
5
  import { readLink, type ProjectLink } from "./config.js";
6
6
 
7
7
  export const NO_LINK_MESSAGE =
8
8
  "No robodev/.robodev file. Run `robodev link` or `robodev create` first.";
9
9
  export const NO_DEPLOYMENT_MESSAGE =
10
- "No deployment for this project. Deploy from the dashboard or run `robodev deploy` first.";
10
+ "No source files for this project. Deploy from the dashboard or run `robodev deploy` first.";
11
+ export const NO_SOURCE_MESSAGE = NO_DEPLOYMENT_MESSAGE;
11
12
 
12
- const PRUNE_SKIP_DIRS = new Set(["node_modules", "dist", ".git", ".robodev", "apps"]);
13
+ const PRUNE_SKIP_DIRS = new Set(["node_modules", "dist", ".git", ".robodev"]);
13
14
 
14
15
  export type LoadFile = { path: string; content: string };
15
16
 
@@ -52,7 +53,6 @@ function isProtectedPath(path: string): boolean {
52
53
  }
53
54
  if (path === ".env" || path.startsWith(".env.")) return true;
54
55
  if (path === "credentials.json" || path.endsWith("/credentials.json")) return true;
55
- if (path === "apps" || path.startsWith("apps/")) return true;
56
56
  if (path === "node_modules" || path.startsWith("node_modules/")) return true;
57
57
  if (path === ".git" || path.startsWith(".git/")) return true;
58
58
  return false;
@@ -79,7 +79,7 @@ export function classifyLoadFiles(root: string, files: LoadFile[]): Classified[]
79
79
  if (isUnsafePath(path)) return { kind: "unsafe", path };
80
80
  const abs = resolveUnderRoot(root, path);
81
81
  if (!abs) return { kind: "unsafe", path };
82
- if (isProtectedPath(path) || !shouldCollectPath(path)) return { kind: "denied", path };
82
+ if (isProtectedPath(path) || !shouldCollectSourcePath(path)) return { kind: "denied", path };
83
83
  return { kind: "allowed", path, content: file.content, abs };
84
84
  });
85
85
  }
@@ -103,7 +103,7 @@ async function listCollectablePaths(root: string): Promise<string[]> {
103
103
  }
104
104
  if (!entry.isFile()) continue;
105
105
  const path = normalize(relative(root, abs));
106
- if (!shouldCollectPath(path)) continue;
106
+ if (!shouldCollectSourcePath(path)) continue;
107
107
  paths.push(path);
108
108
  }
109
109
  }
@@ -161,7 +161,7 @@ export async function runLoad(args: string[], deps: LoadDeps = defaultDeps): Pro
161
161
  throw new Error(NO_LINK_MESSAGE);
162
162
  }
163
163
  const body = await deps.authed<{ files?: LoadFile[] }>(
164
- `/v1/projects/${link.projectId}/deployment/files`,
164
+ `/v1/projects/${link.projectId}/workspace`,
165
165
  );
166
166
  const files = body.files ?? [];
167
167
  if (files.length === 0) {
@@ -12,7 +12,7 @@
12
12
  "@robodev-ai/sdk": "^0.13.0"
13
13
  },
14
14
  "devDependencies": {
15
- "robodev": "^0.44.0",
15
+ "robodev": "^0.45.1",
16
16
  "rulesync": "^8.18.0",
17
17
  "typescript": "^5.9.2"
18
18
  }
@@ -11,7 +11,7 @@
11
11
  "@robodev-ai/sdk": "^0.13.0"
12
12
  },
13
13
  "devDependencies": {
14
- "robodev": "^0.44.0",
14
+ "robodev": "^0.45.1",
15
15
  "typescript": "^5.9.2"
16
16
  }
17
17
  }
@@ -16,7 +16,7 @@
16
16
  "react-dom": "^18.3.1"
17
17
  },
18
18
  "devDependencies": {
19
- "robodev": "^0.44.0",
19
+ "robodev": "^0.45.1",
20
20
  "typescript": "^5.9.2",
21
21
  "vite": "^6.0.0"
22
22
  }
@@ -12,7 +12,7 @@
12
12
  "@robodev-ai/sdk": "^0.13.0"
13
13
  },
14
14
  "devDependencies": {
15
- "robodev": "^0.44.0",
15
+ "robodev": "^0.45.1",
16
16
  "rulesync": "^8.18.0",
17
17
  "typescript": "^5.9.2"
18
18
  }
@@ -12,7 +12,7 @@
12
12
  "@robodev-ai/sdk": "^0.13.0"
13
13
  },
14
14
  "devDependencies": {
15
- "robodev": "^0.44.0",
15
+ "robodev": "^0.45.1",
16
16
  "rulesync": "^8.18.0",
17
17
  "typescript": "^5.9.2"
18
18
  }