@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.
- package/README.md +6 -0
- package/dist/index.js +97 -49
- 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
|
-
|
|
62379
|
+
const dir = join3(root, MANIFEST_DIR);
|
|
62380
|
+
mkdirSync2(dir, { recursive: true });
|
|
62378
62381
|
writeFileSync2(
|
|
62379
|
-
join3(
|
|
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
|
|
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:
|
|
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
|
|
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
|
-
//
|
|
66797
|
-
//
|
|
66798
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
66856
|
-
|
|
66857
|
-
A templated filter is **not** an access boundary
|
|
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
|
|
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.**
|
|
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
|
|
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;
|