@overaihq/cli 0.14.12 → 0.14.13

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.
Files changed (3) hide show
  1. package/README.md +6 -0
  2. package/dist/index.js +97 -49
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -170,6 +170,12 @@ overai best-practices webPages # one guide (add a section name to narrow it)
170
170
  overai review # before every push
171
171
  ```
172
172
 
173
+ Clone also writes `.overai/base/`, a pristine copy of every resource file that
174
+ backs `diff`, `restore`, and push's stale-base check. Never edit it by hand. The
175
+ clone writes `.overai/.gitignore` and `.overai/.ignore` so git and ripgrep-based
176
+ agents skip the folder. If you already committed `.overai/`, run
177
+ `git rm -r --cached .overai` once.
178
+
173
179
  When platform behavior blocks progress, agents can send detailed feedback with
174
180
  `overai report bug -` or `overai report feature -` and the report body on stdin.
175
181
  Reports include CLI, runtime, organization, and cloned-workspace identifiers,
package/dist/index.js CHANGED
@@ -62373,13 +62373,21 @@ var pinOrgFromClone = () => {
62373
62373
  } catch {
62374
62374
  }
62375
62375
  };
62376
+ var IGNORE_FILES = [".ignore", ".gitignore"];
62377
+ var IGNORE_ALL = "*\n";
62376
62378
  var writeManifest = (root, manifest) => {
62377
- mkdirSync2(join3(root, MANIFEST_DIR), { recursive: true });
62379
+ const dir = join3(root, MANIFEST_DIR);
62380
+ mkdirSync2(dir, { recursive: true });
62378
62381
  writeFileSync2(
62379
- join3(root, MANIFEST_DIR, MANIFEST_FILE),
62382
+ join3(dir, MANIFEST_FILE),
62380
62383
  `${JSON.stringify(manifest, null, 2)}
62381
62384
  `
62382
62385
  );
62386
+ for (const name2 of IGNORE_FILES) {
62387
+ const path = join3(dir, name2);
62388
+ if (!existsSync2(path) || readFileSync4(path, "utf8") !== IGNORE_ALL)
62389
+ writeFileSync2(path, IGNORE_ALL);
62390
+ }
62383
62391
  };
62384
62392
  var basePath = (root, relPath) => join3(root, MANIFEST_DIR, BASE_DIR, relPath);
62385
62393
  var writeSynced = (root, relPath, content) => {
@@ -65063,7 +65071,9 @@ var workflow = `# CLI editing workflow
65063
65071
  step users can feel; push freely, deploy deliberately.
65064
65072
 
65065
65073
  Never edit .overai/ by hand \u2014 it holds the manifest and pristine base copies
65066
- that make status/diff/fast-forward work.
65074
+ that make status/diff/fast-forward work. \`.overai/base/\` mirrors every
65075
+ resource file as last synced; edit the working copy, never the base. Git and
65076
+ ripgrep skip the folder through its own ignore files.
65067
65077
 
65068
65078
  A push conflict does not deploy anything. Normal push sends the manifest base
65069
65079
  to an atomic server check; only explicit --force omits it. Never use --force as
@@ -65553,7 +65563,7 @@ Users who prefer their own editor or an AI coding agent (Claude Code, Cursor) ca
65553
65563
 
65554
65564
  Clone includes accessible, non-archived descendants under \`workspaces/<slug>.<workspaceId>/\`, each with its own manifest and overview. Commands use the nearest workspace folder: change into a child to work there. Add \`--recursive\` to status/diff/pull/push to include the live accessible subtree; recursive pull clones new children and matches existing folders by workspace ID. Recursive push saves only and rejects --deploy; deploy/restore remain single-workspace. Conflicts or failures exit nonzero while preserving local edits and successful work. Unavailable or moved-out workspaces stay on disk and are skipped. Local file moves do not move resources between workspaces in OverAI.
65555
65565
 
65556
- The loop is git-shaped: \`overai clone <workspaceId>\` writes the workspace overview to \`WORKSPACE.md\` and materializes automations, functions, web pages, printable pages, and agents as local files; \`status\`/\`diff\`/\`pull\` track local resource edits against remote drift; \`push -m "why"\` creates new versions and \`deploy\` makes them live \u2014 **pushing alone changes nothing users see**. \`overai create <type>\` creates any current resource, while \`resource show|update <id>\` handles mutable metadata. \`workspace show [workspaceId]\` reads the current overview, and \`workspace users [workspaceId]\` lists effective, direct, and inherited access; inside a clone the id is optional. Resource-specific commands invoke functions and agents, trigger automations, run SQL, append/query streams, upload/download files, configure integrations, render printable pages, and pair or use runners. \`overai review\` validates changed resources before a push, and \`overai best-practices\` serves these same doc topics, plus the CLI's own workflow guide, to the local agent.
65566
+ The loop is git-shaped: \`overai clone <workspaceId>\` writes the workspace overview to \`WORKSPACE.md\` and materializes automations, functions, web pages, printable pages, and agents as local files (\`.overai/\` holds sync state and is ignored by git); \`status\`/\`diff\`/\`pull\` track local resource edits against remote drift; \`push -m "why"\` creates new versions and \`deploy\` makes them live \u2014 **pushing alone changes nothing users see**. \`overai create <type>\` creates any current resource, while \`resource show|update <id>\` handles mutable metadata. \`workspace show [workspaceId]\` reads the current overview, and \`workspace users [workspaceId]\` lists effective, direct, and inherited access; inside a clone the id is optional. Resource-specific commands invoke functions and agents, trigger automations, run SQL, append/query streams, upload/download files, configure integrations, render printable pages, and pair or use runners. \`overai review\` validates changed resources before a push, and \`overai best-practices\` serves these same doc topics, plus the CLI's own workflow guide, to the local agent.
65557
65567
 
65558
65568
  Point users here when they ask about editing outside the browser, using a local AI coding agent, version-controlling a workspace, or scripting against the platform. Everything the CLI does is also doable in the Architect \u2014 it is an alternative surface, not a required one. Full command list: \`overai help\`; the user-facing guide is the help center article at \`/<orgId>/help/cli\`.
65559
65569
  `
@@ -65775,6 +65785,10 @@ Two optional JSON Schemas declare the function's contract:
65775
65785
 
65776
65786
  The deployed version's schemas type \`env.FNxxx.call()\` everywhere authors call this function: the envelope's \`body\` member types the call body, and the output schema types \`res.json()\`. Declare the output schema whenever the response shape is stable \u2014 callers get autocomplete and typo-catching lint instead of \`any\` \u2014 and keep both schemas in sync with what the code actually reads and returns. They describe the base path; a router's sub-paths are outside the declared contract.
65777
65787
 
65788
+ Autocomplete and deployed-schema types cover functions owned by or shared into this workspace. A manually named \`env.FNxxx\` (or \`env.fnxxx\`) outside that list has a generic call type, not the target's schema. Static unlisted references warn through \`architect/unlisted-function\`; an intentional cross-workspace call may be valid, but verify the ID and the deployed caller access against the actual caller. Do not claim access was verified without checking, change sharing or caller access just to silence the warning, or invoke a side-effecting function merely to test it. After verification, acknowledge a known reference with \`// eslint-disable-next-line architect/unlisted-function -- <verified reason>\` directly above it. Give a concrete reason; avoid casts, \`@ts-ignore\`, and blanket suppressions. Dynamic IDs are outside this static check.
65789
+
65790
+ If the warning gives a listed function's spelling, use that spelling instead of suppressing it; different casing loses its deployed-schema types.
65791
+
65778
65792
  Lint enforces the output side: when an output schema is declared, a handler whose return value doesn't match it gets a lint error (both on code edits and on schema edits). Returning the plain JSON value (the runtime wraps it in a 200 response) keeps the check active; a hand-built \`Response\` passes unchecked because its payload is invisible to the type checker. Enum values are checked as their base type (\`string\`, not the exact members), and \`null\`/missing-return code paths are not caught \u2014 verify those yourself.
65779
65793
 
65780
65794
  Callers can also name the declared shapes: the deployed schemas generate \`Env.FNxxx.Input\` (the call body) and \`Env.FNxxx.Output\` (the response) for annotating payloads and results.
@@ -66599,8 +66613,6 @@ Saves reject a stale editing base without discarding your draft. Incorporate the
66599
66613
 
66600
66614
  \`\`\`typescript
66601
66615
  declare global {
66602
- const filterData: { id: string; attributes: { id: string; attributeId: string; value: string; }[]; }[] | null;
66603
- const dataFile: Record<string, unknown> | null;
66604
66616
  const searchParams: Record<string, unknown>;
66605
66617
  const setSearchParam: { (key: string, value: string | null): void; (params: Record<string, string | null>): void }; // pass null to delete a param; pass an object to set/delete several in one URL update
66606
66618
  const navigateToPage: (webPageId: string, queryParameters?: Record<string, string>, options?: { newTab?: boolean }) => void; // { newTab: true } opens the page in a new browser tab \u2014 use it instead of hand-building URLs with window.open
@@ -66614,6 +66626,8 @@ declare global {
66614
66626
  /** @deprecated Use useLLMText, useLLMObject, or useLLMChat instead. */
66615
66627
  const llmCompletion: (message: string, systemPrompt?: string, model?: string) => Promise<Record<string, unknown>>;
66616
66628
  const callFunction: (functionId: string, options?: { body?: unknown; method?: string; query?: Record<string, string>; path?: string }) => Promise<Response>;
66629
+ // Start a deployed automation. \`inputs\` maps onto its event node's declared outputs (e.g. { fileUrl } for a file-upload trigger \u2014 upload with env.STORAGE.upload first). Throws on non-2xx.
66630
+ const startAutomation: (automationId: string, inputs?: Record<string, unknown>) => Promise<{ automationRunId: string }>;
66617
66631
 
66618
66632
  // Imperative AI functions
66619
66633
  // Prefer stream: false for one-shot generateText calls \u2014 errors (invalid PDF, prompt too long, rate limit) come back as HTTP errors with a friendly message instead of an empty body. Only stream when you wire onChunk for progressive UI.
@@ -66630,20 +66644,10 @@ declare global {
66630
66644
  const callAgentGenerateObject: (agentId: string, message: string, schema: Record<string, unknown>) => Promise<{ object: unknown; model: string; finishReason: string; usage: { promptTokens: number; completionTokens: number; totalTokens: number }; toolResults: { toolName: string; input: unknown; output: unknown; error: unknown }[] }>;
66631
66645
  const callAgentChat: (agentId: string, messages: { role: string; parts: { type: string; text?: string }[] }[], options?: { conversationId?: string; onChunk?: (accumulated: string) => void }) => Promise<string>;
66632
66646
 
66633
- // Agent Hook \u2014 multi-turn streaming chat with an OverAI Agent.
66634
- // Every turn is persisted server-side for a signed-in viewer. Pass conversationId
66635
- // to resume a past thread (its full transcript, tool calls included, loads on
66636
- // mount). Omit it to start fresh \u2014 a conversation is created on the first send and
66637
- // returned as conversationId. messages[].parts holds full UI parts for loaded turns;
66638
- // the live-streaming turn carries text only.
66639
- // On a PUBLIC page there is no viewer identity: chat works, but the transcript
66640
- // lives in the hook for the tab's life, conversationId stays null, and the
66641
- // conversation helpers below throw. See "Public pages" below.
66647
+ // Agent Hook \u2014 multi-turn streaming chat with an OverAI Agent; persistence and resume are explained below the block. messages[].parts holds full UI parts for loaded turns; the live-streaming turn carries text only.
66642
66648
  const useAgentChat: (agentId: string, options?: { conversationId?: string }) => { messages: { id?: string; role: string; text: string; parts?: { type: string; text?: string; toolName?: string; toolCallId?: string; state?: string; input?: unknown; output?: unknown }[] }[]; sendMessage: (text: string) => void; clearMessages: () => void; loading: boolean; error: string | null; conversationId: string | null };
66643
66649
 
66644
- // Agent conversation management \u2014 list/read/manage a signed-in user's chat threads
66645
- // with an agent. Unavailable on public pages (no identity owns a thread):
66646
- // useAgentConversations returns an empty list, the rest throw.
66650
+ // Agent conversation management \u2014 list/read/manage a signed-in user's chat threads with an agent.
66647
66651
  const useAgentConversations: (agentId: string) => { conversations: { id: string; title: string; updatedAt: string }[]; loading: boolean; error: string | null; refresh: () => Promise<void> };
66648
66652
  const listAgentConversations: (agentId: string) => Promise<{ id: string; title: string; updatedAt: string }[]>;
66649
66653
  const getAgentConversationMessages: (conversationId: string) => Promise<{ id?: string; role: string; text: string; parts?: { type: string; text?: string }[] }[]>;
@@ -66665,9 +66669,12 @@ declare global {
66665
66669
  /** Escape hatch for dynamic SQL. Caller is responsible for parameterizing. */
66666
66670
  unsafe: <Row = any>(query: string, params?: unknown[]) => Promise<{ rows: Row[]; rowCount: number }>;
66667
66671
  };
66672
+ /** Several statements that must all land or none do. Pass lazy \`sql\` fragments; resolves to { results, executionId, durationMs } with results[i] = { rows, rowCount }. Any failure rolls everything back and throws. */
66673
+ transaction: (statements: readonly unknown[]) => Promise<{ results: { rows: Record<string, unknown>[]; rowCount: number }[]; executionId: string; durationMs: number }>;
66668
66674
  /** @deprecated Use \`sql\` \u2014 it auto-parameterizes interpolated values and returns rows directly. query returns camelCased columns (sql returns snake_case); do not mix the two. */
66669
66675
  query: (sql: TemplateStringsArray, ...values: unknown[]) => Promise<Response>;
66670
66676
  };
66677
+ [automationId: \`AT\${string}\`]: { start: (inputs?: Record<string, unknown>) => Promise<{ automationRunId: string }> };
66671
66678
  /** \`path\` routes to a sub-path inside the function (e.g. "/features/123") for functions that ship their own router; omit it to hit the base path. Method still defaults to POST. */
66672
66679
  [functionId: \`FN\${string}\`]: { call: (options?: { body?: unknown; method?: string; query?: Record<string, string>; path?: string }) => Promise<Response> };
66673
66680
  [agentId: \`AG\${string}\`]: { generateText: (message: string) => Promise<{ text: string; model: string; finishReason: string; usage: { promptTokens: number; completionTokens: number; totalTokens: number }; toolResults: { toolName: string; input: unknown; output: unknown; error: unknown }[] }>; generateObject: (message: string, schema: Record<string, unknown>) => Promise<{ object: unknown; model: string; finishReason: string; usage: { promptTokens: number; completionTokens: number; totalTokens: number }; toolResults: { toolName: string; input: unknown; output: unknown; error: unknown }[] }> };
@@ -66684,8 +66691,6 @@ declare global {
66684
66691
  };
66685
66692
  };
66686
66693
  const React: typeof import("react");
66687
- const useDataFile: () => Record<string, unknown> | null;
66688
- const useFilterData: () => { id: string; attributes: { id: string; attributeId: string; value: string; }[]; }[] | null;
66689
66694
  const useCurrentUser: () => { id: string; email: string; name: string; } | null;
66690
66695
  // useTheme re-renders on the app's light/dark toggle. The page root already carries a \`dark\` class (Tailwind darkMode: "class"), so style with \`dark:\` variants \u2014 e.g. bg-white dark:bg-slate-900 \u2014 and reach for the hook only for JS-driven styling (chart palettes, canvas, map tiles).
66691
66696
  // Two things a light-only page gets wrong in dark mode: a border or divide utility with no color class falls back to Tailwind's default light gray, so \`border-b\` alone stays a bright rule \u2014 pair it, e.g. border-b border-gray-200 dark:border-gray-700. And if the page loads daisyUI, its theme drives the inherited text color, so a hardcoded bg-white left to inherit turns light-on-white; give every surface a dark counterpart.
@@ -66698,19 +66703,29 @@ declare global {
66698
66703
  const useLocalStorageState: <T>(key: string, initialValue: T) => [T, React.Dispatch<React.SetStateAction<T>>];
66699
66704
  // useSessionStorageState is a useState-like hook backed by sessionStorage. Persists for the lifetime of the tab. REQUIRES noSandbox: true in the page config.
66700
66705
  const useSessionStorageState: <T>(key: string, initialValue: T) => [T, React.Dispatch<React.SetStateAction<T>>];
66706
+
66707
+ // Legacy \u2014 populated only by the deprecated dataSourceUrl / filter page settings (see "Legacy data sources" below). New pages use env instead.
66708
+ const dataFile: Record<string, unknown> | null;
66709
+ const useDataFile: () => Record<string, unknown> | null;
66710
+ const filterData: { id: string; attributes: { id: string; attributeId: string; value: string; }[]; }[] | null;
66711
+ const useFilterData: () => { id: string; attributes: { id: string; attributeId: string; value: string; }[]; }[] | null;
66701
66712
  }
66702
66713
  \`\`\`
66703
66714
 
66704
- IMPORTANT: Use the hooks (useDataFile, useFilterData, useSearchParamState) instead of the global variables when you need the component to re-render when data or URL state changes. URL state belongs in search params (via useSearchParamState), not local React state, so links and reloads preserve it.
66715
+ IMPORTANT: Use useSearchParamState instead of the searchParams global when the component must re-render as the URL changes. URL state belongs in search params (via useSearchParamState), not local React state, so links and reloads preserve it.
66705
66716
 
66706
66717
  IMPORTANT: callFunction and env.FNxxx.call return a Response object (like fetch) \u2014 call .json() or .text() to get the data. Date/timestamp values come back as ISO 8601 strings (e.g., "2026-04-06T00:00:00.000Z"). When the function declares input/output schemas, env.FNxxx.call types the body and .json() from them (the deployed version's declaration) \u2014 no annotation needed.
66707
66718
 
66719
+ Function autocomplete and deployed-schema types cover functions owned by or shared into this workspace. A manually named \`env.FNxxx\` or \`env.fnxxx\` outside that list remains a generic call, not a typed discovery of organization functions. A static unlisted reference raises advisory \`architect/unlisted-function\`; dynamic IDs are not checked. Verify the ID and deployed caller access for the page's actual caller before using an intentional cross-workspace reference. Never claim unverified access, change sharing or authorization just to silence the warning, or invoke a side-effecting function merely to test it. Once verified, acknowledge the reference with \`// eslint-disable-next-line architect/unlisted-function -- <verified reason>\` directly above it. Prefer a concrete reason, not casts, \`@ts-ignore\`, or blanket suppression.
66720
+
66721
+ If the warning gives a listed function's spelling, use that spelling instead of suppressing it; different casing loses its deployed-schema types.
66722
+
66708
66723
  The \`env\` proxy matches the API available in Functions and Automations \u2014 the same code works in all three.
66709
66724
 
66710
66725
  For AI in React components, prefer the reactive hooks (useLLMText, useLLMObject, useLLMChat, useAgentChat) \u2014 they re-render as the response streams in. In event handlers or imperative code, call generateText/generateObject (or env.AI.*) directly.
66711
66726
  useAgentChat persists every turn server-side for a signed-in viewer: pass a conversationId to resume a past thread (full transcript, tool calls included, loads automatically), or omit it to start fresh and read the new thread's id off the returned conversationId. useAgentConversations(agentId) backs a history sidebar \u2014 call its refresh() after a first send so the newly created thread appears. listAgentConversations, getAgentConversationMessages, createAgentConversation, renameAgentConversation, and deleteAgentConversation are the imperative equivalents. On a public page none of that persistence exists (see Public pages) \u2014 useAgentChat still streams, holding the transcript in memory.
66712
66727
  All AI functions/hooks accept optional File[] attachments for images/PDFs, and webSearch: true to enable web search.
66713
- Available models: the model ids this workspace can use. Omit \`model\` to use this workspace's default.
66728
+ Available models: read \`availableModelIds\` from the \`getWebPageContext\` data tool. Omit \`model\` to use this workspace's default.
66714
66729
 
66715
66730
  IMPORTANT: Keep generateObject / useLLMObject / agent generateObject schemas simple. Anthropic models reject schemas with more than 16 nullable or union-typed fields (this includes optional fields the AI SDK emits as \`type: [..., "null"]\`). Prefer non-nullable fields, split into multiple calls, or switch to a non-Anthropic model from the list above for larger schemas.
66716
66731
 
@@ -66786,25 +66801,16 @@ const { rows } = await env.TBS123.sql.unsafe(
66786
66801
 
66787
66802
  Pages render UI; they don't perform server-side actions directly. To do anything with side effects beyond table-space SQL (send email, hit a third-party API, post to Slack, etc.), call a Function or trigger an Automation. Table spaces are the exception: read AND write them directly with env.TBSxxx.sql as shown above.
66788
66803
 
66789
- - **Common actions** like sending email, Slack messages, SMS, or HTTP calls are first-class Automation nodes. \`SEND_EMAIL\` and \`HTTP\` need no integration; \`SLACK_SEND_MESSAGE\` and \`TWILIO_SEND_SMS\` require their respective integration to be configured. Build a webhook-triggered automation and trigger it from the page (snippet below).
66804
+ - **Common actions** like sending email, Slack messages, SMS, or HTTP calls are first-class Automation nodes. \`SEND_EMAIL\` and \`HTTP\` need no integration; \`SLACK_SEND_MESSAGE\` and \`TWILIO_SEND_SMS\` require their respective integration to be configured. Build the automation, deploy it, and start it from the page with \`env.ATxxx.start(inputs)\` (snippet below) \u2014 never hand-build its webhook URL.
66790
66805
  - **Custom server logic** belongs in a Function. Call it from the page via \`callFunction('FN123', { body: {...} })\` or \`env.FN123.call({ body: {...} })\`. A Function that ships its own router serves many routes \u2014 reach one with \`path\`: \`env.FN123.call({ method: 'GET', path: '/features/123' })\`. Omit \`path\` for the base path; the method defaults to POST either way.
66791
66806
  - **Action runners** (\`env.ACTIONS.run\`) are server-only \u2014 not available on a page. To run an action runner action from a page, put the \`env.ACTIONS.run(...)\` call in a Function and invoke that Function from the page.
66792
66807
  - **Email** (\`env.EMAIL.send\`) is server-only for the same reason. Put the \`env.EMAIL.send(...)\` call in a Function and invoke that Function from the page.
66793
66808
  - **Integrations** are credentials/config consumed *by* automation nodes or functions \u2014 they are not available directly in the page context. Only reach for an Integration when the work requires authenticated access to a specific third-party service.
66794
66809
 
66795
66810
  \`\`\`typescript
66796
- // Trigger a webhook-triggered automation from a page. The full webhook URL
66797
- // is shown in the automation editor \u2014 webhook automations are unauthenticated,
66798
- // so the URL itself is the credential.
66799
- const response = await fetch(
66800
- 'https://api.overai.com/v1/automation/webhook/ORG123/AT123',
66801
- {
66802
- method: 'POST',
66803
- headers: { 'Content-Type': 'application/json' },
66804
- body: JSON.stringify({ recipient: 'user@example.com' }),
66805
- },
66806
- );
66807
- const { automationRunId } = await response.json();
66811
+ // Start a deployed automation. inputs maps onto its event node's declared
66812
+ // outputs; for a file-upload trigger, upload first and pass the url.
66813
+ const { automationRunId } = await env.AT123.start({ recipient: 'user@example.com' });
66808
66814
 
66809
66815
  // To use an integration's credentials, call a Function that reads the integration config server-side.
66810
66816
  const config = await callFunction('FN123', { body: { integrationId: 'IN123' } });
@@ -66844,17 +66850,13 @@ Page settings (\`authorizationMode\`, \`noSandbox\`, \`hideTopBar\`, \`runtime\`
66844
66850
 
66845
66851
  \`runtime: "v2"\` serves the page from its own origin instead of the sandboxed srcdoc iframe: separate renderer process, real localStorage/sessionStorage and cookies with no noSandbox flag needed. It is versioned like every other setting \u2014 save and deploy to publish. Public (/public/:id) links always use v1 regardless of the toggle. Only v2 reaches browser devices: Web Serial, WebUSB and WebHID always work there; camera, microphone, geolocation, display-capture, fullscreen and screen-wake-lock work once listed in \`permissions\`.
66846
66852
 
66847
- **\`dataSourceUrl\` and \`filter\` are legacy.** They populate the \`data\` and \`items\` globals, frozen at render start; \`filter\` reads the old item/attribute model, which builders can no longer create or manage. For new pages use \`env\` instead \u2014 \`env.TBSxxx.sql\` for tabular data, \`env.DFxxx\` for data files. That path can depend on search params, re-query on demand, and report its own errors. Don't rewrite a working page just to migrate it.
66848
-
66849
- \`dataSourceUrl\` is fetched **verbatim** and its response becomes the \`data\` global. It is a plain static URL, not a template:
66853
+ ### Legacy data sources: \`dataSourceUrl\`, \`filter\`, \`data\`, \`items\`
66850
66854
 
66851
- - It does **not** substitute search params. Neither \`{x}\` nor Liquid \`{{ x }}\` is interpolated \u2014 the URL is fetched exactly as written. (\`filter\` does support Liquid; \`dataSourceUrl\` does not.)
66852
- - If your data depends on a search param or anything else known only at render time, don't use \`dataSourceUrl\` \u2014 fetch it in the page with \`useEffect\` + \`env\`/\`fetch\`, which is the normal path anyway.
66853
- - To clear it, pass JSON \`null\`. The string \`"null"\` is a four-character URL, not an empty value, and it fails at fetch time.
66855
+ These populate the \`data\`/\`items\` globals once, at render start, from the old item/attribute model that builders can no longer create. New pages use \`env\` (\`env.TBSxxx.sql\`, \`env.DFxxx\`), which can depend on search params, re-query on demand and report its own errors. Don't rewrite a working page just to migrate it. If you must touch them:
66854
66856
 
66855
- \`filter\`, unlike \`dataSourceUrl\`, **does** evaluate Liquid \u2014 in every string value it holds, against \`now\` (a Date rounded down to the minute, so it doesn't move mid-session) plus every search param by name. Use \`{{ now | minus: 1209600000 | toISOString }}\` for a relative date, \`{{ since }}\` for \`?since=\`. A template that won't evaluate, or that evaluates to nothing because its variable isn't on the URL, fails the whole page rather than quietly querying on an empty value.
66856
-
66857
- A templated filter is **not** an access boundary. On a public or portal page the visitor supplies the search params, so \`{"orgUnit": {"_eq": "{{ tenant }}"}}\` reads like tenant scoping but is editable from the URL bar. Scope with the workspace's own access, not with a filter.
66857
+ - \`dataSourceUrl\` is fetched verbatim \u2014 no \`{x}\` or Liquid interpolation. Anything that depends on a search param belongs in a page-side fetch instead. Clear it with JSON \`null\`; the string \`"null"\` is a four-character URL.
66858
+ - \`filter\` evaluates Liquid in every string value against \`now\` (rounded to the minute) and each search param by name, e.g. \`{{ now | minus: 1209600000 | toISOString }}\` or \`{{ since }}\`. A template whose variable is missing from the URL fails the whole page rather than querying on an empty value.
66859
+ - A templated filter is **not** an access boundary: on a public or portal page the visitor controls the search params, so \`{{ tenant }}\` scoping is editable from the URL bar. Scope with the workspace's own access.
66858
66860
 
66859
66861
  ## Critical Rules
66860
66862
 
@@ -66867,13 +66869,13 @@ A templated filter is **not** an access boundary. On a public or portal page the
66867
66869
  | \`useSessionStorageState(key, init)\` | Single-tab session state (wizard progress, scroll restoration) | Reload (same tab); cleared on tab close | **Requires \`noSandbox: true\`** |
66868
66870
  | \`useState\` | Truly ephemeral UI (hover, animation step, in-flight form field before submit) | Nothing | Lost on every navigation |
66869
66871
 
66870
- When the page uses \`useLocalStorageState\` or \`useSessionStorageState\`, set \`noSandbox: true\` in the page config first \u2014 the hooks throw a clear error otherwise.
66872
+ When the page uses \`useLocalStorageState\` or \`useSessionStorageState\` on the default v1 runtime, set \`noSandbox: true\` in the page config first \u2014 the hooks throw a clear error otherwise. A \`runtime: "v2"\` page needs no flag.
66871
66873
 
66872
66874
  2. **DO NOT import React or hooks.** React, useState, useEffect, useRef, useMemo, useCallback, and useReducer are all globally available. Importing them will cause errors.
66873
66875
  3. **The main component MUST be named \`App\` and MUST NOT be exported.** Write \`function App() { ... }\` \u2014 no \`export\`.
66874
66876
  4. **Use Tailwind CSS for styling.** It's built in.
66875
66877
  5. **Import external libraries from esm.sh.** Example: \`import confetti from "https://esm.sh/canvas-confetti@1.9.3"\`. Imports must be absolute https URLs \u2014 the sandbox cannot resolve bare or relative specifiers.
66876
- 6. **Sandbox restrictions.** The page runs in a sandboxed iframe (allow-scripts only). Raw \`localStorage\`, \`sessionStorage\`, cookies, alert/confirm/prompt, and form submissions are blocked. Use \`useLocalStorageState\` / \`useSessionStorageState\` for storage (both require disabling the sandbox \u2014 see Rule 1). Set \`noSandbox: true\` in the page config to disable the sandbox when any of these are needed.
66878
+ 6. **Sandbox restrictions (v1 runtime).** By default the page runs in a sandboxed iframe (allow-scripts only). Raw \`localStorage\`, \`sessionStorage\`, cookies, alert/confirm/prompt, and form submissions are blocked. Use \`useLocalStorageState\` / \`useSessionStorageState\` for storage (both require disabling the sandbox \u2014 see Rule 1). Set \`noSandbox: true\` to lift the sandbox, or \`runtime: "v2"\` (see Web Page Configuration) when the page also needs its own origin or browser devices.
66877
66879
  7. **Read code before editing.** Reading a large page's config returns its outline instead of the code \u2014 then read only the blocks or line ranges you are about to change. Edits return the changed lines, numbered, so don't re-read to verify. Prefer small, targeted edits \u2014 insert or search-replace \u2014 over rewriting a whole block.
66878
66880
  8. **Decompose large pages.** When a page exceeds ~200 lines, break it into multiple top-level function components (e.g., \`function DataTable() { ... }\` used inside \`function App() { ... }\`), so each can be read and edited on its own.
66879
66881
  9. **Add TSDoc comments to every top-level declaration.** Write a \`/** brief description */\` comment above each function, component, and constant. Code outlines show these comments, giving structural context without reading the full code. Always add or update these when creating or modifying a block.
@@ -66884,6 +66886,46 @@ A templated filter is **not** an access boundary. On a public or portal page the
66884
66886
  12. **Sanity-check chart math with console.log.** You cannot see the rendered output. When building or debugging visualizations, log intermediate values (scales, domains, ranges, computed positions, angles) and verify they make sense before saving. For example: \`console.log("gauge", { min, max, value, fraction, startAngle, endAngle, valueAngle })\`. Remove logs once the chart is working.
66885
66887
  13. **Rebuild imported HTML natively \u2014 never embed it.** When the user brings HTML from somewhere else (an exported dashboard, a design mockup, a page from another tool), recreate it as a native page: an \`App\` component, Tailwind, d3 for charts. Pay close attention to preserving the original styling \u2014 colors, spacing, type scale, layout. Do NOT load a foreign HTML file into a \`srcDoc\` iframe or inject it into the DOM. Embedded HTML is opaque to code outlines and reachable only by blind string patches that silently do nothing when the source drifts \u2014 so every later request turns into guesswork. Port it once and every edit after that is a normal edit. (A Liquid printable page is the exception: Liquid is HTML templating, so imported markup becomes the template body directly.)
66886
66888
 
66889
+ ## Best Practices
66890
+
66891
+ **Decide the page's purpose first, then design for it.** A page answers one question; several questions become tabs or separate pages. Most pages are one of three kinds, and each wants a different shape:
66892
+
66893
+ - **Single-flow page** \u2014 one task, start to finish (submit a request, review an item, check in). Keep it simple: one column, one primary action per screen, generous spacing. Think consumer app, not admin panel.
66894
+ - **KPI dashboard** \u2014 a few measures that matter, seen from several angles (trend, breakdown, against target). Users don't want to read a number, they want to feel it: a dense, color-accented table or a chart built for the specific question beats a row of big-number tiles. Add headline tiles only when asked.
66895
+ - **Command center** \u2014 operators who live in the page and need to act fast. Information density is the point: compact rows, inline status, filters and actions within reach, no decorative whitespace.
66896
+
66897
+ Name the kind to the user when you propose a design; if the request mixes kinds, split it rather than compromising all three.
66898
+
66899
+ **Build a clear hierarchy.** Size is only one tool. Group related elements and separate unrelated ones with spacing, borders or dividers; order sections by importance, with the thing the user came for first; distinguish primary actions from secondary ones; mute labels and metadata so values stand out. The visible structure should mirror the data model underneath, so relationships between concepts are obvious. A reader should see what matters before reading a word.
66900
+
66901
+ **Spend pixels on data, not decoration.** Don't repeat a label on every row or card when a column header or section title carries it. Use color sparingly and for meaning: if problems are red, tune the thresholds so only real problems are, or the signal is lost. No emojis. Add a tooltip where a value's origin or calculation isn't obvious. Disable a control the user can't use right now instead of hiding it, so the page teaches what it can do.
66902
+
66903
+ **Choose the layout deliberately.**
66904
+
66905
+ - A scrolling document, or a command center where panels scroll independently inside a fixed viewport? For the latter, use CSS grid over flex, fill the viewport, and don't cap the page with a top-level max-width.
66906
+ - Tables for rows the user compares; cards or grids for non-uniform items. Size each column for its content and decide which wrap and which truncate.
66907
+ - Drill in with a drawer when the surrounding context should stay visible, a large modal when the user needs focus. Make the background inert while a modal is open.
66908
+ - Decide whether the page must work on a small screen; not every page does, but check the ones that will be opened on a phone.
66909
+ - Style for both themes: the page root carries a \`dark\` class, so pair every surface and border with a \`dark:\` variant (see \`useTheme\` above).
66910
+
66911
+ **Represent data accurately.** A page that looks right but misreports the data is worse than one that shows an error. Common failures:
66912
+
66913
+ - **A query \`LIMIT\` is not a count.** Loading \`LIMIT 1000\` and labeling it "1000 items" misreports every larger table. Run a separate \`count(*)\` for totals, and label a truncated list as such ("Showing 1,000 of 14,212").
66914
+ - **Offer "Load more" when there is more.** Prefer a subtle "Load more" control over numbered pagination; either way the user must be able to reach every row.
66915
+ - **Dates are not timestamps \u2014 check the time zone before you format.** A \`date\` column (a calendar day, no time) arrives from \`env.TBSxxx.sql\` as a JS \`Date\` at 00:00 UTC, so \`toLocaleDateString()\` in any zone west of UTC shows the previous day. Format a day from its UTC fields (\`d.toISOString().slice(0, 10)\` or \`Intl.DateTimeFormat(undefined, { timeZone: "UTC", dateStyle: "medium" })\`), and never pass it through local-time math. A \`timestamptz\` is a real instant: format it in the viewer's zone and show the zone (\`timeZoneName: "short"\`) when readers may be in different places. A \`timestamp\` without zone is ambiguous \u2014 ask which zone it was written in rather than guessing. Function responses carry dates as ISO strings; the same rules apply after \`new Date(iso)\`. Day-boundary filters (\`today\`, \`this week\`) must be computed in the business's zone, in SQL via \`AT TIME ZONE\`, not in the browser's. Tell the user which columns are days and which are instants, and label the zone in the UI.
66916
+ - **Never fall back to a value that hides a data problem.** \`?? 0\`, \`?? "N/A"\`, \`?? []\` make a broken query or a missing column look like a legitimate result. Show an explicit empty or error state, and when a value is unexpectedly null, surface it (a dash, an "unknown" badge) rather than inventing a default.
66917
+
66918
+ **Embed PDFs with \`<object>\` on a v2 page.** When \`runtime: "v2"\`, render a PDF with \`<object data={url} type="application/pdf" />\` rather than an \`<iframe>\`.
66919
+
66920
+ **Link stored files through the app host.** Storage URLs are persisted as \`https://api.<host>/v1/storage/...\`, where the browser holds no session. Before rendering one as a link, embed or download, rewrite it to the app origin's proxy, \`https://app.<host>/api/v1/storage/...\`:
66921
+
66922
+ \`\`\`typescript
66923
+ const browserStorageUrl = (url: string) =>
66924
+ url.replace(/^https:\\/\\/api\\.([^/]+)\\/v1\\/storage\\//, "https://app.$1/api/v1/storage/");
66925
+ \`\`\`
66926
+
66927
+ The edge also redirects a credential-less api.* read there, so an unrewritten link degrades to an extra hop rather than failing. The proxy authenticates with the signed-in session cookie, so it works for workspace members only. On a public page the visitor has no session: use \`presignFile\` for links there. Page code calling \`fetchFile\`, \`env.STORAGE.fetch\` or \`presignFile\` is unaffected either way \u2014 those carry their own credentials.
66928
+
66887
66929
  ## Known-Good Libraries
66888
66930
 
66889
66931
  These libraries are tested and work from \`https://esm.sh/...\`. Do NOT import React \u2014 it's already global.
@@ -66893,10 +66935,15 @@ These libraries are tested and work from \`https://esm.sh/...\`. Do NOT import R
66893
66935
  - **date-fns** \u2014 \`import { format, parseISO } from "https://esm.sh/date-fns@4"\` \u2014 Date formatting and manipulation.
66894
66936
  - **papaparse** \u2014 \`import Papa from "https://esm.sh/papaparse@5"\` \u2014 CSV parsing.
66895
66937
 
66896
- ### Do NOT use these libraries (broken ESM imports):
66938
+ ### Do NOT use libraries that depend on React
66939
+
66940
+ An esm.sh build of a React library imports its own React (\`import "/react@>=16.8"\`), a second copy beside the page's global one, so every hook inside it fails with "Invalid hook call". The page has no import map to redirect that specifier, so there is no workaround. This rules out, among others:
66941
+
66897
66942
  - **react-resize-detector** \u2014 Use a \`ResizeObserver\` hook instead (see pattern below).
66898
66943
  - **@tanstack/react-table** \u2014 Build tables with plain \`<table>\` + Tailwind + \`useState\` for sorting/filtering/pagination.
66899
- - **recharts, visx** \u2014 Use d3 directly instead.
66944
+ - **recharts, visx, and any other React chart or component kit** \u2014 Use d3 directly instead.
66945
+
66946
+ React-free libraries (d3, date-fns, papaparse) import fine.
66900
66947
 
66901
66948
  ### Responsive chart pattern
66902
66949
 
@@ -67727,7 +67774,8 @@ Usage:
67727
67774
  overai sql credential rotate|revoke <credentialId>
67728
67775
 
67729
67776
  Workspace sync (edit resources as local files):
67730
- overai clone <workspaceId> [dir] Clone workspace and accessible descendants
67777
+ overai clone <workspaceId> [dir] Clone workspace and accessible descendants;
67778
+ .overai/ holds sync state, ignored by git
67731
67779
  overai status [--recursive] Local edits + remote drift
67732
67780
  overai diff [filter] [--from <ref>] [--to <ref>] [--recursive]
67733
67781
  Diff versions (refs: N, base, local, deployed, latest;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@overaihq/cli",
3
- "version": "0.14.12",
3
+ "version": "0.14.13",
4
4
  "description": "Create, edit, deploy, and run OverAI workspace resources from the command line",
5
5
  "type": "module",
6
6
  "homepage": "https://overai.com",