diff --git a/.playwright-mcp/console-2026-02-08T10-34-13-899Z.log b/.playwright-mcp/console-2026-02-08T10-34-13-899Z.log new file mode 100644 index 0000000..3245cfb --- /dev/null +++ b/.playwright-mcp/console-2026-02-08T10-34-13-899Z.log @@ -0,0 +1 @@ +[ 1159ms] [ERROR] Failed to load resource: the server responded with a status of 404 (File not found) @ http://localhost:8234/favicon.ico:0 diff --git a/.playwright-mcp/console-2026-02-08T10-37-18-687Z.log b/.playwright-mcp/console-2026-02-08T10-37-18-687Z.log new file mode 100644 index 0000000..c43b359 --- /dev/null +++ b/.playwright-mcp/console-2026-02-08T10-37-18-687Z.log @@ -0,0 +1 @@ +[ 158ms] [ERROR] Failed to load resource: the server responded with a status of 404 (File not found) @ http://localhost:8235/favicon.ico:0 diff --git a/.playwright-mcp/console-2026-02-08T10-41-22-731Z.log b/.playwright-mcp/console-2026-02-08T10-41-22-731Z.log new file mode 100644 index 0000000..c176b52 --- /dev/null +++ b/.playwright-mcp/console-2026-02-08T10-41-22-731Z.log @@ -0,0 +1 @@ +[ 240ms] [ERROR] Failed to load resource: the server responded with a status of 404 (File not found) @ http://localhost:8236/favicon.ico:0 diff --git a/.playwright-mcp/console-2026-02-08T10-45-05-101Z.log b/.playwright-mcp/console-2026-02-08T10-45-05-101Z.log new file mode 100644 index 0000000..abc3d56 --- /dev/null +++ b/.playwright-mcp/console-2026-02-08T10-45-05-101Z.log @@ -0,0 +1 @@ +[ 130ms] [ERROR] Failed to load resource: the server responded with a status of 404 (File not found) @ http://localhost:8237/favicon.ico:0 diff --git a/evals/.gitignore b/evals/.gitignore new file mode 100644 index 0000000..71cefbc --- /dev/null +++ b/evals/.gitignore @@ -0,0 +1,14 @@ +# Dependencies +node_modules/ + +# Build output +dist/ + +# Results (generated reports) +results/*.md + +# Copied skills in fixtures (synced from parent /skills directory by setup script) +fixtures/*/project/skills/ + +# OS files +.DS_Store diff --git a/evals/README.md b/evals/README.md new file mode 100644 index 0000000..84ab05e --- /dev/null +++ b/evals/README.md @@ -0,0 +1,449 @@ +# Skills Eval System + +An evaluation system for testing skill activation and result correctness against Claude Code (or other coding agents). + +## Quick Start + +```bash +# Install dependencies +npm install + +# Run all evals (automatically syncs skills from parent directory) +npm run eval + +# Run with a named run +npm run eval -- --name "baseline" + +# Filter to specific fixtures +npm run eval -- --filter nextjs --verbose + +# Run without re-syncing skills (faster for repeated runs) +npm run eval:only -- --name "quick-test" +``` + +### Setup + +The `npm run eval` command automatically runs `npm run setup` first, which copies skills from the parent `../skills/` directory into each fixture. This ensures fixtures always test against the latest skill versions. + +To manually sync skills: +```bash +npm run setup +``` + +## Directory Structure + +``` +evals/ +├── src/ # Source code +│ ├── index.ts # CLI entry point +│ ├── runner.ts # Eval runner orchestration +│ ├── agents/ # Coding agent implementations +│ │ ├── interface.ts # CodingAgent interface +│ │ └── claude-code.ts # Claude Code CLI implementation +│ ├── checks/ # Check implementations +│ │ ├── interface.ts # Check registry +│ │ ├── entity-config.ts # Entity schema validation +│ │ ├── function-def.ts # Function definition validation +│ │ ├── agent-config.ts # Agent config validation +│ │ ├── contains.ts # String containment check +│ │ ├── file-exists.ts # File existence check +│ │ ├── file-content.ts # File content pattern matching +│ │ ├── command-passes.ts # Command execution check +│ │ ├── valid-json.ts # JSON validity check +│ │ └── json-schema.ts # Generic JSON schema validation +│ ├── reporters/ +│ │ └── markdown.ts # Markdown report generator +│ └── types.ts # Shared types +├── fixtures/ # Test fixtures (realistic projects) +│ ├── nextjs-todo/ # Next.js app - add todo entities +│ ├── react-task-manager/ # React app - implement SDK usage +│ └── existing-app-add-feature/ # Add backend function +├── results/ # Generated reports +├── eval.schema.json # JSON Schema for eval.json files +├── package.json +└── tsconfig.json +``` + +## Fixtures + +Each fixture is a **complete, isolated project** that the agent runs against: + +``` +fixture-name/ +├── eval.json # Eval config: prompt, expected outcomes, checks +├── AGENTS.md # Project context for the agent +└── project/ # The actual project directory (cwd for agent) + ├── .claude/ + │ └── settings.json # Claude settings (skills paths) + ├── skills/ # Skills (copied by setup script) + │ ├── base44-cli/ + │ └── base44-sdk/ + ├── base44/ + │ ├── config.jsonc + │ └── entities/ + ├── src/ + ├── package.json + └── ... +``` + +**Important:** Skills are copied into `project/skills/` by the setup script. The `.claude/settings.json` file inside `project/` references these skills. + +### Current Fixtures + +| Fixture | Description | Tests | +|---------|-------------|-------| +| `nextjs-todo` | Next.js app needing todo entities | Entity creation, file structure, CLI commands | +| `react-task-manager` | React app with existing Task entity | SDK usage, API calls, React hooks | +| `existing-app-add-feature` | App needing a backend function | Function creation, Deno patterns, integrations | + +## Creating a New Test + +1. **Create a fixture directory**: + ```bash + mkdir -p fixtures/my-new-test/project + ``` + +2. **Create `eval.json`** with the schema reference for IDE autocomplete: + ```json + { + "$schema": "../../eval.schema.json", + "name": "my-new-test", + "description": "Test that validates specific behavior", + "prompt": "Your prompt to Claude here", + "expectedSkills": ["base44-sdk"], + "checks": [ + { + "type": "contains", + "description": "Output mentions the feature", + "value": "feature" + } + ] + } + ``` + +3. **Add project files**: Create `project/.claude/settings.json` and any starter files needed. + +4. **Add `AGENTS.md`** with project context for the agent. + +5. **Run setup and test**: + ```bash + npm run setup + npm run eval -- --filter my-new-test --verbose + ``` + +## eval.json Format + +```json +{ + "$schema": "../../eval.schema.json", + "name": "nextjs-todo-entities", + "description": "Test creating Todo and Category entities", + "prompt": "Add entities for a todo app...", + "expectedSkills": ["base44-cli", "base44-sdk"], + "checks": [ + { + "type": "file-exists", + "description": "Todo entity file created", + "filePath": "base44/entities/todo.jsonc" + }, + { + "type": "file-content", + "description": "Todo has title property", + "filePath": "base44/entities/todo.jsonc", + "pattern": "\"title\"\\s*:\\s*\\{" + }, + { + "type": "valid-json", + "description": "Todo entity is valid JSON", + "filePath": "base44/entities/todo.jsonc" + }, + { + "type": "contains", + "description": "mentions entities push", + "value": "entities push" + } + ] +} +``` + +The `$schema` reference enables IDE features like autocomplete, validation, and inline documentation in VS Code and other editors. + +## Check Types Reference + +| Type | Description | Required Fields | Optional Fields | +|------|-------------|-----------------|-----------------| +| `contains` | Search for text in agent output | `value` | | +| `file-exists` | Verify a file was created | `filePath` | | +| `file-content` | Match content in a file | `filePath` | `pattern`, `value` | +| `valid-json` | Validate JSON/JSONC syntax | `filePath` | | +| `command-passes` | Run a shell command | `command` | | +| `json-schema` | Validate output against schema | `schema` | `filePath` | +| `entity-config` | Validate Base44 entity config | | `filePath`, `target`, `expectedValid` | +| `agent-config` | Validate Base44 agent config | | `filePath`, `target`, `expectedValid` | +| `function-def` | Validate Base44 function config | | `filePath`, `target`, `expectedValid` | + +### Check Examples + +#### `contains` - Search agent output + +Checks if the agent's text output contains a substring (case-insensitive). + +```json +{ + "type": "contains", + "description": "Output mentions entities", + "value": "entities" +} +``` + +#### `file-exists` - Verify file creation + +Checks if a file exists at the specified path (relative to project directory). + +```json +{ + "type": "file-exists", + "description": "Component file created", + "filePath": "src/components/TaskList.tsx" +} +``` + +#### `file-content` - Match file content with regex + +Checks if file content matches a regular expression pattern (case-insensitive). + +```json +{ + "type": "file-content", + "description": "Uses React hooks", + "filePath": "src/components/TaskList.tsx", + "pattern": "useState|useEffect" +} +``` + +#### `file-content` - Match file content with exact value + +Checks if file contains an exact substring. + +```json +{ + "type": "file-content", + "description": "Imports base44", + "filePath": "src/components/TaskList.tsx", + "value": "import base44" +} +``` + +#### `valid-json` - Validate JSON syntax + +Verifies the file contains valid JSON/JSONC (supports `//` and `/* */` comments). + +```json +{ + "type": "valid-json", + "description": "Config is valid JSON", + "filePath": "base44/entities/todo.jsonc" +} +``` + +#### `command-passes` - Run shell command + +Runs a shell command and passes if exit code is 0. Commands have a 2-minute timeout. + +```json +{ + "type": "command-passes", + "description": "TypeScript compiles", + "command": "npx tsc --noEmit" +} +``` + +#### `json-schema` - Validate against JSON Schema + +Validates JSON in agent output (or a file) against a JSON Schema definition. + +```json +{ + "type": "json-schema", + "description": "Output matches expected structure", + "schema": { + "type": "object", + "properties": { + "name": { "type": "string" }, + "version": { "type": "string" } + }, + "required": ["name"] + } +} +``` + +#### `entity-config` - Validate Base44 entity schema + +Validates that a file contains a valid Base44 entity configuration with `name`, `properties`, etc. + +```json +{ + "type": "entity-config", + "description": "Valid entity schema", + "filePath": "base44/entities/todo.jsonc", + "target": "file" +} +``` + +When `target` is omitted or set to `"output"`, extracts entity JSON from the agent's output. + +#### `agent-config` - Validate Base44 agent config + +Validates that a file contains a valid Base44 agent configuration with `name`, `instructions`, `tool_configs`, etc. + +```json +{ + "type": "agent-config", + "description": "Valid agent configuration", + "filePath": "base44/agents/support_agent.jsonc", + "target": "file" +} +``` + +#### `function-def` - Validate Base44 function config + +Validates that a file contains a valid Base44 function definition with `name`, `entrypoint`, etc. + +```json +{ + "type": "function-def", + "description": "Valid function definition", + "filePath": "base44/functions/my-func/function.jsonc", + "target": "file" +} +``` + +## CLI Options + +``` +-n, --name Name for this eval run (default: "default") +-a, --agent Agent to use (default: "claude-code") +-f, --fixtures Fixtures directory (default: "fixtures") +-o, --output Output directory for reports (default: "results") +-v, --verbose Verbose output +--filter Filter fixtures by name pattern +``` + +## Reports + +Reports are generated in `results/` with timestamped filenames: + +``` +results/ +├── run-2026-02-02-143052-default.md +├── run-2026-02-02-150312-with-new-skill.md +└── latest.md → (symlink to most recent) +``` + +Example report format: + +```markdown +# Eval Run: baseline +**Date**: 2026-02-02 15:03:12 +**Agent**: claude-code +**Fixtures**: 3 + +## Summary +| Status | Count | +|--------|-------| +| ✅ Passed | 2 | +| ❌ Failed | 1 | + +## nextjs-todo Suite + +### ✅ nextjs-todo-entities +**Fixture**: `fixtures/nextjs-todo/` +**Prompt**: Add entities for a todo app... + +- **Expected Skills**: base44-cli, base44-sdk ✓ +- **Skills Invoked**: base44-cli, base44-sdk +- **Checks**: + | Check | Status | Details | + |-------|--------|---------| + | Todo entity file created | ✅ | File exists | + | Todo has title property | ✅ | Pattern found | +``` + +## Adding Custom Checks + +To add a new check type: + +1. **Create the check class** in `src/checks/`: + ```typescript + // src/checks/my-check.ts + import type { Check, CheckConfig, CheckResult, EvalContext } from '../types.js'; + + export class MyCheck implements Check { + type = 'my-check'; + private config: CheckConfig; + + constructor(config: CheckConfig) { + this.config = config; + } + + async run(context: EvalContext): Promise { + const { agentResponse, projectDir } = context; + + // Your validation logic here + const passed = /* ... */; + + return { + name: this.config.description, + passed, + details: passed ? 'Check passed' : 'Check failed', + }; + } + } + ``` + +2. **Register it** in `src/checks/interface.ts`: + ```typescript + import { MyCheck } from './my-check.js'; + + // In createCheck function: + case 'my-check': + return new MyCheck(config); + ``` + +3. **Update the schema** in `eval.schema.json`: + - Add your type to the `type` enum + - Add a conditional schema for your check's fields + +4. **Update this README** with documentation for your new check type. + +### EvalContext + +Check implementations receive an `EvalContext` with: + +```typescript +interface EvalContext { + agentResponse: { + output: string; // Claude's text response + skillsInvoked: string[]; // Skills that were invoked + metadata?: Record; + }; + fixtureDir: string; // Original fixture directory + projectDir: string; // Temp directory where agent ran + checkConfig: CheckConfig; +} +``` + +## Skill Detection + +The eval system detects skill activation by pattern matching in the agent's output: + +**base44-cli patterns:** +- `npx base44 entities push`, `functions deploy`, etc. +- File paths like `base44/entities/*.jsonc` +- `Deno.serve`, `createClientFromRequest` + +**base44-sdk patterns:** +- `@base44/sdk` imports +- `entities.Task.list()`, `entities.Task.create()` +- `integrations.Core.SendEmail` +- `asServiceRole` diff --git a/evals/docs/eval-slides.html b/evals/docs/eval-slides.html new file mode 100644 index 0000000..9204a5a --- /dev/null +++ b/evals/docs/eval-slides.html @@ -0,0 +1,743 @@ + + + + + + Skill Evals: Regression Testing for Claude Code Skills + + + + + + + +
+
+ + +
+

Skill Evals

+

Regression Testing for Claude Code Skills

+
+

+ Automated verification that Claude Code uses correct Base44 APIs
+ instead of hallucinating Firebase / Supabase patterns +

+
+ + +
+

The Hallucination Problem

+

+ Without skill documentation, Claude falls back to pre-training patterns +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
APICorrect (Base44)Hallucinated
Create recordentities.Product.create(data)Product.insert(data)
Filter recordsentities.Product.filter(query)Product.find(query) / .where()
Auth loginloginViaEmailPassword(email, pw)signInWithEmailAndPassword()
OAuth providerloginWithProvider('google')signInWithGoogle()
SDK importimport { ... } from "@base44/sdk"import { ... } from "firebase"
Config initcreateClient({ appId })initializeApp({ apiKey })
+
+ + +
+

What Value Does This Provide?

+
+
+

Regression Testing

+

Every change to skill documentation is tested against 16 fixtures and 30+ prompts. Catch regressions before they ship.

+
+
+

Strategy Comparison

+

Compare documentation approaches (AGENTS.md format, verbosity level, context priming) to find the most effective strategy.

+
+
+

Consistency Measurement

+

Run the same fixture N times to measure how reliably Claude produces correct output. Quantify flakiness.

+
+
+
+ + +
+

Architecture Overview

+
+
+
Experiments
+
CLAUDE.md + AGENTS.md
+ skills/
+
+
+
+
Setup
+
Inject skills &
docs into fixtures
+
+
+
+
Fixtures
+
eval.json + project/
16 test scenarios
+
+
+
+
Runner
+
Copy to /tmp
Run agent
+
+
+
+
Checks
+
10 check types
Validate output
+
+
+
+
Reports
+
Pass/fail matrix
Best experiment
+
+
+
+ src/experiments.tssrc/setup.tssrc/runner.tssrc/checks/src/compare-runner.ts +
+
+ + +
+

Fixture Anatomy

+
+
+ fixtures/sdk-entity-crud/
+   eval.json← prompts + checks
+   project/← working directory for agent
+     CLAUDE.md← injected by setup
+     AGENTS.md← injected by setup
+     skills/← injected from experiment
+       base44-sdk/
+         SKILL.md
+         references/← symlink
+       base44-cli/
+         SKILL.md
+         references/← symlink
+     src/← optional starter code
+
+
+

Experiment injection:

+
    +
  • setup.ts copies experiment's skills/ into each fixture's project/skills/
  • +
  • CLAUDE.md and AGENTS.md copied from experiment dir if present
  • +
  • Baseline experiment = empty dir, no docs injected
  • +
+

At runtime:

+
    +
  • Entire fixture copied to /tmp/eval-*
  • +
  • Agent runs inside project/
  • +
  • Checks validate output files + stdout
  • +
  • Temp dir cleaned up after each run
  • +
+
+
+
+ + +
+

eval.json Format

+
{
+  "$schema": "../../eval.schema.json",
+  "name": "anti-hallucination",
+  "description": "Verify correct Base44 APIs, block hallucinated patterns",
+  "prompts": [
+    {
+      "name": "anti-hallucination-auth",
+      "prompt": "Create a React login page with email/password login,
+                 Google OAuth, registration, and logout...",
+      "expectedSkills": ["base44-sdk"],
+      "checks": [
+        {
+          "type": "file-exists",
+          "description": "Login component created",
+          "filePath": "src/components/Login.jsx"
+        },
+        {
+          "type": "file-content",
+          "description": "Uses loginViaEmailPassword",
+          "filePath": "src/components/Login.jsx",
+          "pattern": "loginViaEmailPassword"
+        },
+        {
+          "type": "file-content-excluded",
+          "description": "No Firebase signIn pattern",
+          "filePath": "src/components/Login.jsx",
+          "pattern": "signInWithEmailAndPassword"
+        }
+      ]
+    }
+  ]
+}
+

+ The three-check pattern: file-exists → + file-content (correct API) → + file-content-excluded (hallucinated API) +

+
+ + +
+

The 10 Check Types

+
+
+

File System

+
    +
  • file-exists — Verify a file was created at the expected path
  • +
  • file-content — Assert a regex pattern exists in a file
  • +
  • file-content-excluded — Assert a pattern does NOT appear in a file
  • +
+
+
+

Output

+
    +
  • contains — Check agent stdout for a substring
  • +
  • valid-json — Verify a file contains parseable JSON
  • +
  • command-passes — Run a shell command, assert exit code 0
  • +
+
+
+

Structural

+
    +
  • json-schema — Validate file contents against a JSON Schema
  • +
+
+
+

Domain-Specific (Base44)

+
    +
  • entity-config — Validate entity schema: PascalCase name, type:object, properties, required
  • +
  • agent-config — Validate agent schema: snake_case name, instructions, tool_configs
  • +
  • function-def — Validate function config: name + entry (not entrypoint)
  • +
+
+
+
+ + +
+

Experiments

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Featurebaselineagents-mdagents-md
-combined
verbose
-index
context
-first
SKILL.md files
AGENTS.md API index
CLAUDE.md priming
Expanded signatures
Retrieval-led reasoning hint
Common Mistakes section
+
+
+ baseline
+ No docs at all. Control group — shows what hallucinations look like without guidance. +
+
+ agents-md
+ Compact API index in AGENTS.md listing modules + method names. Skills with SKILL.md + references. No CLAUDE.md. +
+
+ agents-md-combined
+ Same AGENTS.md index + CLAUDE.md that primes agent to explore project structure first and prefer retrieval over pre-training. +
+
+ verbose-index
+ AGENTS.md with expanded method signatures including param types and return types. More verbose, potentially more precise. +
+
+ context-first
+ CLAUDE.md with “read docs first” hint + compact AGENTS.md index. Tests if retrieval priming improves accuracy. +
+
+
+ + +
+

Execution Flow

+
+
+
1 Discover fixtures
+
+
2 Expand multi-prompt
+
+
3 Group by suite
+
+
+
+
4 Copy to /tmp
+
+
5 Run agent in project/
+
+
6 Check expectedSkills
+
+
+
+
7 Run check suite
+
+
8 Aggregate results
+
+
9 Report pass/fail
+
+
+
+ Concurrency: up to 5 fixtures run in parallel — configurable via --concurrency flag +
+
+ + +
+

Results: Experiment Comparison

+

5 experiments × 16 fixtures (45 expanded prompts)

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
ExperimentPassedFailedPass Rate
agents-md-combined38784%
agents-md-context-first36980%
agents-md341176%
agents-md-verbose-index321371%
baseline212447%
+
+
+ +
+
+ combined +
+ 84% +
+
+ context-first +
+ 80% +
+
+ agents-md +
+ 76% +
+
+ verbose-index +
+ 71% +
+
+ baseline +
+ 47% +
+
+

+ bestExperiment: agents-md-combined +

+
+
+

+ CLAUDE.md retrieval priming + AGENTS.md compact index = highest pass rate. Verbose signatures did not help — more docs != better results. +

+
+ + +
+

Test Coverage: 16 Fixtures

+
+
+

SDK / Frontend (7)

+
    +
  • sdk-entity-crud (6 prompts)
  • +
  • sdk-auth
  • +
  • sdk-functions
  • +
  • sdk-integrations
  • +
  • anti-hallucination (4 prompts)
  • +
  • agent-chat-component
  • +
  • react-task-manager
  • +
+
+
+

CLI / Config (5)

+
    +
  • cli-entities (6 prompts)
  • +
  • cli-agents (4 prompts)
  • +
  • cli-functions
  • +
  • create-support-agent
  • +
  • skill-check
  • +
+
+
+

Cross-Cutting (4)

+
    +
  • fullstack (4 prompts)
  • +
  • existing-app-add-feature
  • +
  • nextjs-todo
  • +
  • agent-with-function
  • +
+
+
+

+ Multi-prompt fixtures expand to 30+ individual test prompts. + Each prompt has its own expectedSkills and checks[]. +

+
+ + +
+

Adding a New Fixture

+
+
+
Step 1: Create eval.json
+
mkdir evals/fixtures/my-new-fixture
+# $schema gives autocomplete in VS Code
+cat > evals/fixtures/my-new-fixture/eval.json
+
+
+
Step 2: Add project/ with starter files
+
mkdir -p evals/fixtures/my-new-fixture/project/src
+# Add any starter code the agent needs
+# project/ becomes the agent's working directory
+
+
+
Step 3: Run it
+
cd evals
+npm run eval -- --fixtures my-new-fixture --verbose
+
+
+

+ Reference eval.schema.json with "$schema": "../../eval.schema.json" for IDE validation and autocomplete. +

+
+ + +
+

Adding a New Experiment

+
+
+
Step 1: Create experiment directory
+
+ experiments/my-experiment/
+   CLAUDE.md← optional project-level instructions
+   AGENTS.md← optional API index
+   skills/
+     base44-sdk/
+       SKILL.md
+       references/← symlink
+
+
+
+
Step 2: Symlink references to avoid duplication
+
cd experiments/my-experiment/skills/base44-sdk
+ln -s ../../../../../skills/base44-sdk/references references
+
+
+
Step 3: Run comparison
+
cd evals
+npm run eval:compare
+
+
+
+ + +
+

Daily Workflows

+
+
Validate a skill change
+
# Edit skills/base44-sdk/SKILL.md, then:
+cd evals && npm run eval -- --verbose
+

Run all fixtures against the current skill documentation

+
+
+
Compare documentation strategies
+
cd evals && npm run eval:compare
+# Runs all experiments x all fixtures
+# Outputs pass/fail matrix + bestExperiment
+

Find which AGENTS.md format produces the highest pass rate

+
+
+
Check consistency (repeated runs)
+
cd evals && tsx src/run-repeated.ts --runs 5 \
+  --fixtures anti-hallucination --verbose
+

Run the same fixture N times to measure flakiness and consistency

+
+
+ + +
+

Bottom Line

+
+
+
+

The Problem

+

Claude's pre-training data contains far more Firebase/Supabase patterns than Base44 APIs. Without skill documentation, it hallucinates wrong method names, wrong imports, and wrong auth flows.

+
+
+

The Solution

+

16 fixtures with 30+ prompts that verify correct API usage and reject hallucinated patterns. Each fixture uses the three-check pattern: file exists, correct API present, hallucinated API absent.

+
+
+
+

The Impact

+
+
+ Every skill change is testable. Run npm run eval before merging to catch regressions in documentation that cause hallucinations. +
+
+ Documentation strategies are comparable. The compare runner quantifies which AGENTS.md format, verbosity level, or priming approach works best. +
+
+ Consistency is measurable. Repeated runs reveal whether Claude reliably uses correct APIs or just gets lucky sometimes. +
+
+
+

+ Skills that pass evals → fewer hallucinations in production → users get working code on the first try. +

+
+
+ +
+
+ + + + + + diff --git a/evals/eval.schema.json b/evals/eval.schema.json new file mode 100644 index 0000000..f734165 --- /dev/null +++ b/evals/eval.schema.json @@ -0,0 +1,291 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Eval Configuration", + "description": "Configuration for a skills eval test case. Supports single-prompt (prompt + checks) or multi-prompt (prompts array) mode.", + "type": "object", + "required": ["name", "description"], + "properties": { + "$schema": { + "type": "string", + "description": "JSON Schema reference for IDE support" + }, + "name": { + "type": "string", + "description": "Unique identifier for the test case" + }, + "description": { + "type": "string", + "description": "Human-readable description of what the test validates" + }, + "prompt": { + "type": "string", + "description": "The prompt sent to Claude for this test (single-prompt mode)" + }, + "expectedSkills": { + "type": "array", + "items": { + "type": "string" + }, + "description": "List of skill names that should be detected/invoked during the test (single-prompt mode)" + }, + "checks": { + "type": "array", + "items": { + "$ref": "#/definitions/check" + }, + "description": "Assertions to run against the agent's output and generated files (single-prompt mode)" + }, + "prompts": { + "type": "array", + "items": { + "$ref": "#/definitions/promptEntry" + }, + "description": "Array of prompt entries for multi-prompt mode. Each prompt is independently executed and evaluated." + } + }, + "definitions": { + "promptEntry": { + "type": "object", + "required": ["name", "prompt", "expectedSkills", "checks"], + "properties": { + "name": { + "type": "string", + "description": "Unique identifier for this prompt within the fixture" + }, + "description": { + "type": "string", + "description": "Human-readable description of what this prompt tests" + }, + "prompt": { + "type": "string", + "description": "The prompt sent to Claude" + }, + "expectedSkills": { + "type": "array", + "items": { + "type": "string" + }, + "description": "List of skill names that should be detected/invoked" + }, + "checks": { + "type": "array", + "items": { + "$ref": "#/definitions/check" + }, + "description": "Assertions to run against the agent's output and generated files" + } + } + }, + "check": { + "type": "object", + "required": ["type", "description"], + "properties": { + "type": { + "type": "string", + "enum": [ + "contains", + "file-exists", + "file-content", + "file-content-excluded", + "valid-json", + "command-passes", + "json-schema", + "entity-config", + "agent-config", + "function-def" + ], + "description": "The type of check to perform" + }, + "description": { + "type": "string", + "description": "Human-readable description of what this check validates" + } + }, + "allOf": [ + { + "if": { + "properties": { "type": { "const": "contains" } } + }, + "then": { + "properties": { + "value": { + "type": "string", + "description": "The substring to search for in the agent's output (case-insensitive)" + } + }, + "required": ["value"] + } + }, + { + "if": { + "properties": { "type": { "const": "file-exists" } } + }, + "then": { + "properties": { + "filePath": { + "type": "string", + "description": "Path to the file (relative to project directory)" + } + }, + "required": ["filePath"] + } + }, + { + "if": { + "properties": { "type": { "const": "file-content" } } + }, + "then": { + "properties": { + "filePath": { + "type": "string", + "description": "Path to the file (relative to project directory)" + }, + "pattern": { + "type": "string", + "description": "Regular expression pattern to match against file content" + }, + "value": { + "type": "string", + "description": "Exact substring to find in file content (alternative to pattern)" + } + }, + "required": ["filePath"] + } + }, + { + "if": { + "properties": { "type": { "const": "file-content-excluded" } } + }, + "then": { + "properties": { + "filePath": { + "type": "string", + "description": "Path to the file (relative to project directory)" + }, + "pattern": { + "type": "string", + "description": "Regular expression pattern that should NOT be found in file content" + } + }, + "required": ["filePath", "pattern"] + } + }, + { + "if": { + "properties": { "type": { "const": "valid-json" } } + }, + "then": { + "properties": { + "filePath": { + "type": "string", + "description": "Path to the JSON/JSONC file to validate" + } + }, + "required": ["filePath"] + } + }, + { + "if": { + "properties": { "type": { "const": "command-passes" } } + }, + "then": { + "properties": { + "command": { + "type": "string", + "description": "Shell command to run (must exit with code 0 to pass)" + } + }, + "required": ["command"] + } + }, + { + "if": { + "properties": { "type": { "const": "json-schema" } } + }, + "then": { + "properties": { + "filePath": { + "type": "string", + "description": "Path to the JSON file to validate (optional, defaults to agent output)" + }, + "schema": { + "type": "object", + "description": "JSON Schema to validate against" + } + }, + "required": ["schema"] + } + }, + { + "if": { + "properties": { "type": { "const": "entity-config" } } + }, + "then": { + "properties": { + "filePath": { + "type": "string", + "description": "Path to the entity config file (optional, extracts from output if not specified)" + }, + "target": { + "type": "string", + "enum": ["output", "file"], + "description": "Where to find the entity config" + }, + "expectedValid": { + "type": "boolean", + "default": true, + "description": "Whether the config is expected to be valid" + } + } + } + }, + { + "if": { + "properties": { "type": { "const": "agent-config" } } + }, + "then": { + "properties": { + "filePath": { + "type": "string", + "description": "Path to the agent config file (optional, extracts from output if not specified)" + }, + "target": { + "type": "string", + "enum": ["output", "file"], + "description": "Where to find the agent config" + }, + "expectedValid": { + "type": "boolean", + "default": true, + "description": "Whether the config is expected to be valid" + } + } + } + }, + { + "if": { + "properties": { "type": { "const": "function-def" } } + }, + "then": { + "properties": { + "filePath": { + "type": "string", + "description": "Path to the function config file (optional, extracts from output if not specified)" + }, + "target": { + "type": "string", + "enum": ["output", "file"], + "description": "Where to find the function config" + }, + "expectedValid": { + "type": "boolean", + "default": true, + "description": "Whether the config is expected to be valid" + } + } + } + } + ] + } + } +} diff --git a/evals/experiments/agents-md-combined/AGENTS.md b/evals/experiments/agents-md-combined/AGENTS.md new file mode 100644 index 0000000..6c0f2be --- /dev/null +++ b/evals/experiments/agents-md-combined/AGENTS.md @@ -0,0 +1,25 @@ +IMPORTANT: Prefer retrieval-led reasoning over pre-training-led reasoning. Base44 is NOT Firebase, Supabase, or any other SDK you know. Every API name below is the ONLY correct form. Always verify against this index and the referenced docs. + +[Base44 SDK Docs Index]|root: ./skills/base44-sdk/references +|entities.md: CRUD - create(data), list(), filter(query), get(id), update(id,data), delete(id), subscribe(cb) +|auth.md: Auth - loginViaEmailPassword(), loginWithProvider('google'), me(), register(), logout() +|base44-agents.md: Agents - createConversation({agent_name}), addMessage(conv,{role,content}), subscribeToConversation(id,cb) +|functions.md: Functions - invoke(name, data) +|integrations.md: Core - InvokeLLM({prompt}), SendEmail({to,subject,body}), UploadFile({file}) +|client.md: Client - createClient({appId}), import from "@base44/sdk" +|QUICK_REFERENCE.md: All method signatures + +[Base44 CLI Docs Index]|root: ./skills/base44-cli/references +|entities-create.md: Entity schema - {name:"PascalCase", type:"object", properties:{}, required:[]} +|functions-create.md: Function - function.jsonc {name,entry} + index.ts with Deno.serve + createClientFromRequest +|create.md: Project creation - npx base44 create name -p . +|deploy.md: Deploy - npx base44 deploy -y +|agents-push.md: Agent config - {name:"snake_case", description, instructions, tool_configs:[{entity_name,allowed_operations}|{function_name,description}]} + +[Common Mistakes - NEVER use these hallucinated names] +|SDK: Do NOT use find() → use filter(), findOne() → get(), insert() → create(), remove() → delete(), onChange() → subscribe() +|SDK: Do NOT use signInWithGoogle() → use loginWithProvider('google'), functions.call() → functions.invoke() +|SDK: Do NOT use ai.generate() → use integrations.Core.InvokeLLM({prompt}) +|CLI: Entity names must be PascalCase alphanumeric. File names must be kebab-case.jsonc +|CLI: Agent names must be lowercase with underscores only. Function entry field is "entry" not "entrypoint" +|CLI: Backend functions import from "npm:@base44/sdk" (Deno requires npm: prefix) diff --git a/evals/experiments/agents-md-combined/CLAUDE.md b/evals/experiments/agents-md-combined/CLAUDE.md new file mode 100644 index 0000000..724ce4d --- /dev/null +++ b/evals/experiments/agents-md-combined/CLAUDE.md @@ -0,0 +1,7 @@ +# Base44 Development Guidelines + +When working with Base44 projects, first explore the existing project structure to understand what already exists. Look at existing code, config files, and patterns in use. + +Then consult the AGENTS.md index in the project root for correct API patterns. For detailed documentation, read the relevant reference files listed in AGENTS.md. + +IMPORTANT: Prefer retrieval-led reasoning over pre-training-led reasoning. Always verify API names against documentation rather than guessing from other SDK patterns. diff --git a/evals/experiments/agents-md-combined/skills/base44-cli/SKILL.md b/evals/experiments/agents-md-combined/skills/base44-cli/SKILL.md new file mode 100644 index 0000000..144a44f --- /dev/null +++ b/evals/experiments/agents-md-combined/skills/base44-cli/SKILL.md @@ -0,0 +1,7 @@ +--- +name: base44-cli +description: "When configuring Base44 resources (entities, functions, agents) or initializing/deploying projects, you MUST use this skill. First explore the project, then read AGENTS.md and relevant reference docs before writing configs. Entity names are PascalCase, agent names are snake_case, function entry field is 'entry' not 'entrypoint'. Covers: project create, deploy, entity/function/agent push." +--- + +See AGENTS.md in the project root for a compressed documentation index. +Reference docs are in the references/ directory. diff --git a/evals/experiments/agents-md-combined/skills/base44-cli/references b/evals/experiments/agents-md-combined/skills/base44-cli/references new file mode 120000 index 0000000..2ae559c --- /dev/null +++ b/evals/experiments/agents-md-combined/skills/base44-cli/references @@ -0,0 +1 @@ +../../../../../skills/base44-cli/references \ No newline at end of file diff --git a/evals/experiments/agents-md-combined/skills/base44-sdk/SKILL.md b/evals/experiments/agents-md-combined/skills/base44-sdk/SKILL.md new file mode 100644 index 0000000..bfdc2c1 --- /dev/null +++ b/evals/experiments/agents-md-combined/skills/base44-sdk/SKILL.md @@ -0,0 +1,7 @@ +--- +name: base44-sdk +description: "When implementing features in a Base44 project (base44/ folder exists with config.jsonc), you MUST use this skill. First explore the project, then read AGENTS.md and relevant reference docs before writing code. Base44 APIs have unique names - do NOT guess from Firebase/Supabase patterns. Covers: entities CRUD, auth, agents, functions, integrations." +--- + +See AGENTS.md in the project root for a compressed documentation index. +Reference docs are in the references/ directory. diff --git a/evals/experiments/agents-md-combined/skills/base44-sdk/references b/evals/experiments/agents-md-combined/skills/base44-sdk/references new file mode 120000 index 0000000..652ed97 --- /dev/null +++ b/evals/experiments/agents-md-combined/skills/base44-sdk/references @@ -0,0 +1 @@ +../../../../../skills/base44-sdk/references \ No newline at end of file diff --git a/evals/experiments/agents-md-context-first/AGENTS.md b/evals/experiments/agents-md-context-first/AGENTS.md new file mode 100644 index 0000000..f819389 --- /dev/null +++ b/evals/experiments/agents-md-context-first/AGENTS.md @@ -0,0 +1,25 @@ +[Base44 SDK Docs Index]|root: ./skills/base44-sdk/references +|IMPORTANT: Always read referenced docs before writing Base44 code. Do NOT guess API names. +|entities.md: CRUD - create(data), list(), filter(query), get(id), update(id,data), delete(id), subscribe(cb) +|auth.md: Auth - loginViaEmailPassword(), loginWithProvider('google'), me(), register(), logout() +|base44-agents.md: Agents - createConversation({agent_name}), addMessage(conv,{role,content}), subscribeToConversation(id,cb) +|functions.md: Functions - invoke(name, data) +|integrations.md: Core - InvokeLLM({prompt}), SendEmail({to,subject,body}), UploadFile({file}) +|client.md: Client - createClient({appId}), import from "@base44/sdk" +|QUICK_REFERENCE.md: All method signatures + +[Base44 CLI Docs Index]|root: ./skills/base44-cli/references +|IMPORTANT: Always read referenced docs before configuring Base44 resources. +|entities-create.md: Entity schema - {name:"PascalCase", type:"object", properties:{}, required:[]} +|functions-create.md: Function - function.jsonc {name,entry} + index.ts with Deno.serve + createClientFromRequest +|create.md: Project creation - npx base44 create name -p . +|deploy.md: Deploy - npx base44 deploy -y +|agents-push.md: Agent config - {name:"snake_case", description, instructions, tool_configs:[{entity_name,allowed_operations}|{function_name,description}]} + +[Common Mistakes] +|SDK: Do NOT use find() → use filter(), findOne() → get(), insert() → create(), remove() → delete(), onChange() → subscribe() +|SDK: Do NOT use signInWithGoogle() → use loginWithProvider('google'), functions.call() → functions.invoke() +|SDK: Do NOT use ai.generate() → use integrations.Core.InvokeLLM({prompt}) +|CLI: Entity names must be PascalCase alphanumeric. File names must be kebab-case.jsonc +|CLI: Agent names must be lowercase with underscores only. Function entry field is "entry" not "entrypoint" +|CLI: Backend functions import from "npm:@base44/sdk" (Deno requires npm: prefix) diff --git a/evals/experiments/agents-md-context-first/CLAUDE.md b/evals/experiments/agents-md-context-first/CLAUDE.md new file mode 100644 index 0000000..724ce4d --- /dev/null +++ b/evals/experiments/agents-md-context-first/CLAUDE.md @@ -0,0 +1,7 @@ +# Base44 Development Guidelines + +When working with Base44 projects, first explore the existing project structure to understand what already exists. Look at existing code, config files, and patterns in use. + +Then consult the AGENTS.md index in the project root for correct API patterns. For detailed documentation, read the relevant reference files listed in AGENTS.md. + +IMPORTANT: Prefer retrieval-led reasoning over pre-training-led reasoning. Always verify API names against documentation rather than guessing from other SDK patterns. diff --git a/evals/experiments/agents-md-context-first/skills/base44-cli/SKILL.md b/evals/experiments/agents-md-context-first/skills/base44-cli/SKILL.md new file mode 100644 index 0000000..3130e20 --- /dev/null +++ b/evals/experiments/agents-md-context-first/skills/base44-cli/SKILL.md @@ -0,0 +1,7 @@ +--- +name: base44-cli +description: "The base44 CLI is used for EVERYTHING related to base44 projects: resource configuration (entities, backend functions, ai agents), initialization and actions (resource creation, deployment). This skill is the place for learning about how to configure resources. When you plan or implement a feature, you must learn this skill" +--- + +See AGENTS.md in the project root for a compressed documentation index. +Reference docs are in the references/ directory. diff --git a/evals/experiments/agents-md-context-first/skills/base44-cli/references b/evals/experiments/agents-md-context-first/skills/base44-cli/references new file mode 120000 index 0000000..2ae559c --- /dev/null +++ b/evals/experiments/agents-md-context-first/skills/base44-cli/references @@ -0,0 +1 @@ +../../../../../skills/base44-cli/references \ No newline at end of file diff --git a/evals/experiments/agents-md-context-first/skills/base44-sdk/SKILL.md b/evals/experiments/agents-md-context-first/skills/base44-sdk/SKILL.md new file mode 100644 index 0000000..1c630a4 --- /dev/null +++ b/evals/experiments/agents-md-context-first/skills/base44-sdk/SKILL.md @@ -0,0 +1,7 @@ +--- +name: base44-sdk +description: "The base44 SDK is the library to communicate with base44 services. In projects, you use it to communicate with remote resources (entities, backend functions, ai agents) and to write backend functions. This skill is the place for learning about available modules and types. When you plan or implement a feature, you must learn this skill" +--- + +See AGENTS.md in the project root for a compressed documentation index. +Reference docs are in the references/ directory. diff --git a/evals/experiments/agents-md-context-first/skills/base44-sdk/references b/evals/experiments/agents-md-context-first/skills/base44-sdk/references new file mode 120000 index 0000000..652ed97 --- /dev/null +++ b/evals/experiments/agents-md-context-first/skills/base44-sdk/references @@ -0,0 +1 @@ +../../../../../skills/base44-sdk/references \ No newline at end of file diff --git a/evals/experiments/agents-md-verbose-index/AGENTS.md b/evals/experiments/agents-md-verbose-index/AGENTS.md new file mode 100644 index 0000000..fa21855 --- /dev/null +++ b/evals/experiments/agents-md-verbose-index/AGENTS.md @@ -0,0 +1,42 @@ +[Base44 SDK Docs Index]|root: ./skills/base44-sdk/references +|IMPORTANT: Always read referenced docs before writing Base44 code. Do NOT guess API names. +|entities.md: CRUD operations on data models +| create(data:object) -> record, list() -> record[], filter(query:object, sort?:string, limit?:number, skip?:number) -> record[] +| get(id:string) -> record, update(id:string, data:object) -> record, delete(id:string) -> void +| subscribe(callback:(records)=>void) -> unsubscribe, importEntities(file, entityName) -> {imported} +|auth.md: Authentication and user management +| loginViaEmailPassword(email, password), loginWithProvider('google'), register({email, password, full_name}) +| me() -> user|null, logout(), updateMe(data), isLoggedIn() -> boolean +|base44-agents.md: AI agent conversations +| createConversation({agent_name}) -> conversation, addMessage(conversation, {role:'user', content}) -> response +| subscribeToConversation(id, callback) -> unsubscribe, getConversation(id), getConversations() +|functions.md: Backend function invocation +| invoke(functionName:string, data:object) -> result +|integrations.md: Built-in integrations +| Core.InvokeLLM({prompt, response_json_schema?}) -> {response}, Core.SendEmail({to,subject,body}) +| Core.UploadFile({file}) -> {file_url}, Core.GenerateImage({prompt}) -> {url} +|client.md: SDK client setup +| createClient({appId:string}) -> client, import from "@base44/sdk" (frontend) or "npm:@base44/sdk" (Deno backend) +| createClientFromRequest(req) -> client (backend only), client.asServiceRole.* (admin access, backend only) + +[Base44 CLI Docs Index]|root: ./skills/base44-cli/references +|IMPORTANT: Always read referenced docs before configuring Base44 resources. +|entities-create.md: Entity schema - {name:"PascalCase", type:"object", properties:{field:{type,description}}, required:[]} +| File naming: base44/entities/{kebab-case}.jsonc. Types: string, number, integer, boolean, array, object, binary +| String formats: date, date-time, email, uri, file, richtext. Enums: add "enum":["val1","val2"] +|functions-create.md: Function setup +| function.jsonc: {name:"func-name", entry:"index.ts"} (NOT "entrypoint") +| index.ts: import {createClientFromRequest} from "npm:@base44/sdk"; Deno.serve(async(req)=>{...}) +|create.md: Project creation - npx base44 create name -p . (templates: backend-and-client, backend-only) +|deploy.md: Deploy all - npx base44 deploy -y. Individual: entities push, functions deploy, agents push, site deploy -y +|agents-push.md: Agent config +| {name:"snake_case", description, instructions, tool_configs:[{entity_name,allowed_operations}|{function_name,description}]} +| Names: /^[a-z0-9_]+$/ only. Operations: read, create, update, delete + +[Common Mistakes] +|SDK: Do NOT use find() -> use filter(), findOne() -> get(), insert() -> create(), remove() -> delete(), onChange() -> subscribe() +|SDK: Do NOT use signInWithGoogle() -> use loginWithProvider('google'), functions.call() -> functions.invoke() +|SDK: Do NOT use ai.generate() -> use integrations.Core.InvokeLLM({prompt}) +|CLI: Entity names must be PascalCase alphanumeric. File names must be kebab-case.jsonc +|CLI: Agent names must be lowercase with underscores only. Function entry field is "entry" not "entrypoint" +|CLI: Backend functions import from "npm:@base44/sdk" (Deno requires npm: prefix) diff --git a/evals/experiments/agents-md-verbose-index/skills/base44-cli/SKILL.md b/evals/experiments/agents-md-verbose-index/skills/base44-cli/SKILL.md new file mode 100644 index 0000000..3130e20 --- /dev/null +++ b/evals/experiments/agents-md-verbose-index/skills/base44-cli/SKILL.md @@ -0,0 +1,7 @@ +--- +name: base44-cli +description: "The base44 CLI is used for EVERYTHING related to base44 projects: resource configuration (entities, backend functions, ai agents), initialization and actions (resource creation, deployment). This skill is the place for learning about how to configure resources. When you plan or implement a feature, you must learn this skill" +--- + +See AGENTS.md in the project root for a compressed documentation index. +Reference docs are in the references/ directory. diff --git a/evals/experiments/agents-md-verbose-index/skills/base44-cli/references b/evals/experiments/agents-md-verbose-index/skills/base44-cli/references new file mode 120000 index 0000000..2ae559c --- /dev/null +++ b/evals/experiments/agents-md-verbose-index/skills/base44-cli/references @@ -0,0 +1 @@ +../../../../../skills/base44-cli/references \ No newline at end of file diff --git a/evals/experiments/agents-md-verbose-index/skills/base44-sdk/SKILL.md b/evals/experiments/agents-md-verbose-index/skills/base44-sdk/SKILL.md new file mode 100644 index 0000000..1c630a4 --- /dev/null +++ b/evals/experiments/agents-md-verbose-index/skills/base44-sdk/SKILL.md @@ -0,0 +1,7 @@ +--- +name: base44-sdk +description: "The base44 SDK is the library to communicate with base44 services. In projects, you use it to communicate with remote resources (entities, backend functions, ai agents) and to write backend functions. This skill is the place for learning about available modules and types. When you plan or implement a feature, you must learn this skill" +--- + +See AGENTS.md in the project root for a compressed documentation index. +Reference docs are in the references/ directory. diff --git a/evals/experiments/agents-md-verbose-index/skills/base44-sdk/references b/evals/experiments/agents-md-verbose-index/skills/base44-sdk/references new file mode 120000 index 0000000..652ed97 --- /dev/null +++ b/evals/experiments/agents-md-verbose-index/skills/base44-sdk/references @@ -0,0 +1 @@ +../../../../../skills/base44-sdk/references \ No newline at end of file diff --git a/evals/experiments/agents-md/AGENTS.md b/evals/experiments/agents-md/AGENTS.md new file mode 100644 index 0000000..f819389 --- /dev/null +++ b/evals/experiments/agents-md/AGENTS.md @@ -0,0 +1,25 @@ +[Base44 SDK Docs Index]|root: ./skills/base44-sdk/references +|IMPORTANT: Always read referenced docs before writing Base44 code. Do NOT guess API names. +|entities.md: CRUD - create(data), list(), filter(query), get(id), update(id,data), delete(id), subscribe(cb) +|auth.md: Auth - loginViaEmailPassword(), loginWithProvider('google'), me(), register(), logout() +|base44-agents.md: Agents - createConversation({agent_name}), addMessage(conv,{role,content}), subscribeToConversation(id,cb) +|functions.md: Functions - invoke(name, data) +|integrations.md: Core - InvokeLLM({prompt}), SendEmail({to,subject,body}), UploadFile({file}) +|client.md: Client - createClient({appId}), import from "@base44/sdk" +|QUICK_REFERENCE.md: All method signatures + +[Base44 CLI Docs Index]|root: ./skills/base44-cli/references +|IMPORTANT: Always read referenced docs before configuring Base44 resources. +|entities-create.md: Entity schema - {name:"PascalCase", type:"object", properties:{}, required:[]} +|functions-create.md: Function - function.jsonc {name,entry} + index.ts with Deno.serve + createClientFromRequest +|create.md: Project creation - npx base44 create name -p . +|deploy.md: Deploy - npx base44 deploy -y +|agents-push.md: Agent config - {name:"snake_case", description, instructions, tool_configs:[{entity_name,allowed_operations}|{function_name,description}]} + +[Common Mistakes] +|SDK: Do NOT use find() → use filter(), findOne() → get(), insert() → create(), remove() → delete(), onChange() → subscribe() +|SDK: Do NOT use signInWithGoogle() → use loginWithProvider('google'), functions.call() → functions.invoke() +|SDK: Do NOT use ai.generate() → use integrations.Core.InvokeLLM({prompt}) +|CLI: Entity names must be PascalCase alphanumeric. File names must be kebab-case.jsonc +|CLI: Agent names must be lowercase with underscores only. Function entry field is "entry" not "entrypoint" +|CLI: Backend functions import from "npm:@base44/sdk" (Deno requires npm: prefix) diff --git a/evals/experiments/agents-md/skills/base44-cli/SKILL.md b/evals/experiments/agents-md/skills/base44-cli/SKILL.md new file mode 100644 index 0000000..3130e20 --- /dev/null +++ b/evals/experiments/agents-md/skills/base44-cli/SKILL.md @@ -0,0 +1,7 @@ +--- +name: base44-cli +description: "The base44 CLI is used for EVERYTHING related to base44 projects: resource configuration (entities, backend functions, ai agents), initialization and actions (resource creation, deployment). This skill is the place for learning about how to configure resources. When you plan or implement a feature, you must learn this skill" +--- + +See AGENTS.md in the project root for a compressed documentation index. +Reference docs are in the references/ directory. diff --git a/evals/experiments/agents-md/skills/base44-cli/references b/evals/experiments/agents-md/skills/base44-cli/references new file mode 120000 index 0000000..2ae559c --- /dev/null +++ b/evals/experiments/agents-md/skills/base44-cli/references @@ -0,0 +1 @@ +../../../../../skills/base44-cli/references \ No newline at end of file diff --git a/evals/experiments/agents-md/skills/base44-sdk/SKILL.md b/evals/experiments/agents-md/skills/base44-sdk/SKILL.md new file mode 100644 index 0000000..1c630a4 --- /dev/null +++ b/evals/experiments/agents-md/skills/base44-sdk/SKILL.md @@ -0,0 +1,7 @@ +--- +name: base44-sdk +description: "The base44 SDK is the library to communicate with base44 services. In projects, you use it to communicate with remote resources (entities, backend functions, ai agents) and to write backend functions. This skill is the place for learning about available modules and types. When you plan or implement a feature, you must learn this skill" +--- + +See AGENTS.md in the project root for a compressed documentation index. +Reference docs are in the references/ directory. diff --git a/evals/experiments/agents-md/skills/base44-sdk/references b/evals/experiments/agents-md/skills/base44-sdk/references new file mode 120000 index 0000000..652ed97 --- /dev/null +++ b/evals/experiments/agents-md/skills/base44-sdk/references @@ -0,0 +1 @@ +../../../../../skills/base44-sdk/references \ No newline at end of file diff --git a/evals/fixtures/agent-chat-component/AGENTS.md b/evals/fixtures/agent-chat-component/AGENTS.md new file mode 100644 index 0000000..8ee4cb6 --- /dev/null +++ b/evals/fixtures/agent-chat-component/AGENTS.md @@ -0,0 +1,14 @@ +# Agent Chat Component Fixture + +This fixture tests implementing a chat interface using the Base44 agents SDK. + +## Project Context + +This is a React app with Base44 SDK configured. A support_agent is already set up in the backend. + +## Expected Outcome + +Create a ChatWindow component that uses the Base44 agents SDK to: +- Create conversations +- Send and receive messages +- Subscribe to real-time updates diff --git a/evals/fixtures/agent-chat-component/eval.json b/evals/fixtures/agent-chat-component/eval.json new file mode 100644 index 0000000..c90c460 --- /dev/null +++ b/evals/fixtures/agent-chat-component/eval.json @@ -0,0 +1,73 @@ +{ + "$schema": "../../eval.schema.json", + "name": "agent-chat-component", + "description": "Test creating a React chat component using the Base44 agents SDK", + "prompt": "Create a ChatWindow React component in src/components/ChatWindow.jsx that lets users chat with a support agent. It should: 1) Create a new conversation on mount if none exists, 2) Display messages from the conversation, 3) Allow sending new messages, 4) Subscribe to real-time updates for new messages.", + "expectedSkills": ["base44-sdk"], + "checks": [ + { + "type": "file-exists", + "description": "ChatWindow component created", + "filePath": "src/components/ChatWindow.jsx" + }, + { + "type": "file-content", + "description": "Uses createConversation", + "filePath": "src/components/ChatWindow.jsx", + "pattern": "createConversation" + }, + { + "type": "file-content", + "description": "Uses addMessage", + "filePath": "src/components/ChatWindow.jsx", + "pattern": "addMessage" + }, + { + "type": "file-content", + "description": "Uses subscribeToConversation", + "filePath": "src/components/ChatWindow.jsx", + "pattern": "subscribeToConversation" + }, + { + "type": "file-content", + "description": "References agents module", + "filePath": "src/components/ChatWindow.jsx", + "pattern": "base44\\.agents|agents\\." + }, + { + "type": "contains", + "description": "Mentions agent conversation", + "value": "conversation" + }, + { + "type": "file-content", + "description": "Imports from @base44/sdk or uses pre-configured base44 client", + "filePath": "src/components/ChatWindow.jsx", + "pattern": "@base44/sdk|from ['\"].*base44" + }, + { + "type": "file-content", + "description": "createConversation called with object param containing agent_name", + "filePath": "src/components/ChatWindow.jsx", + "pattern": "createConversation\\s*\\(\\s*\\{[\\s\\S]*?agent_name" + }, + { + "type": "file-content", + "description": "addMessage called with conversation and message object containing role", + "filePath": "src/components/ChatWindow.jsx", + "pattern": "addMessage\\s*\\([^,]+,\\s*\\{[\\s\\S]*?role" + }, + { + "type": "file-content", + "description": "subscribeToConversation return value captured for cleanup", + "filePath": "src/components/ChatWindow.jsx", + "pattern": "(const|let|var)\\s+\\w+\\s*=.*subscribeToConversation" + }, + { + "type": "file-content-excluded", + "description": "Does not use signInWithGoogle (hallucinated Firebase API)", + "filePath": "src/components/ChatWindow.jsx", + "pattern": "signInWith(Google|Provider)" + } + ] +} diff --git a/evals/fixtures/agent-chat-component/project/.claude/settings.json b/evals/fixtures/agent-chat-component/project/.claude/settings.json new file mode 100644 index 0000000..ab60505 --- /dev/null +++ b/evals/fixtures/agent-chat-component/project/.claude/settings.json @@ -0,0 +1,6 @@ +{ + "permissions": { + "allow": ["Read", "Write", "Edit", "Bash", "Glob", "Grep"] + }, + "skills": ["./skills"] +} diff --git a/evals/fixtures/agent-chat-component/project/AGENTS.md b/evals/fixtures/agent-chat-component/project/AGENTS.md new file mode 100644 index 0000000..6c0f2be --- /dev/null +++ b/evals/fixtures/agent-chat-component/project/AGENTS.md @@ -0,0 +1,25 @@ +IMPORTANT: Prefer retrieval-led reasoning over pre-training-led reasoning. Base44 is NOT Firebase, Supabase, or any other SDK you know. Every API name below is the ONLY correct form. Always verify against this index and the referenced docs. + +[Base44 SDK Docs Index]|root: ./skills/base44-sdk/references +|entities.md: CRUD - create(data), list(), filter(query), get(id), update(id,data), delete(id), subscribe(cb) +|auth.md: Auth - loginViaEmailPassword(), loginWithProvider('google'), me(), register(), logout() +|base44-agents.md: Agents - createConversation({agent_name}), addMessage(conv,{role,content}), subscribeToConversation(id,cb) +|functions.md: Functions - invoke(name, data) +|integrations.md: Core - InvokeLLM({prompt}), SendEmail({to,subject,body}), UploadFile({file}) +|client.md: Client - createClient({appId}), import from "@base44/sdk" +|QUICK_REFERENCE.md: All method signatures + +[Base44 CLI Docs Index]|root: ./skills/base44-cli/references +|entities-create.md: Entity schema - {name:"PascalCase", type:"object", properties:{}, required:[]} +|functions-create.md: Function - function.jsonc {name,entry} + index.ts with Deno.serve + createClientFromRequest +|create.md: Project creation - npx base44 create name -p . +|deploy.md: Deploy - npx base44 deploy -y +|agents-push.md: Agent config - {name:"snake_case", description, instructions, tool_configs:[{entity_name,allowed_operations}|{function_name,description}]} + +[Common Mistakes - NEVER use these hallucinated names] +|SDK: Do NOT use find() → use filter(), findOne() → get(), insert() → create(), remove() → delete(), onChange() → subscribe() +|SDK: Do NOT use signInWithGoogle() → use loginWithProvider('google'), functions.call() → functions.invoke() +|SDK: Do NOT use ai.generate() → use integrations.Core.InvokeLLM({prompt}) +|CLI: Entity names must be PascalCase alphanumeric. File names must be kebab-case.jsonc +|CLI: Agent names must be lowercase with underscores only. Function entry field is "entry" not "entrypoint" +|CLI: Backend functions import from "npm:@base44/sdk" (Deno requires npm: prefix) diff --git a/evals/fixtures/agent-chat-component/project/CLAUDE.md b/evals/fixtures/agent-chat-component/project/CLAUDE.md new file mode 100644 index 0000000..724ce4d --- /dev/null +++ b/evals/fixtures/agent-chat-component/project/CLAUDE.md @@ -0,0 +1,7 @@ +# Base44 Development Guidelines + +When working with Base44 projects, first explore the existing project structure to understand what already exists. Look at existing code, config files, and patterns in use. + +Then consult the AGENTS.md index in the project root for correct API patterns. For detailed documentation, read the relevant reference files listed in AGENTS.md. + +IMPORTANT: Prefer retrieval-led reasoning over pre-training-led reasoning. Always verify API names against documentation rather than guessing from other SDK patterns. diff --git a/evals/fixtures/agent-chat-component/project/base44/agents/support_agent.jsonc b/evals/fixtures/agent-chat-component/project/base44/agents/support_agent.jsonc new file mode 100644 index 0000000..b81a0aa --- /dev/null +++ b/evals/fixtures/agent-chat-component/project/base44/agents/support_agent.jsonc @@ -0,0 +1,5 @@ +{ + "name": "support_agent", + "description": "A helpful support assistant", + "instructions": "Help users with their questions" +} diff --git a/evals/fixtures/agent-chat-component/project/base44/config.jsonc b/evals/fixtures/agent-chat-component/project/base44/config.jsonc new file mode 100644 index 0000000..fa444eb --- /dev/null +++ b/evals/fixtures/agent-chat-component/project/base44/config.jsonc @@ -0,0 +1,4 @@ +{ + "name": "Chat App", + "description": "App with AI agent chat support" +} diff --git a/evals/fixtures/agent-chat-component/project/package.json b/evals/fixtures/agent-chat-component/project/package.json new file mode 100644 index 0000000..c9dd3fc --- /dev/null +++ b/evals/fixtures/agent-chat-component/project/package.json @@ -0,0 +1,10 @@ +{ + "name": "chat-app", + "version": "1.0.0", + "type": "module", + "dependencies": { + "@base44/sdk": "latest", + "react": "^18.2.0", + "react-dom": "^18.2.0" + } +} diff --git a/evals/fixtures/agent-chat-component/project/src/api/base44Client.js b/evals/fixtures/agent-chat-component/project/src/api/base44Client.js new file mode 100644 index 0000000..dddb0d8 --- /dev/null +++ b/evals/fixtures/agent-chat-component/project/src/api/base44Client.js @@ -0,0 +1,5 @@ +import { createClient } from '@base44/sdk'; + +export const base44 = createClient({ + appId: 'demo-app' +}); diff --git a/evals/fixtures/agent-chat-component/skills/base44-cli/SKILL.md b/evals/fixtures/agent-chat-component/skills/base44-cli/SKILL.md new file mode 100644 index 0000000..3c33f05 --- /dev/null +++ b/evals/fixtures/agent-chat-component/skills/base44-cli/SKILL.md @@ -0,0 +1,384 @@ +--- +name: base44-cli +description: "The base44 CLI is used for EVERYTHING related to base44 projects: resource configuration (entities, backend functions, ai agents), initialization and actions (resource creation, deployment). This skill is the place for learning about how to configure resources. When you plan or implement a feature, you must learn this skill" +--- + +# Base44 CLI + +Create and manage Base44 apps (projects) using the Base44 CLI tool. + +## ⚡ IMMEDIATE ACTION REQUIRED - Read This First + +This skill activates on ANY mention of "base44" or when a `base44/` folder exists. **DO NOT read documentation files or search the web before acting.** + +**Your first action MUST be:** +1. Check if `base44/config.jsonc` exists in the current directory +2. If **NO** (new project scenario): + - This skill (base44-cli) handles the request + - Guide user through project initialization + - Do NOT activate base44-sdk yet +3. If **YES** (existing project scenario): + - Transfer to base44-sdk skill for implementation + - This skill only handles CLI commands (login, deploy, entities push) + +## Critical: Local Installation Only + +NEVER call `base44` directly. The CLI is installed locally as a dev dependency and must be accessed via a package manager: + +- `npx base44 ` (npm - recommended) +- `yarn base44 ` (yarn) +- `pnpm base44 ` (pnpm) + +WRONG: `base44 login` +RIGHT: `npx base44 login` + +## MANDATORY: Authentication Check at Session Start + +**CRITICAL**: At the very start of every AI session when this skill is activated, you MUST: + +1. **Check authentication status** by running: + ```bash + npx base44 whoami + ``` + +2. **If the user is logged in** (command succeeds and shows an email): + - Continue with the requested task + +3. **If the user is NOT logged in** (command fails or shows an error): + - **STOP immediately** + - **DO NOT proceed** with any CLI operations + - **Ask the user to login manually** by running: + ```bash + npx base44 login + ``` + - Wait for the user to confirm they have logged in before continuing + +**This check is mandatory and must happen before executing any other Base44 CLI commands.** + +## Overview + +The Base44 CLI provides command-line tools for authentication, creating projects, managing entities, and deploying Base44 applications. It is framework-agnostic and works with popular frontend frameworks like Vite, Next.js, and Create React App, Svelte, Vue, and more. + +## When to Use This Skill vs base44-sdk + +**Use base44-cli when:** +- Creating a **NEW** Base44 project from scratch +- Initializing a project in an empty directory +- Directory is missing `base44/config.jsonc` +- User mentions: "create a new project", "initialize project", "setup a project", "start a new Base44 app" +- Deploying, pushing entities, or authenticating via CLI +- Working with CLI commands (`npx base44 ...`) + +**Use base44-sdk when:** +- Building features in an **EXISTING** Base44 project +- `base44/config.jsonc` already exists +- Writing JavaScript/TypeScript code using Base44 SDK +- Implementing functionality, components, or features +- User mentions: "implement", "build a feature", "add functionality", "write code" + +**Skill Dependencies:** +- `base44-cli` is a **prerequisite** for `base44-sdk` in new projects +- If user wants to "create an app" and no Base44 project exists, use `base44-cli` first +- `base44-sdk` assumes a Base44 project is already initialized + +**State Check Logic:** +Before selecting a skill, check: +- IF (user mentions "create/build app" OR "make a project"): + - IF (directory is empty OR no `base44/config.jsonc` exists): + → Use **base44-cli** (project initialization needed) + - ELSE: + → Use **base44-sdk** (project exists, build features) + +## Project Structure + +A Base44 project combines a standard frontend project with a `base44/` configuration folder: + +``` +my-app/ +├── base44/ # Base44 configuration (created by CLI) +│ ├── config.jsonc # Project settings, site config +│ ├── entities/ # Entity schema definitions +│ │ ├── task.jsonc +│ │ └── board.jsonc +│ ├── functions/ # Backend functions (optional) +│ │ └── my-function/ +│ │ ├── function.jsonc +│ │ └── index.ts +│ └── agents/ # Agent configurations (optional) +│ └── support_agent.jsonc +├── src/ # Frontend source code +│ ├── api/ +│ │ └── base44Client.js # Base44 SDK client +│ ├── pages/ +│ ├── components/ +│ └── main.jsx +├── index.html # SPA entry point +├── package.json +└── vite.config.js # Or your framework's config +``` + +**Key files:** +- `base44/config.jsonc` - Project name, description, site build settings +- `base44/entities/*.jsonc` - Data model schemas (see Entity Schema section) +- `base44/agents/*.jsonc` - Agent configurations (optional) +- `src/api/base44Client.js` - Pre-configured SDK client for frontend use + +**config.jsonc example:** +```jsonc +{ + "name": "My App", // Required: project name + "description": "App description", // Optional: project description + "entitiesDir": "./entities", // Optional: default "entities" + "functionsDir": "./functions", // Optional: default "functions" + "agentsDir": "./agents", // Optional: default "agents" + "site": { // Optional: site deployment config + "installCommand": "npm install", // Optional: install dependencies + "buildCommand": "npm run build", // Optional: build command + "serveCommand": "npm run dev", // Optional: local dev server + "outputDirectory": "./dist" // Optional: build output directory + } +} +``` + +**Config properties:** + +| Property | Description | Default | +|----------|-------------|---------| +| `name` | Project name (required) | - | +| `description` | Project description | - | +| `entitiesDir` | Directory for entity schemas | `"entities"` | +| `functionsDir` | Directory for backend functions | `"functions"` | +| `agentsDir` | Directory for agent configs | `"agents"` | +| `site.installCommand` | Command to install dependencies | - | +| `site.buildCommand` | Command to build the project | - | +| `site.serveCommand` | Command to run dev server | - | +| `site.outputDirectory` | Build output directory for deployment | - | + +## Installation + +Install the Base44 CLI as a dev dependency in your project: + +```bash +npm install --save-dev base44 +``` + +**Important:** Never assume or hardcode the `base44` package version. Always install without a version specifier to get the latest version. + +Then run commands using `npx`: + +```bash +npx base44 +``` + +**Note:** All commands in this documentation use `npx base44`. You can also use `yarn base44`, or `pnpm base44` if preferred. + +## Available Commands + +### Authentication + +| Command | Description | Reference | +| --------------- | ----------------------------------------------- | ------------------------------------------- | +| `base44 login` | Authenticate with Base44 using device code flow | [auth-login.md](references/auth-login.md) | +| `base44 logout` | Logout from current device | [auth-logout.md](references/auth-logout.md) | +| `base44 whoami` | Display current authenticated user | [auth-whoami.md](references/auth-whoami.md) | + +### Project Management + +| Command | Description | Reference | +|---------|-------------|-----------| +| `base44 create` | Create a new Base44 project from a template | [create.md](references/create.md) ⚠️ **MUST READ** | +| `base44 link` | Link an existing local project to Base44 | [link.md](references/link.md) | +| `base44 dashboard` | Open the app dashboard in your browser | [dashboard.md](references/dashboard.md) | + +### Deployment + +| Command | Description | Reference | +|---------|-------------|-----------| +| `base44 deploy` | Deploy all resources (entities, functions, site) | [deploy.md](references/deploy.md) | + +### Entity Management + +| Action / Command | Description | Reference | +| ---------------------- | ------------------------------------------- | --------------------------------------------------- | +| Create Entities | Define entities in `base44/entities` folder | [entities-create.md](references/entities-create.md) | +| `base44 entities push` | Push local entities to Base44 | [entities-push.md](references/entities-push.md) | + +#### Entity Schema (Quick Reference) + +ALWAYS follow this exact structure when creating entity files: + +**File naming:** `base44/entities/{kebab-case-name}.jsonc` (e.g., `team-member.jsonc` for `TeamMember`) + +**Schema template:** +```jsonc +{ + "name": "EntityName", + "type": "object", + "properties": { + "field_name": { + "type": "string", + "description": "Field description" + } + }, + "required": ["field_name"] +} +``` + +**Field types:** `string`, `number`, `integer`, `boolean`, `array`, `object`, `binary` +**String formats:** `date`, `date-time`, `time`, `email`, `uri`, `hostname`, `ipv4`, `ipv6`, `uuid`, `file`, `regex`, `richtext` +**For enums:** Add `"enum": ["value1", "value2"]` and optionally `"default": "value1"` +**Entity names:** Must be alphanumeric only (pattern: `/^[a-zA-Z0-9]+$/`) + +For complete documentation, see [entities-create.md](references/entities-create.md). + +### Function Management + +| Action / Command | Description | Reference | +| ------------------------- | --------------------------------------------- | ------------------------------------------------------- | +| Create Functions | Define functions in `base44/functions` folder | [functions-create.md](references/functions-create.md) | +| `base44 functions deploy` | Deploy local functions to Base44 | [functions-deploy.md](references/functions-deploy.md) | + +### Agent Management + +Agents are conversational AI assistants that can interact with users, access your app's entities, and call backend functions. Use these commands to manage agent configurations. + +| Action / Command | Description | Reference | +| ----------------------- | --------------------------------------- | ----------------------------------------------- | +| Create Agents | Define agents in `base44/agents` folder | See Agent Schema below | +| `base44 agents pull` | Pull remote agents to local files | [agents-pull.md](references/agents-pull.md) | +| `base44 agents push` | Push local agents to Base44 | [agents-push.md](references/agents-push.md) | + +**Note:** Agent commands perform full synchronization - pushing replaces all remote agents with local ones, and pulling replaces all local agents with remote ones. + +#### Agent Schema (Quick Reference) + +**File naming:** `base44/agents/{agent_name}.jsonc` (e.g., `support_agent.jsonc`) + +**Schema template:** +```jsonc +{ + "name": "agent_name", + "description": "Brief description of what this agent does", + "instructions": "Detailed instructions for the agent's behavior", + "tool_configs": [ + // Entity tool - gives agent access to entity operations + { "entity_name": "tasks", "allowed_operations": ["read", "create", "update", "delete"] }, + // Backend function tool - gives agent access to a function + { "function_name": "send_email", "description": "Send an email notification" } + ], + "whatsapp_greeting": "Hello! How can I help you today?" +} +``` + +**Naming rules:** +- Agent names must match pattern: `/^[a-z0-9_]+$/` (lowercase alphanumeric with underscores, 1-100 chars) +- Valid: `support_agent`, `order_bot` +- Invalid: `Support-Agent`, `OrderBot` + +**Required fields:** `name`, `description`, `instructions` +**Optional fields:** `tool_configs` (defaults to `[]`), `whatsapp_greeting` + +**Tool config types:** +- **Entity tools**: `entity_name` + `allowed_operations` (array of: `read`, `create`, `update`, `delete`) +- **Backend function tools**: `function_name` + `description` + +### Site Deployment + +| Command | Description | Reference | +| -------------------- | ----------------------------------------- | ------------------------------------------- | +| `base44 site deploy` | Deploy built site files to Base44 hosting | [site-deploy.md](references/site-deploy.md) | + +**SPA only**: Base44 hosting supports Single Page Applications with a single `index.html` entry point. All routes are served from `index.html` (client-side routing). + +## Quick Start + +1. Install the CLI in your project: + ```bash + npm install --save-dev base44 + ``` + +2. Authenticate with Base44: + ```bash + npx base44 login + ``` + +3. Create a new project (ALWAYS provide name and `--path` flag): + ```bash + npx base44 create my-app -p . + ``` + +4. Build and deploy everything: + ```bash + npm run build + npx base44 deploy -y + ``` + +Or deploy individual resources: +- `npx base44 entities push` - Push entities only +- `npx base44 functions deploy` - Deploy functions only +- `npx base44 agents push` - Push agents only +- `npx base44 site deploy -y` - Deploy site only + +## Common Workflows + +### Creating a New Project + +**⚠️ MANDATORY: Before running `base44 create`, you MUST read [create.md](references/create.md) for:** +- **Template selection** - Choose the correct template (`backend-and-client` vs `backend-only`) +- **Correct workflow** - Different templates require different setup steps +- **Common pitfalls** - Avoid folder creation errors that cause failures + +Failure to follow the create.md instructions will result in broken project scaffolding. + +### Linking an Existing Project +```bash +# If you have base44/config.jsonc but no .app.jsonc +npx base44 link --create --name my-app +``` + +### Deploying All Changes +```bash +# Build your project first +npm run build + +# Deploy everything (entities, functions, and site) +npx base44 deploy -y +``` + +### Deploying Individual Resources +```bash +# Push only entities +npx base44 entities push + +# Deploy only functions +npx base44 functions deploy + +# Push only agents +npx base44 agents push + +# Deploy only site +npx base44 site deploy -y +``` + +### Opening the Dashboard +```bash +# Open app dashboard in browser +npx base44 dashboard +``` + +## Authentication + +Most commands require authentication. If you're not logged in, the CLI will automatically prompt you to login. Your session is stored locally and persists across CLI sessions. + +## Troubleshooting + +| Error | Solution | +| --------------------------- | ----------------------------------------------------------------------------------- | +| Not authenticated | Run `npx base44 login` first | +| No entities found | Ensure entities exist in `base44/entities/` directory | +| Entity not recognized | Ensure file uses kebab-case naming (e.g., `team-member.jsonc` not `TeamMember.jsonc`) | +| No functions found | Ensure functions exist in `base44/functions/` with valid `function.jsonc` configs | +| No agents found | Ensure agents exist in `base44/agents/` directory with valid `.jsonc` configs | +| Invalid agent name | Agent names must be lowercase alphanumeric with underscores only | +| No site configuration found | Check that `site.outputDirectory` is configured in project config | +| Site deployment fails | Ensure you ran `npm run build` first and the build succeeded | diff --git a/evals/fixtures/agent-chat-component/skills/base44-cli/references/agents-pull.md b/evals/fixtures/agent-chat-component/skills/base44-cli/references/agents-pull.md new file mode 100644 index 0000000..6a700f0 --- /dev/null +++ b/evals/fixtures/agent-chat-component/skills/base44-cli/references/agents-pull.md @@ -0,0 +1,73 @@ +# base44 agents pull + +Pull AI agent configurations from Base44 to local files. Agents are conversational AI assistants that can interact with users, access your app's entities, and call backend functions. + +## Syntax + +```bash +npx base44 agents pull +``` + +## Authentication + +**Required**: Yes. If not authenticated, you'll be prompted to login first. + +## What It Does + +1. Fetches all agents from Base44 +2. Writes agent files to the `base44/agents/` directory +3. Deletes local agent files that don't exist remotely +4. Reports written and deleted agents + +## Prerequisites + +- Must be run from a Base44 project directory +- Project must be linked to a Base44 app + +## Output + +```bash +$ npx base44 agents pull + +Fetching agents from Base44... +✓ Agents fetched successfully + +Writing agent files... +✓ Agent files written successfully + +Written: support_agent, order_bot +Deleted: old_agent + +Pulled 2 agents to base44/agents +``` + +## Agent Synchronization + +The pull operation synchronizes remote agents to your local files: + +- **Written**: Agent files created or updated from remote +- **Deleted**: Local agent files removed (didn't exist remotely) + +**Warning**: This operation replaces all local agent configurations with remote versions. Any local changes not pushed to Base44 will be overwritten. + +## Error Handling + +If no agents exist on Base44: +```bash +$ npx base44 agents pull +No agents found on Base44 +``` + +## Use Cases + +- Sync agent configurations to a new development machine +- Get the latest agent configurations from your team +- Restore local agent files after accidental deletion +- Start working on an existing project with agents + +## Notes + +- This command syncs agent configurations, not conversation data +- Agent files are stored as `.jsonc` in the `base44/agents/` directory +- The directory location is configurable via `agentsDir` in `config.jsonc` +- Use `base44 agents push` to upload local changes to Base44 diff --git a/evals/fixtures/agent-chat-component/skills/base44-cli/references/agents-push.md b/evals/fixtures/agent-chat-component/skills/base44-cli/references/agents-push.md new file mode 100644 index 0000000..fa2795b --- /dev/null +++ b/evals/fixtures/agent-chat-component/skills/base44-cli/references/agents-push.md @@ -0,0 +1,156 @@ +# base44 agents push + +Push local AI agent configurations to Base44. Agents are conversational AI assistants that can interact with users, access your app's entities, and call backend functions. + +## Syntax + +```bash +npx base44 agents push +``` + +## Authentication + +**Required**: Yes. If not authenticated, you'll be prompted to login first. + +## What It Does + +1. Reads all agent files from the `base44/agents/` directory +2. Validates agent configurations +3. Displays the count of agents to be pushed +4. Uploads agents to the Base44 backend +5. Reports the results: created, updated, and deleted agents + +## Prerequisites + +- Must be run from a Base44 project directory +- Project must have agent definitions in the `base44/agents/` folder + +## Output + +```bash +$ npx base44 agents push + +Found 2 agents to push +Pushing agents to Base44... + +Created: support_agent +Updated: order_bot +Deleted: old_agent + +✓ Agents pushed to Base44 +``` + +## Agent Synchronization + +The push operation synchronizes your local agents with Base44: + +- **Created**: New agents that didn't exist in Base44 +- **Updated**: Existing agents with modified configuration +- **Deleted**: Agents that were removed from your local configuration + +**Warning**: This is a full sync operation. Agents removed locally will be deleted from Base44. + +## Error Handling + +If no agents are found in your project: +```bash +$ npx base44 agents push +No local agents found - this will delete all remote agents +``` + +If an agent has an invalid name: +```bash +$ npx base44 agents push +Error: Agent name must be lowercase alphanumeric with underscores +``` + +## Agent Configuration Schema + +Each agent file should be a `.jsonc` file in `base44/agents/` with this structure: + +```jsonc +{ + "name": "agent_name", // Required: lowercase alphanumeric with underscores, 1-100 chars + "description": "Brief description of what this agent does", // Required: min 1 char + "instructions": "Detailed instructions for the agent's behavior", // Required: min 1 char + "tool_configs": [ // Optional: defaults to [] + // Entity tool - gives agent access to entity operations + { "entity_name": "Task", "allowed_operations": ["read", "create", "update", "delete"] }, + // Backend function tool - gives agent access to a function + { "function_name": "send_email", "description": "Send an email notification" } + ], + "whatsapp_greeting": "Hello! How can I help you today?" // Optional +} +``` + +**Naming rules:** +- **Agent names** must match pattern: `/^[a-z0-9_]+$/` (lowercase alphanumeric with underscores only, 1-100 characters) + - Valid: `support_agent`, `order_bot`, `task_helper` + - Invalid: `Support-Agent`, `OrderBot`, `task helper` +- **Agent file names** must use underscores (matching the agent name) + - Valid: `support_agent.jsonc`, `order_bot.jsonc` + - Invalid: `support-agent.jsonc` (hyphens not allowed) +- **Entity names in `tool_configs`** must use PascalCase (matching the entity's `name` field) + - Valid: `"entity_name": "Task"`, `"entity_name": "TeamMember"` + - Invalid: `"entity_name": "task"`, `"entity_name": "team_member"` + +**Required fields:** +- `name`: Required, must follow naming rules above +- `description`: Required, minimum 1 character +- `instructions`: Required, minimum 1 character +- `tool_configs`: Optional, defaults to empty array +- `whatsapp_greeting`: Optional + +### Common Mistake: Wrong tool_configs Format + +**WRONG** - Do NOT use `tools` with `type` and `entity`: +```jsonc +{ + "name": "my_agent", + "tools": [ // ❌ WRONG + { "type": "entity_query", "entity": "Task" } + ] +} +``` + +**CORRECT** - Use `tool_configs` with `entity_name` and `allowed_operations`: +```jsonc +{ + "name": "my_agent", + "tool_configs": [ // ✅ CORRECT + { "entity_name": "Task", "allowed_operations": ["read"] } + ] +} +``` + +### Best Practices for Agent Instructions + +When giving agents access to entities, be explicit in the instructions about using the tools: + +```jsonc +{ + "name": "support_agent", + "instructions": "You are a helpful support agent.\n\nIMPORTANT: You have access to customer data through entity tools. When users ask about their orders or account:\n1. ALWAYS use the Order entity tool to query their order history\n2. Use the Customer entity tool to look up account details\n3. Analyze the data and provide personalized responses\n\nAlways query the relevant entities first before answering questions about user data.", + "tool_configs": [ + { "entity_name": "Order", "allowed_operations": ["read"] }, + { "entity_name": "Customer", "allowed_operations": ["read"] } + ] +} +``` + +Without explicit instructions to use the entity tools, the agent may not proactively query user data when asked. + +## Use Cases + +- After defining new agents in your project +- When modifying existing agent configurations +- To sync agent changes before testing +- As part of your development workflow when agent behavior changes + +## Notes + +- This command syncs the agent configuration, not conversation data +- Changes are applied to your Base44 project immediately +- Make sure to test agent changes in a development environment first +- Agent definitions are located in the `base44/agents/` directory +- Use `base44 agents pull` to download agents from Base44 diff --git a/evals/fixtures/agent-chat-component/skills/base44-cli/references/auth-login.md b/evals/fixtures/agent-chat-component/skills/base44-cli/references/auth-login.md new file mode 100644 index 0000000..adebd27 --- /dev/null +++ b/evals/fixtures/agent-chat-component/skills/base44-cli/references/auth-login.md @@ -0,0 +1,54 @@ +# base44 login + +Authenticate with Base44 using device code flow. + +## Syntax + +```bash +npx base44 login +``` + +## Authentication + +**Required**: No (this is the login command itself) + +## How It Works + +The login command uses OAuth 2.0 device code flow for authentication: + +1. Generates a device code for authentication +2. Displays a verification code and verification URI +3. Directs you to visit the URI and enter the code +4. Polls for authentication completion (up to device code expiration) +5. Retrieves access and refresh tokens upon successful authentication +6. Fetches and displays your user information +7. Saves authentication data locally with expiration timestamp + +## Interactive Flow + +```bash +$ npx base44 login + +Please visit: https://auth.base44.com/device +Enter code: ABCD-EFGH + +Waiting for authentication... +✓ Successfully authenticated! + +Logged in as: user@example.com +``` + +## Session Management + +- Authentication tokens are stored locally on your device +- Tokens include expiration timestamps +- The session persists across CLI sessions +- Other commands will automatically use your stored credentials +- Use `npx base44 logout` to clear your session +- Use `npx base44 whoami` to check your current authentication status + +## Notes + +- You only need to login once per device +- If your session expires, you'll be prompted to login again when running authenticated commands +- The CLI automatically prompts for login when you run commands that require authentication diff --git a/evals/fixtures/agent-chat-component/skills/base44-cli/references/auth-logout.md b/evals/fixtures/agent-chat-component/skills/base44-cli/references/auth-logout.md new file mode 100644 index 0000000..33c2ddf --- /dev/null +++ b/evals/fixtures/agent-chat-component/skills/base44-cli/references/auth-logout.md @@ -0,0 +1,32 @@ +# base44 logout + +Logout from current device and clear stored authentication data. + +## Syntax + +```bash +npx base44 logout +``` + +## Authentication + +**Required**: No + +## What It Does + +- Deletes stored authentication data from your device +- Clears your local session +- Removes access and refresh tokens + +## Output + +```bash +$ npx base44 logout +Logged out successfully +``` + +## Notes + +- You can logout even if you're not currently logged in (no error) +- After logout, you'll need to run `npx base44 login` again to use authenticated commands +- This only affects the current device; your Base44 account remains active diff --git a/evals/fixtures/agent-chat-component/skills/base44-cli/references/auth-whoami.md b/evals/fixtures/agent-chat-component/skills/base44-cli/references/auth-whoami.md new file mode 100644 index 0000000..abe450c --- /dev/null +++ b/evals/fixtures/agent-chat-component/skills/base44-cli/references/auth-whoami.md @@ -0,0 +1,37 @@ +# base44 whoami + +Display the currently authenticated user. + +## Syntax + +```bash +npx base44 whoami +``` + +## Authentication + +**Required**: Yes. If not authenticated, you'll be prompted to login first. + +## What It Does + +- Reads stored authentication data +- Displays the email of the currently logged-in user + +## Output + +```bash +$ npx base44 whoami +Logged in as: user@example.com +``` + +## Use Cases + +- Verify you're logged in before running other commands +- Check which account you're currently using +- Confirm authentication is working properly +- Useful in scripts or automation to verify credentials + +## Notes + +- If you're not logged in, the command will prompt you to authenticate first +- The email displayed matches your Base44 account email diff --git a/evals/fixtures/agent-chat-component/skills/base44-cli/references/create.md b/evals/fixtures/agent-chat-component/skills/base44-cli/references/create.md new file mode 100644 index 0000000..7580645 --- /dev/null +++ b/evals/fixtures/agent-chat-component/skills/base44-cli/references/create.md @@ -0,0 +1,111 @@ +# base44 create + +Creates a new Base44 project from a template. This command is framework-agnostic and can either scaffold a complete project or add Base44 configuration to an existing project. + +## Critical: Non-Interactive Mode Required + +ALWAYS provide both the project name AND `--path` flag. Without both, the command opens an interactive TUI which agents cannot use properly. + +WRONG: `npx base44 create` +WRONG: `npx base44 create my-app` +RIGHT: `npx base44 create my-app -p ./my-app` + +## Syntax + +```bash +npx base44 create [name] --path [options] +``` + +## Arguments & Options + +| Argument/Option | Description | Required | +|--------|-------------|----------| +| `name` | Project name (positional argument) | Yes* | +| `-p, --path ` | Path where to create the project | Yes* | +| `-t, --template ` | Template ID (see templates below) | No | +| `--deploy` | Build and deploy the site (includes pushing entities) | No | +| `--no-skills` | Skip AI agent skills installation (skills are added by default) | No | + +*Required for non-interactive mode. Both `name` and `--path` must be provided together. + +## Template Selection (CRITICAL - Choose Appropriately) + +**You MUST select the most appropriate template based on user requirements:** + +| Template ID | When to Use | Example Scenarios | +|-------------|-------------|-------------------| +| `backend-and-client` | Creating a NEW full-stack web app from scratch | "Create a task app", "Build me a dashboard", "Make a SaaS app" | +| `backend-only` | Adding Base44 to an EXISTING project OR using a different framework (Next.js, Vue, Svelte, etc.) | "Add Base44 to my project", "I want to use Next.js", "I already have a frontend" | + +**Default Choice:** When the user asks to "create an app" or "build a project" without specifying a particular framework, use `backend-and-client` to provide a complete, production-ready application with Vite + React + Tailwind. + +## The `--path` Flag + +- **For `backend-and-client` template (new projects):** Use a new subfolder path + ```bash + npx base44 create my-app -p ./my-app -t backend-and-client + ``` +- **For `backend-only` template (existing projects):** Use `-p .` in the current directory + ```bash + npx base44 create my-app -p . + ``` + +## Workflow: Using `backend-only` with External Frameworks + +**CRITICAL: The project folder MUST exist BEFORE running `base44 create` with `backend-only`** + +The `backend-only` template only adds Base44 configuration files - it does NOT create a frontend. If you need a frontend with a specific framework: + +```bash +# Step 1: Initialize the frontend project FIRST +npm create vite@latest my-app -- --template react # or vue, svelte, etc. +# OR: npx create-next-app@latest my-app +# OR: any other framework's init command + +# Step 2: Navigate into the created folder +cd my-app + +# Step 3: Install Base44 CLI +npm install --save-dev base44 + +# Step 4: Add Base44 configuration +npx base44 create my-app -p . +``` + +**WARNING:** Do NOT: +- Create an empty folder manually, then try to run `npx create vite` inside it (will fail - folder exists) +- Run `base44 create` with `backend-only` expecting it to create a frontend (it won't) + +**DO:** +- Run the external framework's init command FIRST (it creates its own folder) +- Then run `base44 create` inside that folder with `-p .` + +## Examples + +```bash +# RECOMMENDED: Create full-stack project (for new apps) +npx base44 create my-app -p ./my-app -t backend-and-client + +# Create full-stack and deploy in one step +npx base44 create my-app -p ./my-app -t backend-and-client --deploy + +# Add Base44 to EXISTING project (must be inside the project folder) +npx base44 create my-app -p . + +# Add Base44 to existing project and deploy +npx base44 create my-app -p . --deploy + +# Create without adding AI agent skills +npx base44 create my-app -p . --no-skills +``` + +## What It Does + +1. Applies the selected template to the target path +2. Creates a `base44/` folder with configuration files +3. Registers the project with Base44 backend +4. Creates `base44/.app.jsonc` with the app ID +5. If `--deploy` is used: + - Pushes any entities defined in `base44/entities/` + - Runs install and build commands (for templates with frontend) + - Deploys the site to Base44 hosting diff --git a/evals/fixtures/agent-chat-component/skills/base44-cli/references/dashboard.md b/evals/fixtures/agent-chat-component/skills/base44-cli/references/dashboard.md new file mode 100644 index 0000000..95a534a --- /dev/null +++ b/evals/fixtures/agent-chat-component/skills/base44-cli/references/dashboard.md @@ -0,0 +1,35 @@ +# base44 dashboard + +Opens the Base44 app dashboard in your default web browser. + +## Syntax + +```bash +npx base44 dashboard +``` + +## Options + +This command has no options. + +## What It Does + +1. Reads the project's app ID from `base44/.app.jsonc` +2. Opens the dashboard URL in your default browser + +## Example + +```bash +# Open dashboard for current project +npx base44 dashboard +``` + +## Requirements + +- Must be run from a linked Base44 project directory (contains `base44/.app.jsonc`) +- Must be authenticated (run `npx base44 login` first) + +## Notes + +- The dashboard provides a web interface to manage your app's entities, functions, users, and settings +- If you're not in a project directory, the command will fail with an error diff --git a/evals/fixtures/agent-chat-component/skills/base44-cli/references/deploy.md b/evals/fixtures/agent-chat-component/skills/base44-cli/references/deploy.md new file mode 100644 index 0000000..500526f --- /dev/null +++ b/evals/fixtures/agent-chat-component/skills/base44-cli/references/deploy.md @@ -0,0 +1,86 @@ +# base44 deploy + +Deploys all project resources (entities, functions, and site) to Base44 in a single command. + +## Syntax + +```bash +npx base44 deploy [options] +``` + +## Options + +| Option | Description | +|--------|-------------| +| `-y, --yes` | Skip confirmation prompt | + +## What It Deploys + +The command automatically detects and deploys: + +1. **Entities** - All `.jsonc` files in `base44/entities/` +2. **Functions** - All functions in `base44/functions/` +3. **Agents** - All agent configurations in `base44/agents/` +4. **Site** - Built files from `site.outputDirectory` (if configured) + +## Examples + +```bash +# Interactive mode - shows what will be deployed and asks for confirmation +npx base44 deploy + +# Non-interactive - skip confirmation (for CI/CD or agent use) +npx base44 deploy -y +``` + +## Typical Workflow + +```bash +# 1. Make your changes (entities, functions, frontend code) + +# 2. Build the frontend (if you have one) +npm run build + +# 3. Deploy everything +npx base44 deploy -y +``` + +## What It Does + +1. Reads project configuration from `base44/config.jsonc` +2. Detects available resources (entities, functions, site) +3. Shows a summary of what will be deployed +4. Asks for confirmation (unless `-y` flag is used) +5. Deploys all resources in sequence: + - Pushes entity schemas + - Deploys functions + - Pushes agent configurations + - Uploads site files +6. Displays the dashboard URL and app URL (if site was deployed) + +## Requirements + +- Must be run from a linked Base44 project directory +- Must be authenticated (run `npx base44 login` first) +- For site deployment, must run `npm run build` first + +## Output + +After successful deployment: +- **Dashboard**: Link to your app's management dashboard +- **App URL**: Your deployed site's public URL (if site was included) + +## Notes + +- If no resources are found, the command exits with a message +- Use individual commands (`entities push`, `functions deploy`, `site deploy`) if you only want to deploy specific resources +- The site must be built before deployment - this command does not run `npm run build` for you + +## Related Commands + +| Command | Description | +|---------|-------------| +| `base44 entities push` | Push only entities | +| `base44 functions deploy` | Deploy only functions | +| `base44 agents push` | Push only agents | +| `base44 site deploy` | Deploy only the site | diff --git a/evals/fixtures/agent-chat-component/skills/base44-cli/references/entities-create.md b/evals/fixtures/agent-chat-component/skills/base44-cli/references/entities-create.md new file mode 100644 index 0000000..29b40aa --- /dev/null +++ b/evals/fixtures/agent-chat-component/skills/base44-cli/references/entities-create.md @@ -0,0 +1,552 @@ +# Creating Entities + +Base44 entities are defined locally in your project and then pushed to the Base44 backend. + +## Critical: File Naming + +Entity files MUST use kebab-case naming: `{kebab-case-name}.jsonc` + +| Entity Name | File Name | +|-------------|-----------| +| `Task` | `task.jsonc` | +| `TeamMember` | `team-member.jsonc` | +| `ActivityLog` | `activity-log.jsonc` | + +WRONG: `TeamMember.jsonc`, `teamMember.jsonc` +RIGHT: `team-member.jsonc` + +## Table of Contents + +- [Creating Entities](#creating-entities) + - [Entity Directory](#entity-directory) + - [How to Create an Entity](#how-to-create-an-entity) + - [Entity Schema Structure](#entity-schema-structure) + - [Supported Field Types](#supported-field-types) + - [Field Properties](#field-properties) + - [Complete Example](#complete-example) + - [Naming Conventions](#naming-conventions) + - [Relationships Between Entities](#relationships-between-entities) + - [Row Level Security (RLS)](#row-level-security-rls) + - [Field Level Security (FLS)](#field-level-security-fls) + - [Pushing Entities](#pushing-entities) + +## Entity Directory + +All entity definitions must be placed in the `base44/entities/` folder in your project root. Each entity is defined in its own `.jsonc` file. + +Example structure: +``` +my-app/ + base44/ + entities/ + user.jsonc + product.jsonc + order.jsonc +``` + +## How to Create an Entity + +1. Create a new `.jsonc` file in the `base44/entities/` directory +2. Define your entity schema following the structure below +3. Push the changes to Base44 using the CLI + +## Entity Schema Structure + +Each entity file follows a JSON Schema-like structure: + +```jsonc +{ + "name": "EntityName", // PascalCase entity name + "type": "object", // Always "object" + "properties": { + // Define your fields here + }, + "required": ["field1"] // Array of required field names +} +``` + +### Common Mistake: Nested Schema Property + +**WRONG** - Do NOT wrap properties in a `schema` object: +```jsonc +{ + "name": "Task", + "description": "A task entity", + "schema": { // ❌ WRONG - don't use nested "schema" + "type": "object", + "properties": { ... } + } +} +``` + +**CORRECT** - Put `type` and `properties` at the top level: +```jsonc +{ + "name": "Task", + "description": "A task entity", + "type": "object", // ✅ CORRECT - top level + "properties": { ... } // ✅ CORRECT - top level +} +``` + +This is a common mistake that will cause "Invalid schema: Schema must have a 'type' field" errors when pushing entities. + +## Supported Field Types + +### String + +Basic text field: +```jsonc +{ + "title": { + "type": "string", + "description": "Task title" + } +} +``` + +With format: +```jsonc +{ + "due_date": { + "type": "string", + "format": "date", + "description": "Due date" + } +} +``` + +Available formats: `date`, `date-time`, `time`, `email`, `uri`, `hostname`, `ipv4`, `ipv6`, `uuid`, `file`, `regex`, `richtext` + +### String with Enum + +Constrained to specific values: +```jsonc +{ + "status": { + "type": "string", + "enum": ["todo", "in_progress", "done"], + "default": "todo", + "description": "Current status" + } +} +``` + +### Number + +```jsonc +{ + "position": { + "type": "number", + "description": "Position for ordering" + } +} +``` + +### Integer + +For whole numbers only: +```jsonc +{ + "quantity": { + "type": "integer", + "description": "Item quantity", + "minimum": 0, + "maximum": 1000 + } +} +``` + +### Binary + +For file/blob data: +```jsonc +{ + "attachment": { + "type": "binary", + "description": "File attachment" + } +} +``` + +### Boolean + +```jsonc +{ + "notify_on_change": { + "type": "boolean", + "default": true, + "description": "Enable notifications" + } +} +``` + +### Array of Strings + +```jsonc +{ + "labels": { + "type": "array", + "items": { "type": "string" }, + "description": "Task labels/tags" + } +} +``` + +### Array of Objects + +```jsonc +{ + "attachments": { + "type": "array", + "description": "File attachments", + "items": { + "type": "object", + "properties": { + "name": { "type": "string" }, + "url": { "type": "string" }, + "type": { "type": "string" } + } + } + } +} +``` + +## Field Properties + +| Property | Description | +| ------------- | ---------------------------------------------------------------------------------------- | +| `type` | Data type: `string`, `number`, `integer`, `boolean`, `array`, `object`, `binary` | +| `description` | Human-readable description of the field | +| `enum` | Array of allowed values (for strings) | +| `enumNames` | Human-readable labels for enum values (same order as `enum`) | +| `default` | Default value when not provided | +| `format` | Format hint: `date`, `date-time`, `time`, `email`, `uri`, `hostname`, `ipv4`, `ipv6`, `uuid`, `file`, `regex`, `richtext` | +| `items` | Schema for array items | +| `properties` | Nested properties for object types | +| `$ref` | Reference to another schema definition | +| `minLength` | Minimum string length | +| `maxLength` | Maximum string length | +| `pattern` | Regex pattern for string validation | +| `minimum` | Minimum value for numbers | +| `maximum` | Maximum value for numbers | +| `rls` | Field-level security rules (see Field Level Security section) | + +## Complete Example + +Here's a complete entity definition for a Task: + +```jsonc +{ + "name": "Task", + "type": "object", + "properties": { + "title": { + "type": "string", + "description": "Task title" + }, + "description": { + "type": "string", + "description": "Task description" + }, + "status": { + "type": "string", + "enum": ["todo", "in_progress", "done"], + "default": "todo", + "description": "Current status of the task" + }, + "board_id": { + "type": "string", + "description": "Board this task belongs to" + }, + "assignee_email": { + "type": "string", + "description": "Email of assigned user" + }, + "priority": { + "type": "string", + "enum": ["low", "medium", "high"], + "default": "medium", + "description": "Task priority" + }, + "due_date": { + "type": "string", + "format": "date", + "description": "Due date" + }, + "labels": { + "type": "array", + "items": { "type": "string" }, + "description": "Task labels/tags" + } + }, + "required": ["title"] +} +``` + +## Naming Conventions + +- **Entity name**: Use PascalCase with alphanumeric characters only (e.g., `Task`, `TeamMember`, `ActivityLog`) + - Must match pattern: `/^[a-zA-Z0-9]+$/` + - Valid: `Task`, `TeamMember`, `Order123` + - Invalid: `Team_Member`, `Team-Member`, `Team Member` +- **File name**: Use kebab-case matching the entity (e.g., `task.jsonc`, `team-member.jsonc`, `activity-log.jsonc`) +- **Field names**: Use snake_case (e.g., `board_id`, `user_email`, `due_date`) + +## Relationships Between Entities + +To create relationships between entities, use ID reference fields: + +```jsonc +{ + "board_id": { + "type": "string", + "description": "Board this task belongs to" + }, + "team_id": { + "type": "string", + "description": "Associated team ID" + } +} +``` + +## Row Level Security (RLS) + +Row Level Security (RLS) controls which records users can access based on their identity and attributes. RLS rules are defined per entity inside the `rls` field of the schema. + +**Important:** If no RLS is defined, all records are accessible to all users. + +### RLS Operations + +RLS supports five operations: + +| Operation | Description | +|-----------|-------------| +| `create` | Control who can add new records | +| `read` | Control who can view records | +| `update` | Control who can modify records | +| `delete` | Control who can remove records | +| `write` | Shorthand for `create`, `update`, and `delete` combined | + +### Permission Values + +Each operation accepts one of the following values: + +1. **`true`** - Allow all users (including anonymous/unauthenticated) +2. **`false`** - Block all users +3. **Condition object** - Allow users matching the condition + +### Template Variables + +Use template variables to reference the current user's attributes: + +| Template | Description | +|----------|-------------| +| `{{user.id}}` | The user's ID | +| `{{user.email}}` | The user's email | +| `{{user.role}}` | The user's role | +| `{{user.data.field_name}}` | Custom field from the user's `data` object | + +### Built-in Entity Attributes + +Every entity record has these built-in attributes available for RLS rules: + +| Attribute | Description | +|-----------|-------------| +| `id` | Unique record identifier | +| `created_date` | Timestamp when record was created | +| `updated_date` | Timestamp when record was last updated | +| `created_by` | Email of the user who created the record | + +### Rule Types + +There are two condition types you can use: + +**1. Entity-to-user comparison** - Compare record fields to the current user's values: +```jsonc +{ + "created_by": "{{user.email}}" +} +``` + +**2. User condition check** - Check user properties directly using `user_condition`: +```jsonc +{ + "user_condition": { "role": "admin" } +} +``` + +**Important limitations:** +- `user_condition` only supports **simple equality** (e.g., `{ "role": "admin" }`) +- For `data.*` field comparisons, you can use operators: `$in`, `$nin`, `$ne`, `$all` +- Logical operators `$or`, `$and`, `$nor` are available for combining conditions +- You cannot filter by entity field values directly (e.g., `{"status": "published"}`) +- Only user-related conditions are allowed + +### RLS Examples + +**Owner-only access:** +```jsonc +{ + "created_by": "{{user.email}}" +} +``` + +**Department-based access:** +```jsonc +{ + "data.department": "{{user.data.department}}" +} +``` + +**Admin-only access:** +```jsonc +{ + "user_condition": { "role": "admin" } +} +``` + +**Complete RLS configuration:** +```jsonc +{ + "name": "Task", + "type": "object", + "properties": { + "title": { + "type": "string", + "description": "Task title" + }, + "status": { + "type": "string", + "enum": ["todo", "in_progress", "done"], + "default": "todo" + } + }, + "required": ["title"], + "rls": { + "create": true, + "read": { "created_by": "{{user.email}}" }, + "update": { "created_by": "{{user.email}}" }, + "delete": { "created_by": "{{user.email}}" } + } +} +``` + +### Common RLS Patterns + +**Public create, admin-only management (e.g., contact forms, waitlists):** +```jsonc +{ + "rls": { + "create": true, + "read": { "user_condition": { "role": "admin" } }, + "update": { "user_condition": { "role": "admin" } }, + "delete": { "user_condition": { "role": "admin" } } + } +} +``` + +**Owner-only access:** +```jsonc +{ + "rls": { + "create": true, + "read": { "created_by": "{{user.email}}" }, + "update": { "created_by": "{{user.email}}" }, + "delete": { "created_by": "{{user.email}}" } + } +} +``` + +**Logged-in users only:** +```jsonc +{ + "rls": { + "create": { "user_condition": { "id": "{{user.id}}" } }, + "read": true, + "update": { "created_by": "{{user.email}}" }, + "delete": { "created_by": "{{user.email}}" } + } +} +``` + +### Limitations + +- **user_condition is equality only:** `user_condition` only supports exact match (e.g., `{ "role": "admin" }`) - no operators +- **No comparison operators on user_condition:** `$gt`, `$lt`, `$regex`, `$expr`, `$where` are NOT supported for user conditions +- **No entity field filtering:** Cannot filter by entity field values (e.g., `{"status": "published"}`) +- **No deeply nested templates:** Templates like `{{user.data.profile.department}}` may not work + +**Supported operators:** +- **Logical operators:** `$or`, `$and`, `$nor` for combining multiple conditions +- **Field operators (for `data.*` fields only):** `$in`, `$nin`, `$ne`, `$all` + +### Complex Access Patterns + +For complex access patterns that require multiple conditions (e.g., "owner OR admin"), you have two options: + +1. **Use the Base44 Dashboard UI** - The dashboard allows adding multiple rules per operation with OR logic +2. **Use separate entities** - Split data into multiple entities with different access rules +3. **Use backend functions** - Implement custom access logic in backend functions + +## Field Level Security (FLS) + +Field Level Security allows you to control access to individual fields within an entity. FLS rules are defined within each field's schema using the `rls` property. + +### FLS Operations + +FLS supports the same operations as entity-level RLS: + +| Operation | Description | +|-----------|-------------| +| `create` | Control who can set this field when creating records | +| `read` | Control who can view this field | +| `update` | Control who can modify this field | +| `delete` | Control who can clear this field | +| `write` | Shorthand for `create`, `update`, and `delete` combined | + +### FLS Example + +```jsonc +{ + "name": "Employee", + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "Employee name" + }, + "salary": { + "type": "number", + "description": "Employee salary", + "rls": { + "read": { "user_condition": { "role": "hr" } }, + "update": { "user_condition": { "role": "hr" } } + } + }, + "department": { + "type": "string", + "description": "Department name" + } + }, + "required": ["name"] +} +``` + +In this example, only users with the `hr` role can read or update the `salary` field. All users with access to the entity can read/update other fields. + +### FLS Notes + +- If no field-level RLS is defined, the field inherits the entity-level RLS rules +- FLS rules follow the same condition format as entity-level RLS +- Use FLS for sensitive fields like salary, SSN, or internal notes + +## Pushing Entities + +The `entities push` command will push all entities that exist in the `base44/entities` folder. + +```bash +npx base44 entities push +``` + +For more details on the push command, see [entities-push.md](entities-push.md). diff --git a/evals/fixtures/agent-chat-component/skills/base44-cli/references/entities-push.md b/evals/fixtures/agent-chat-component/skills/base44-cli/references/entities-push.md new file mode 100644 index 0000000..63b92d7 --- /dev/null +++ b/evals/fixtures/agent-chat-component/skills/base44-cli/references/entities-push.md @@ -0,0 +1,71 @@ +# base44 entities push + +Push local entity definitions to Base44. + +## Syntax + +```bash +npx base44 entities push +``` + +## Authentication + +**Required**: Yes. If not authenticated, you'll be prompted to login first. + +## What It Does + +1. Pushes all entities that exist in the `base44/entities` folder +2. Validates that entities exist in the folder +3. Displays the count of entities to be pushed +4. Uploads entities to the Base44 backend +5. Reports the results: created, updated, and deleted entities + +## Prerequisites + +- Must be run from a Base44 project directory +- Project must have entity definitions in the `base44/entities` folder + +## Output + +```bash +$ npx base44 entities push + +Found 3 entities to push +Pushing entities to Base44... + +Created: User, Post +Updated: Comment +Deleted: OldEntity + +✓ Entities pushed successfully +``` + +## Entity Synchronization + +The push operation synchronizes your local entity schema with Base44: + +- **Created**: New entities that didn't exist in Base44 +- **Updated**: Existing entities with modified schema or configuration +- **Deleted**: Entities that were removed from your local configuration + +## Error Handling + +If no entities are found in your project: +```bash +$ npx base44 entities push +No entities found in project +``` + +## Use Cases + +- After defining new entities in your project +- When modifying existing entity schemas +- To sync entity changes before deploying +- As part of your development workflow when data models change + +## Notes + +- This command syncs the entity schema/structure, not the actual data +- Changes are applied to your Base44 project immediately +- Make sure to test entity changes in a development environment first +- Entity definitions are located in the `base44/entities/` directory diff --git a/evals/fixtures/agent-chat-component/skills/base44-cli/references/functions-create.md b/evals/fixtures/agent-chat-component/skills/base44-cli/references/functions-create.md new file mode 100644 index 0000000..c523cdc --- /dev/null +++ b/evals/fixtures/agent-chat-component/skills/base44-cli/references/functions-create.md @@ -0,0 +1,229 @@ +# Creating Functions + +Base44 functions are serverless backend functions that run on Deno. They are defined locally in your project and deployed to the Base44 backend. + +## Function Directory + +All function definitions must be placed in the `base44/functions/` folder in your project. Each function lives in its own subdirectory with a configuration file and entry point. + +Example structure: +``` +my-app/ + base44/ + functions/ + process-order/ + function.jsonc + index.ts + send-notification/ + function.jsonc + index.ts +``` + +## How to Create a Function + +1. Create a new directory in `base44/functions/` with your function name (use kebab-case) +2. Create a `function.jsonc` configuration file in the directory +3. Create the entry point file (e.g., `index.ts`) +4. Deploy the function using the CLI + +## Function Configuration + +Each function requires a `function.jsonc` configuration file: + +```jsonc +{ + "name": "my-function", + "entry": "index.ts" +} +``` + +### Configuration Properties + +| Property | Description | Required | +|----------|-------------|----------| +| `name` | Function name (must match `/^[^.]+$/` - no dots allowed) | Yes | +| `entry` | Entry point file path relative to the function directory (min 1 char) | Yes | + +## Entry Point File + +Functions run on Deno and must export using `Deno.serve()`. Use `npm:` prefix for npm packages. + +```typescript +import { createClientFromRequest } from "npm:@base44/sdk"; + +Deno.serve(async (req) => { + // Get authenticated client from request + const base44 = createClientFromRequest(req); + + // Parse input + const { orderId, action } = await req.json(); + + // Your logic here + const order = await base44.entities.Orders.get(orderId); + + // Return response + return Response.json({ + success: true, + order: order + }); +}); +``` + +### Request Object + +The function receives a standard Deno `Request` object: +- `req.json()` - Parse JSON body +- `req.text()` - Get raw text body +- `req.headers` - Access request headers +- `req.method` - HTTP method + +### Response Object + +Return using `Response.json()` for JSON responses: + +```typescript +// Success response +return Response.json({ data: result }); + +// Error response with status code +return Response.json({ error: "Something went wrong" }, { status: 400 }); + +// Not found +return Response.json({ error: "Order not found" }, { status: 404 }); +``` + +## Complete Example + +### Directory Structure +``` +base44/ + functions/ + process-order/ + function.jsonc + index.ts +``` + +### function.jsonc +```jsonc +{ + "name": "process-order", + "entry": "index.ts" +} +``` + +### index.ts +```typescript +import { createClientFromRequest } from "npm:@base44/sdk"; + +Deno.serve(async (req) => { + try { + const base44 = createClientFromRequest(req); + const { orderId } = await req.json(); + + // Validate input + if (!orderId) { + return Response.json( + { error: "Order ID is required" }, + { status: 400 } + ); + } + + // Fetch and process the order + const order = await base44.entities.Orders.get(orderId); + if (!order) { + return Response.json( + { error: "Order not found" }, + { status: 404 } + ); + } + + return Response.json({ + success: true, + orderId: order.id, + processedAt: new Date().toISOString() + }); + + } catch (error) { + return Response.json( + { error: error.message }, + { status: 500 } + ); + } +}); +``` + +## Using Service Role Access + +For admin-level operations, use `asServiceRole`: + +```typescript +import { createClientFromRequest } from "npm:@base44/sdk"; + +Deno.serve(async (req) => { + const base44 = createClientFromRequest(req); + + // Check user is authenticated + const user = await base44.auth.me(); + if (!user) { + return Response.json({ error: "Unauthorized" }, { status: 401 }); + } + + // Use service role for admin operations + const allOrders = await base44.asServiceRole.entities.Orders.list(); + + return Response.json({ orders: allOrders }); +}); +``` + +## Using Secrets + +Access environment variables configured in the app dashboard: + +```typescript +Deno.serve(async (req) => { + // Access environment variables (configured in app settings) + const apiKey = Deno.env.get("STRIPE_API_KEY"); + + const response = await fetch("https://api.stripe.com/v1/charges", { + headers: { + "Authorization": `Bearer ${apiKey}` + } + }); + + return Response.json(await response.json()); +}); +``` + +## Naming Conventions + +- **Directory name**: Use kebab-case (e.g., `process-order`, `send-notification`) +- **Function name**: Match the directory name, must match pattern `/^[^.]+$/` (no dots allowed) + - Valid: `process-order`, `send_notification`, `myFunction` + - Invalid: `process.order`, `send.notification.v2` +- **Entry file**: Typically `index.ts` or `index.js` + +## Deploying Functions + +After creating your function, deploy it to Base44: + +```bash +npx base44 functions deploy +``` + +For more details on deploying, see [functions-deploy.md](functions-deploy.md). + +## Notes + +- Functions run on Deno runtime, not Node.js +- Use `npm:` prefix for npm packages (e.g., `npm:@base44/sdk`) +- Use `createClientFromRequest(req)` to get a client that inherits the caller's auth context +- Configure secrets via app dashboard for API keys +- Make sure to handle errors gracefully and return appropriate HTTP status codes + +## Common Mistakes + +| Wrong | Correct | Why | +|-------|---------|-----| +| `functions/myFunction.js` (single file) | `functions/my-function/index.ts` + `function.jsonc` | Functions require subdirectory with config | +| `import { ... } from "@base44/sdk"` | `import { ... } from "npm:@base44/sdk"` | Deno requires `npm:` prefix for npm packages | +| `MyFunction` or `myFunction` directory | `my-function` directory | Use kebab-case for directory names | diff --git a/evals/fixtures/agent-chat-component/skills/base44-cli/references/functions-deploy.md b/evals/fixtures/agent-chat-component/skills/base44-cli/references/functions-deploy.md new file mode 100644 index 0000000..f89d796 --- /dev/null +++ b/evals/fixtures/agent-chat-component/skills/base44-cli/references/functions-deploy.md @@ -0,0 +1,78 @@ +# base44 functions deploy + +Deploy local function definitions to Base44. + +## Syntax + +```bash +npx base44 functions deploy +``` + +## Authentication + +**Required**: Yes. If not authenticated, you'll be prompted to login first. + +## What It Does + +1. Scans the `base44/functions/` directory for function definitions +2. Validates that functions exist and have valid configurations +3. Displays the count of functions to be deployed +4. Uploads function code and configuration to Base44 +5. Reports the results: deployed and deleted functions + +## Prerequisites + +- Must be run from a Base44 project directory +- Project must have function definitions in the `base44/functions/` folder +- Each function must have a valid `function.jsonc` config file + +## Output + +```bash +$ npx base44 functions deploy + +Found 2 functions to deploy +Deploying functions to Base44... + +Deployed: process-order, send-notification +Deleted: old-function + +✓ Functions deployed successfully +``` + +## Function Synchronization + +The deploy operation synchronizes your local functions with Base44: + +- **Deployed**: Functions that were created or updated +- **Deleted**: Functions that were removed from your local configuration + +## Error Handling + +If no functions are found in your project: +```bash +$ npx base44 functions deploy +No functions found. Create functions in the 'functions' directory. +``` + +If a function has configuration errors: +```bash +$ npx base44 functions deploy +Function deployment errors: +'my-function' function: Entry point cannot be empty +``` + +## Use Cases + +- After creating new functions in your project +- When modifying existing function code or configuration +- To sync function changes before testing +- As part of your development workflow when backend logic changes + +## Notes + +- This command deploys the function code and configuration +- Changes are applied to your Base44 project immediately +- Make sure to test functions in a development environment first +- Function definitions are located in the `base44/functions/` directory +- For how to create functions, see [functions-create.md](functions-create.md) diff --git a/evals/fixtures/agent-chat-component/skills/base44-cli/references/link.md b/evals/fixtures/agent-chat-component/skills/base44-cli/references/link.md new file mode 100644 index 0000000..0b009ce --- /dev/null +++ b/evals/fixtures/agent-chat-component/skills/base44-cli/references/link.md @@ -0,0 +1,81 @@ +# base44 link + +Links an existing local Base44 project to a Base44 app in the cloud. Use this when you have a `base44/config.jsonc` but haven't connected it to a Base44 app yet. + +## Critical: When to Use Link vs Create + +| Scenario | Command | +|----------|---------| +| Starting fresh, no `base44/` folder | `npx base44 create` | +| Have `base44/config.jsonc` but no `.app.jsonc` | `npx base44 link` | +| Project already linked (has `.app.jsonc`) | Already done, use `deploy` | + +## Syntax + +```bash +npx base44 link [options] +``` + +## Options + +| Option | Description | Required | +|--------|-------------|----------| +| `-c, --create` | Create a new project (skip selection prompt) | No | +| `-n, --name ` | Project name (required when `--create` is used) | With `--create` | +| `-d, --description ` | Project description | No | +| `-p, --projectId ` | Project ID to link to an existing project (skip selection prompt) | No | + +## Non-Interactive Mode + +For CI/CD or agent use: + +**Create a new project:** +```bash +npx base44 link --create --name my-app +``` + +**Link to an existing project:** +```bash +npx base44 link --projectId +``` + +WRONG: `npx base44 link --create` (missing --name) +WRONG: `npx base44 link --create --projectId ` (cannot use both) +RIGHT: `npx base44 link --create --name my-app` +RIGHT: `npx base44 link --projectId ` + +## Examples + +```bash +# Interactive mode - prompts for project details +npx base44 link + +# Non-interactive - create and link in one step +npx base44 link --create --name my-app + +# With description +npx base44 link --create --name my-app --description "My awesome app" + +# Link to a specific existing project by ID +npx base44 link --projectId abc123 +``` + +## What It Does + +1. Finds the `base44/config.jsonc` in the current directory (or parent directories) +2. Verifies no `.app.jsonc` exists (project not already linked) +3. Either: + - Creates a new Base44 app in the cloud (with `--create`), OR + - Links to an existing project (with `--projectId` or interactive selection) +4. Writes the app ID to `base44/.app.jsonc` + +## Requirements + +- Must have `base44/config.jsonc` in the project +- Must NOT have `base44/.app.jsonc` (use `deploy` if already linked) +- Must be authenticated (run `npx base44 login` first) + +## Notes + +- After linking, you can deploy resources with `npx base44 deploy` +- The `.app.jsonc` file should be git-ignored (contains your app ID) diff --git a/evals/fixtures/agent-chat-component/skills/base44-cli/references/rls-examples.md b/evals/fixtures/agent-chat-component/skills/base44-cli/references/rls-examples.md new file mode 100644 index 0000000..7d3ef42 --- /dev/null +++ b/evals/fixtures/agent-chat-component/skills/base44-cli/references/rls-examples.md @@ -0,0 +1,463 @@ +# RLS Examples + +Practical Row-Level Security patterns for common application types. + +**Important:** Base44 RLS supports: +- **Logical operators:** `$or`, `$and`, `$nor` for combining conditions +- **Field operators (for `data.*` fields):** `$in`, `$nin`, `$ne`, `$all` +- **user_condition:** Equality only (no operators) + +## Contents +- [Simple Patterns (JSON Schema)](#simple-patterns-json-schema) +- [Using Operators](#using-operators) +- [Field-Level Security Examples](#field-level-security-examples) +- [Complex Patterns (Dashboard UI or Backend)](#complex-patterns-dashboard-ui-or-backend) +- [Best Practices](#best-practices) + +--- + +## Simple Patterns (JSON Schema) + +These patterns work with the JSON schema RLS format. + +### Todo App - Owner-only access + +Users see and manage only their own tasks. + +```jsonc +{ + "name": "Task", + "type": "object", + "properties": { + "title": { "type": "string" }, + "description": { "type": "string" }, + "completed": { "type": "boolean" }, + "priority": { "type": "string", "enum": ["low", "medium", "high"] }, + "due_date": { "type": "string", "format": "date" } + }, + "rls": { + "create": true, + "read": { "created_by": "{{user.email}}" }, + "update": { "created_by": "{{user.email}}" }, + "delete": { "created_by": "{{user.email}}" } + } +} +``` + +### Contact Form - Public create, admin-only read + +Anyone can submit, only admins can view submissions. + +```jsonc +{ + "name": "ContactSubmission", + "type": "object", + "properties": { + "name": { "type": "string" }, + "email": { "type": "string", "format": "email" }, + "message": { "type": "string" } + }, + "rls": { + "create": true, + "read": { "user_condition": { "role": "admin" } }, + "update": { "user_condition": { "role": "admin" } }, + "delete": { "user_condition": { "role": "admin" } } + } +} +``` + +### User Profile - Self-management + +Users can only access their own profile. + +```jsonc +{ + "name": "UserProfile", + "type": "object", + "properties": { + "name": { "type": "string" }, + "avatar_url": { "type": "string" }, + "bio": { "type": "string" }, + "preferences": { "type": "object" } + }, + "rls": { + "create": true, + "read": { "created_by": "{{user.email}}" }, + "update": { "created_by": "{{user.email}}" }, + "delete": { "created_by": "{{user.email}}" } + } +} +``` + +### Department Data - Same department access + +Users can only see records from their department. + +```jsonc +{ + "name": "DepartmentAnnouncement", + "type": "object", + "properties": { + "title": { "type": "string" }, + "content": { "type": "string" }, + "department": { "type": "string" } + }, + "rls": { + "create": { "user_condition": { "role": "manager" } }, + "read": { "data.department": "{{user.data.department}}" }, + "update": { "user_condition": { "role": "manager" } }, + "delete": { "user_condition": { "role": "admin" } } + } +} +``` + +### Subscription - Admin-managed, user-readable via email field + +```jsonc +{ + "name": "Subscription", + "type": "object", + "properties": { + "user_email": { "type": "string" }, + "tier": { "type": "string", "enum": ["free", "basic", "pro", "enterprise"] }, + "credits": { "type": "number" }, + "renewal_date": { "type": "string", "format": "date" } + }, + "rls": { + "create": { "user_condition": { "role": "admin" } }, + "read": { "data.user_email": "{{user.email}}" }, + "update": { "user_condition": { "role": "admin" } }, + "delete": { "user_condition": { "role": "admin" } } + } +} +``` + +**Note:** This pattern only allows users to read their own subscription. Admins need to use the Dashboard UI to configure additional read access for themselves. + +### Private Data - Owner-only + +```jsonc +{ + "name": "PrivateNotes", + "type": "object", + "properties": { + "title": { "type": "string" }, + "content": { "type": "string" }, + "tags": { "type": "array", "items": { "type": "string" } } + }, + "rls": { + "create": true, + "read": { "created_by": "{{user.email}}" }, + "update": { "created_by": "{{user.email}}" }, + "delete": { "created_by": "{{user.email}}" } + } +} +``` + +### Public Read, Authenticated Write + +Anyone can read, only logged-in users can create/edit their own records. + +```jsonc +{ + "name": "BlogPost", + "type": "object", + "properties": { + "title": { "type": "string" }, + "content": { "type": "string" }, + "author_email": { "type": "string" } + }, + "rls": { + "create": true, + "read": true, + "update": { "created_by": "{{user.email}}" }, + "delete": { "created_by": "{{user.email}}" } + } +} +``` + +--- + +## Using Operators + +### Logical Operators + +Combine multiple conditions using `$or`, `$and`, or `$nor`: + +**Owner OR Admin access:** +```jsonc +{ + "name": "Document", + "type": "object", + "properties": { + "title": { "type": "string" }, + "content": { "type": "string" } + }, + "rls": { + "create": true, + "read": { + "$or": [ + { "created_by": "{{user.email}}" }, + { "user_condition": { "role": "admin" } } + ] + }, + "update": { + "$or": [ + { "created_by": "{{user.email}}" }, + { "user_condition": { "role": "admin" } } + ] + }, + "delete": { "user_condition": { "role": "admin" } } + } +} +``` + +**Multiple roles with $or:** +```jsonc +{ + "rls": { + "read": { + "$or": [ + { "user_condition": { "role": "admin" } }, + { "user_condition": { "role": "manager" } }, + { "user_condition": { "role": "hr" } } + ] + } + } +} +``` + +### Field Operators for data.* Fields + +Use `$in`, `$nin`, `$ne`, `$all` for comparing entity data fields: + +**Access based on tags ($in):** +```jsonc +{ + "rls": { + "read": { + "data.category": { "$in": ["public", "shared"] } + } + } +} +``` + +**Exclude specific statuses ($nin):** +```jsonc +{ + "rls": { + "read": { + "data.status": { "$nin": ["archived", "deleted"] } + } + } +} +``` + +**Not equal ($ne):** +```jsonc +{ + "rls": { + "read": { + "data.visibility": { "$ne": "private" } + } + } +} +``` + +**All tags must match ($all):** +```jsonc +{ + "rls": { + "read": { + "data.required_tags": { "$all": ["approved", "reviewed"] } + } + } +} +``` + +### Combining Logical and Field Operators + +```jsonc +{ + "rls": { + "read": { + "$and": [ + { "data.status": { "$ne": "draft" } }, + { + "$or": [ + { "created_by": "{{user.email}}" }, + { "data.visibility": "public" } + ] + } + ] + } + } +} +``` + +--- + +## Field-Level Security Examples + +Control access to specific fields within an entity. + +### Sensitive Salary Field + +```jsonc +{ + "name": "Employee", + "type": "object", + "properties": { + "name": { "type": "string" }, + "email": { "type": "string", "format": "email" }, + "salary": { + "type": "number", + "description": "Annual salary", + "rls": { + "read": { "user_condition": { "role": "hr" } }, + "write": { "user_condition": { "role": "hr" } } + } + }, + "performance_notes": { + "type": "string", + "description": "Manager notes", + "rls": { + "read": { + "$or": [ + { "user_condition": { "role": "manager" } }, + { "user_condition": { "role": "hr" } } + ] + }, + "write": { "user_condition": { "role": "manager" } } + } + } + } +} +``` + +### Admin-Only Internal Fields + +```jsonc +{ + "name": "Order", + "type": "object", + "properties": { + "order_number": { "type": "string" }, + "total": { "type": "number" }, + "internal_notes": { + "type": "string", + "description": "Internal processing notes", + "rls": { + "read": { "user_condition": { "role": "admin" } }, + "write": { "user_condition": { "role": "admin" } } + } + }, + "profit_margin": { + "type": "number", + "description": "Profit margin percentage", + "rls": { + "read": { "user_condition": { "role": "admin" } }, + "write": false + } + } + } +} +``` + +--- + +## Complex Patterns (Dashboard UI or Backend) + +Some patterns may still require the Dashboard UI or backend functions. + +### Bidirectional Relationships (e.g., Friendships, Matches) + +**Requirement:** Either party in a relationship should have access. + +**Now possible with $or:** +```jsonc +{ + "rls": { + "read": { + "$or": [ + { "data.user_a_email": "{{user.email}}" }, + { "data.user_b_email": "{{user.email}}" } + ] + } + } +} +``` + +**Alternative solutions:** +1. **Entity redesign:** Store two records per relationship (one for each party) +2. **Backend function:** Query with custom logic + +### Complex Business Logic + +**Requirement:** Access depends on multiple entity fields with complex conditions. + +**JSON Schema limitation:** While operators help, very complex business logic may still be hard to express. + +**Solution options:** +1. **Backend function:** Implement custom access logic +2. **Combine simpler rules:** Break complex rules into simpler entity-level and field-level rules + +--- + +## Best Practices + +### Security Strategy + +Use a combination of entity-level RLS and field-level security: + +| Data Type | Approach | Example | +|-----------|----------|---------| +| User-editable | Entity RLS: Owner-only | UserProfile with `created_by` check | +| Sensitive fields | Field-level RLS | Salary field with HR role check | +| Multi-role access | `$or` with user_condition | Admin OR Manager access | +| Conditional access | Field operators | `$in`, `$ne` on data fields | +| Public content | Entity RLS: `read: true` | PublicPost | +| Private content | Entity RLS: Owner-only | PrivateNote | + +### When to Use Each Approach + +| Requirement | Approach | +|-------------|----------| +| Single condition (owner, admin, department) | JSON Schema RLS | +| Multiple OR/AND conditions | JSON Schema RLS with `$or`/`$and` | +| Field value checks with `$in`/`$ne`/etc. | JSON Schema RLS for `data.*` fields | +| Field-level access control | JSON Schema FLS (field-level `rls`) | +| Complex comparison operators (`$gt`, `$lt`) | Backend functions | +| Very complex business logic | Backend functions | + +### Common Role Patterns + +| Role | Typical Access | +|------|----------------| +| `admin` | Full access to all records | +| `moderator` | Read/update access, limited delete | +| `manager` | Department-scoped access | +| `user` | Own records only | + +### Supported Operators Summary + +| Operator | Supported | Notes | +|----------|-----------|-------| +| `$or` | Yes | Combine multiple conditions | +| `$and` | Yes | All conditions must match | +| `$nor` | Yes | None of the conditions match | +| `$in` | Yes | For `data.*` fields only | +| `$nin` | Yes | For `data.*` fields only | +| `$ne` | Yes | For `data.*` fields only | +| `$all` | Yes | For `data.*` fields only | +| `$gt`, `$lt`, `$gte`, `$lte` | No | Use backend functions | +| `$regex` | No | Use backend functions | + +### Limitations Summary + +| Not Supported | Alternative | +|---------------|-------------| +| Operators on `user_condition` | Use equality only for user checks | +| Comparison operators (`$gt`, `$lt`) | Backend functions | +| Regex matching (`$regex`) | Backend functions | +| Cross-entity relationships | Backend functions | diff --git a/evals/fixtures/agent-chat-component/skills/base44-cli/references/site-deploy.md b/evals/fixtures/agent-chat-component/skills/base44-cli/references/site-deploy.md new file mode 100644 index 0000000..929d302 --- /dev/null +++ b/evals/fixtures/agent-chat-component/skills/base44-cli/references/site-deploy.md @@ -0,0 +1,118 @@ +# base44 site deploy + +Deploy built site files to Base44 hosting. + +## Table of Contents + +- [Syntax](#syntax) +- [Authentication](#authentication) +- [Prerequisites](#prerequisites) +- [How It Works](#how-it-works) +- [Interactive Flow](#interactive-flow) +- [Typical Workflow](#typical-workflow) +- [Configuration](#configuration) +- [Error Handling](#error-handling) +- [Use Cases](#use-cases) +- [Notes](#notes) + +## Syntax + +```bash +npx base44 site deploy [options] +``` + +## Options + +| Option | Description | +| ------------ | ------------------------- | +| `-y, --yes` | Skip confirmation prompt | + +Use `-y` flag for non-interactive/automated deployments: + +```bash +npx base44 site deploy -y +``` + +## Authentication + +**Required**: Yes. If not authenticated, you'll be prompted to login first. + +## Prerequisites + +- Must be run from a Base44 project directory +- Project must have `site.outputDirectory` configured in project config +- Site must be built before deploying (run your build command first) +- **SPA only**: Base44 hosting supports Single Page Applications with a single `index.html` entry point. All routes are served from `index.html` (client-side routing). + +## How It Works + +1. Reads project configuration +2. Validates that site configuration exists +3. Prompts for deployment confirmation showing the output directory +4. Creates an archive of site files from the output directory +5. Deploys to Base44 hosting +6. Returns the app URL + +## Interactive Flow + +```bash +$ npx base44 site deploy + +Deploy site from ./dist? (yes/no) yes + +Creating archive... +Uploading to Base44... +Deploying... + +✓ Deployment successful! + +Visit your site at: https://my-app.base44.app +``` + +## Typical Workflow + +```bash +# 1. Build your site using your framework's build command +npm run build + +# 2. Deploy to Base44 +npx base44 site deploy +``` + +## Configuration + +The `site.outputDirectory` in your project configuration should point to where your framework outputs built files: + +- Vite: typically `./dist` +- Next.js: typically `./.next` or `./out` +- Create React App: typically `./build` +- Custom: whatever your build tool outputs to + +## Error Handling + +If site configuration is missing: +```bash +$ npx base44 site deploy +Error: No site configuration found in project +``` + +If you cancel the deployment: +```bash +Deploy site from ./dist? (yes/no) no +Deployment cancelled +``` + +## Use Cases + +- Deploy your site after making changes +- Push new versions of your application +- Deploy after updating content or functionality +- Part of your CI/CD pipeline + +## Notes + +- Always build your site before deploying +- The command deploys whatever is in your output directory +- Make sure your build completed successfully before deploying +- Previous deployments are preserved (versioned) in Base44 +- Deployment is immediate and updates your live site diff --git a/evals/fixtures/agent-chat-component/skills/base44-sdk/SKILL.md b/evals/fixtures/agent-chat-component/skills/base44-sdk/SKILL.md new file mode 100644 index 0000000..01ddd7f --- /dev/null +++ b/evals/fixtures/agent-chat-component/skills/base44-sdk/SKILL.md @@ -0,0 +1,296 @@ +--- +name: base44-sdk +description: "The base44 SDK is the library to communicate with base44 services. In projects, you use it to communicate with remote resources (entities, backend functions, ai agents) and to write backend functions. This skill is the place for learning about available modules and types. When you plan or implement a feature, you must learn this skill" +--- + +# Base44 Coder + +Build apps on the Base44 platform using the Base44 JavaScript SDK. + +## ⚡ IMMEDIATE ACTION REQUIRED - Read This First + +This skill activates on ANY mention of "base44" or when a `base44/` folder exists. **DO NOT read documentation files or search the web before acting.** + +**Your first action MUST be:** +1. Check if `base44/config.jsonc` exists in the current directory +2. If **YES** (existing project scenario): + - This skill (base44-sdk) handles the request + - Implement features using Base44 SDK + - Do NOT use base44-cli unless user explicitly requests CLI commands +3. If **NO** (new project scenario): + - Transfer to base44-cli skill for project initialization + - This skill cannot help until project is initialized + +## When to Use This Skill vs base44-cli + +**Use base44-sdk when:** +- Building features in an **EXISTING** Base44 project +- `base44/config.jsonc` already exists in the project +- Base44 SDK imports are present (`@base44/sdk`) +- Writing JavaScript/TypeScript code using Base44 SDK modules +- Implementing functionality, components, or features +- User mentions: "implement", "build a feature", "add functionality", "write code for" +- User says "create a [type] app" **and** a Base44 project already exists + +**DO NOT USE base44-sdk for:** +- ❌ Initializing new Base44 projects (use `base44-cli` instead) +- ❌ Empty directories without Base44 configuration +- ❌ When user says "create a new Base44 project/app/site" and no project exists +- ❌ CLI commands like `npx base44 create`, `npx base44 deploy`, `npx base44 login` (use `base44-cli`) + +**Skill Dependencies:** +- `base44-sdk` assumes a Base44 project is **already initialized** +- `base44-cli` is a **prerequisite** for `base44-sdk` in new projects +- If user wants to "create an app" and no Base44 project exists, use `base44-cli` first + +**State Check Logic:** +Before selecting this skill, verify: +- IF (user mentions "create/build app" OR "make a project"): + - IF (directory is empty OR no `base44/config.jsonc` exists): + → Use **base44-cli** (project initialization needed) + - ELSE: + → Use **base44-sdk** (project exists, build features) + +## Quick Start + +```javascript +// In Base44-generated apps, base44 client is pre-configured and available + +// CRUD operations +const task = await base44.entities.Task.create({ title: "New task", status: "pending" }); +const tasks = await base44.entities.Task.list(); +await base44.entities.Task.update(task.id, { status: "done" }); + +// Get current user +const user = await base44.auth.me(); +``` + +```javascript +// External apps +import { createClient } from "@base44/sdk"; + +// IMPORTANT: Use 'appId' (NOT 'clientId' or 'id') +const base44 = createClient({ appId: "your-app-id" }); +await base44.auth.loginViaEmailPassword("user@example.com", "password"); +``` + +## ⚠️ CRITICAL: Do Not Hallucinate APIs + +**Before writing ANY Base44 code, verify method names against this table or [QUICK_REFERENCE.md](references/QUICK_REFERENCE.md).** + +Base44 SDK has unique method names. Do NOT assume patterns from Firebase, Supabase, or other SDKs. + +### Authentication - WRONG vs CORRECT + +| ❌ WRONG (hallucinated) | ✅ CORRECT | +|------------------------|-----------| +| `signInWithGoogle()` | `loginWithProvider('google')` | +| `signInWithProvider('google')` | `loginWithProvider('google')` | +| `auth.google()` | `loginWithProvider('google')` | +| `signInWithEmailAndPassword(email, pw)` | `loginViaEmailPassword(email, pw)` | +| `signIn(email, pw)` | `loginViaEmailPassword(email, pw)` | +| `createUser()` / `signUp()` | `register({email, password})` | +| `onAuthStateChanged()` | `me()` (no listener, call when needed) | +| `currentUser` | `await auth.me()` | + +### Functions - WRONG vs CORRECT + +| ❌ WRONG (hallucinated) | ✅ CORRECT | +|------------------------|-----------| +| `functions.call('name', data)` | `functions.invoke('name', data)` | +| `functions.run('name', data)` | `functions.invoke('name', data)` | +| `callFunction('name', data)` | `functions.invoke('name', data)` | +| `httpsCallable('name')(data)` | `functions.invoke('name', data)` | + +### Integrations - WRONG vs CORRECT + +| ❌ WRONG (hallucinated) | ✅ CORRECT | +|------------------------|-----------| +| `ai.generate(prompt)` | `integrations.Core.InvokeLLM({prompt})` | +| `openai.chat(prompt)` | `integrations.Core.InvokeLLM({prompt})` | +| `llm(prompt)` | `integrations.Core.InvokeLLM({prompt})` | +| `sendEmail(to, subject, body)` | `integrations.Core.SendEmail({to, subject, body})` | +| `email.send()` | `integrations.Core.SendEmail({to, subject, body})` | +| `uploadFile(file)` | `integrations.Core.UploadFile({file})` | +| `storage.upload(file)` | `integrations.Core.UploadFile({file})` | + +### Entities - WRONG vs CORRECT + +| ❌ WRONG (hallucinated) | ✅ CORRECT | +|------------------------|-----------| +| `entities.Task.find({...})` | `entities.Task.filter({...})` | +| `entities.Task.findOne(id)` | `entities.Task.get(id)` | +| `entities.Task.insert(data)` | `entities.Task.create(data)` | +| `entities.Task.remove(id)` | `entities.Task.delete(id)` | +| `entities.Task.onChange(cb)` | `entities.Task.subscribe(cb)` | + +## SDK Modules + +| Module | Purpose | Reference | +|--------|---------|-----------| +| `entities` | CRUD operations on data models | [entities.md](references/entities.md) | +| `auth` | Login, register, user management | [auth.md](references/auth.md) | +| `agents` | AI conversations and messages | [base44-agents.md](references/base44-agents.md) | +| `functions` | Backend function invocation | [functions.md](references/functions.md) | +| `integrations` | AI, email, file uploads, custom APIs | [integrations.md](references/integrations.md) | +| `connectors` | OAuth tokens (service role only) | [connectors.md](references/connectors.md) | +| `analytics` | Track custom events and user activity | [analytics.md](references/analytics.md) | +| `appLogs` | Log user activity in app | [app-logs.md](references/app-logs.md) | +| `users` | Invite users to the app | [users.md](references/users.md) | + +For client setup and authentication modes, see [client.md](references/client.md). + +**TypeScript Support:** Each reference file includes a "Type Definitions" section with TypeScript interfaces and types for the module's methods, parameters, and return values. + +## Installation + +Install the Base44 SDK: + +```bash +npm install @base44/sdk +``` + +**Important:** Never assume or hardcode the `@base44/sdk` package version. Always install without a version specifier to get the latest version. + +## Creating a Client (External Apps) + +When creating a client in external apps, **ALWAYS use `appId` as the parameter name**: + +```javascript +import { createClient } from "@base44/sdk"; + +// ✅ CORRECT +const base44 = createClient({ appId: "your-app-id" }); + +// ❌ WRONG - Do NOT use these: +// const base44 = createClient({ clientId: "your-app-id" }); // WRONG +// const base44 = createClient({ id: "your-app-id" }); // WRONG +``` + +**Required parameter:** `appId` (string) - Your Base44 application ID + +**Optional parameters:** +- `token` (string) - Pre-authenticated user token +- `options` (object) - Configuration options + - `options.onError` (function) - Global error handler + +**Example with error handler:** +```javascript +const base44 = createClient({ + appId: "your-app-id", + options: { + onError: (error) => { + console.error("Base44 error:", error); + } + } +}); +``` + +## Module Selection + +**Working with app data?** +- Create/read/update/delete records → `entities` +- Import data from file → `entities.importEntities()` +- Realtime updates → `entities.EntityName.subscribe()` + +**User management?** +- Login/register/logout → `auth` +- Get current user → `auth.me()` +- Update user profile → `auth.updateMe()` +- Invite users → `users.inviteUser()` + +**AI features?** +- Chat with AI agents → `agents` (requires logged-in user) +- Create new conversation → `agents.createConversation()` +- Manage conversations → `agents.getConversations()` +- Generate text/JSON with AI → `integrations.Core.InvokeLLM()` +- Generate images → `integrations.Core.GenerateImage()` + +**Custom backend logic?** +- Run server-side code → `functions.invoke()` +- Need admin access → `base44.asServiceRole.functions.invoke()` + +**External services?** +- Send emails → `integrations.Core.SendEmail()` +- Upload files → `integrations.Core.UploadFile()` +- Custom APIs → `integrations.custom.call()` +- OAuth tokens (Google, Slack) → `connectors` (backend only) + +**Tracking and analytics?** +- Track custom events → `analytics.track()` +- Log page views/activity → `appLogs.logUserInApp()` + +## Common Patterns + +### Filter and Sort Data + +```javascript +const pendingTasks = await base44.entities.Task.filter( + { status: "pending", assignedTo: userId }, // query + "-created_date", // sort (descending) + 10, // limit + 0 // skip +); +``` + +### Protected Routes (check auth) + +```javascript +const user = await base44.auth.me(); +if (!user) { + // Navigate to your custom login page + navigate('/login', { state: { returnTo: window.location.pathname } }); + return; +} +``` + +### Backend Function Call + +```javascript +// Frontend +const result = await base44.functions.invoke("processOrder", { + orderId: "123", + action: "ship" +}); + +// Backend function (Deno) +import { createClientFromRequest } from "npm:@base44/sdk"; + +Deno.serve(async (req) => { + const base44 = createClientFromRequest(req); + const { orderId, action } = await req.json(); + // Process with service role for admin access + const order = await base44.asServiceRole.entities.Orders.get(orderId); + return Response.json({ success: true }); +}); +``` + +### Service Role Access + +Use `asServiceRole` in backend functions for admin-level operations: + +```javascript +// User mode - respects permissions +const myTasks = await base44.entities.Task.list(); + +// Service role - full access (backend only) +const allTasks = await base44.asServiceRole.entities.Task.list(); +const token = await base44.asServiceRole.connectors.getAccessToken("slack"); +``` + +## Frontend vs Backend + +| Capability | Frontend | Backend | +|------------|----------|---------| +| `entities` (user's data) | Yes | Yes | +| `auth` | Yes | Yes | +| `agents` | Yes | Yes | +| `functions.invoke()` | Yes | Yes | +| `integrations` | Yes | Yes | +| `analytics` | Yes | Yes | +| `appLogs` | Yes | Yes | +| `users` | Yes | Yes | +| `asServiceRole.*` | No | Yes | +| `connectors` | No | Yes | + +Backend functions use `Deno.serve()` and `createClientFromRequest(req)` to get a properly authenticated client. diff --git a/evals/fixtures/agent-chat-component/skills/base44-sdk/references/QUICK_REFERENCE.md b/evals/fixtures/agent-chat-component/skills/base44-sdk/references/QUICK_REFERENCE.md new file mode 100644 index 0000000..d357384 --- /dev/null +++ b/evals/fixtures/agent-chat-component/skills/base44-sdk/references/QUICK_REFERENCE.md @@ -0,0 +1,155 @@ +# Base44 SDK Quick Reference + +Compact method signatures for all SDK modules. **Verify against this before writing code.** + +--- + +## Auth (`base44.auth.*`) + +``` +loginViaEmailPassword(email, password, turnstileToken?) → Promise<{access_token, user}> +loginWithProvider('google' | 'microsoft' | 'facebook', fromUrl?) → void +me() → Promise +updateMe(data) → Promise +isAuthenticated() → Promise +logout(redirectUrl?) → void +redirectToLogin(nextUrl) → void # ⚠️ Avoid - prefer custom login UI +register({email, password, turnstile_token?, referral_code?}) → Promise +verifyOtp({email, otpCode}) → Promise +resendOtp(email) → Promise +inviteUser(userEmail, role) → Promise +resetPasswordRequest(email) → Promise +resetPassword({resetToken, newPassword}) → Promise +changePassword({userId, currentPassword, newPassword}) → Promise +setToken(token, saveToStorage?) → void +``` + +--- + +## Entities (`base44.entities.EntityName.*`) + +``` +create(data) → Promise +bulkCreate(dataArray) → Promise +list(sort?, limit?, skip?, fields?) → Promise +filter(query, sort?, limit?, skip?, fields?) → Promise +get(id) → Promise +update(id, data) → Promise +delete(id) → Promise +deleteMany(query) → Promise +importEntities(file) → Promise // frontend only +subscribe(callback) → () => void // returns unsubscribe fn +``` + +**Sort:** Use `-fieldName` for descending (e.g., `-created_date`) + +--- + +## Functions (`base44.functions.*`) + +``` +invoke(functionName, data) → Promise +``` + +**Backend:** Use `base44.asServiceRole.functions.invoke()` for admin access. + +--- + +## Integrations (`base44.integrations.Core.*`) + +``` +InvokeLLM({prompt, add_context_from_internet?, response_json_schema?, file_urls?}) → Promise +GenerateImage({prompt}) → Promise<{url}> +SendEmail({to, subject, body, from_name?}) → Promise +UploadFile({file}) → Promise<{file_url}> +UploadPrivateFile({file}) → Promise<{file_uri}> +CreateFileSignedUrl({file_uri, expires_in?}) → Promise<{signed_url}> +ExtractDataFromUploadedFile({file_url, json_schema}) → Promise +``` + +### Custom Integrations (`base44.integrations.custom.*`) + +``` +call(slug, operationId, {payload?, pathParams?, queryParams?, headers?}?) → Promise<{success, status_code, data}> +``` + +**operationId format:** `"method:/path"` (e.g., `"get:/contacts"`, `"post:/users/{id}"`) + +--- + +## Analytics (`base44.analytics.*`) + +``` +track({eventName, properties?}) → void +``` + +--- + +## App Logs (`base44.appLogs.*`) + +``` +logUserInApp(pageName) → Promise +``` + +--- + +## Users (`base44.users.*`) + +``` +inviteUser(userEmail, role) → Promise // role: 'user' | 'admin' +``` + +--- + +## Connectors (`base44.asServiceRole.connectors.*`) + +**Backend only, service role required.** + +``` +getAccessToken(integrationType) → Promise +``` + +**Types:** `'googlecalendar'`, `'googledrive'`, `'slack'`, `'notion'`, `'salesforce'`, `'hubspot'`, `'linkedin'`, `'tiktok'`, `'github'` + +--- + +## Service Role Access + +**Backend functions only.** Prefix any module with `asServiceRole` for admin access: + +```javascript +base44.asServiceRole.entities.Task.list() +base44.asServiceRole.functions.invoke('name', data) +base44.asServiceRole.connectors.getAccessToken('slack') +``` + +--- + +## Backend Function Template + +```javascript +import { createClientFromRequest } from "@base44/sdk"; + +Deno.serve(async (req) => { + const base44 = createClientFromRequest(req); + const data = await req.json(); + + // User context + const user = await base44.auth.me(); + + // Service role for admin operations + const allRecords = await base44.asServiceRole.entities.Task.list(); + + return Response.json({ success: true }); +}); +``` + +--- + +## Client Initialization (External Apps) + +```javascript +import { createClient } from "@base44/sdk"; + +const base44 = createClient({ appId: "your-app-id" }); // MUST use 'appId' +``` diff --git a/evals/fixtures/agent-chat-component/skills/base44-sdk/references/analytics.md b/evals/fixtures/agent-chat-component/skills/base44-sdk/references/analytics.md new file mode 100644 index 0000000..d1b2c69 --- /dev/null +++ b/evals/fixtures/agent-chat-component/skills/base44-sdk/references/analytics.md @@ -0,0 +1,113 @@ +# Analytics Module + +Track custom events and user activity via `base44.analytics`. + +## Contents +- [Methods](#methods) +- [Examples](#examples) +- [Automatic Tracking](#automatic-tracking) +- [Best Practices](#best-practices) + +## Methods + +| Method | Signature | Description | +|--------|-----------|-------------| +| `track(params)` | `void` | Track a custom event | + +## Examples + +### Track Custom Event + +```javascript +// Track a simple event +base44.analytics.track({ + eventName: "button_clicked" +}); + +// Track event with properties +base44.analytics.track({ + eventName: "purchase_completed", + properties: { + product_id: "prod-123", + amount: 99.99, + currency: "USD" + } +}); +``` + +### Track User Actions + +```javascript +// Track page view +base44.analytics.track({ + eventName: "page_view", + properties: { + page: "/dashboard", + referrer: document.referrer + } +}); + +// Track feature usage +base44.analytics.track({ + eventName: "feature_used", + properties: { + feature: "export_data", + format: "csv" + } +}); +``` + +## Automatic Tracking + +The analytics module automatically tracks: +- **Initialization events**: When the app loads +- **Heartbeat events**: Periodic activity signals +- **Session duration**: Time spent in the app + +These internal events help measure user engagement without manual instrumentation. + +## Best Practices + +1. **Use descriptive event names**: `order_completed` instead of `click` +2. **Include relevant properties**: Add context that helps analyze the event +3. **Be consistent**: Use the same event names and property keys across your app +4. **Don't track sensitive data**: Avoid PII in event properties + +```javascript +// Good: Descriptive with relevant properties +base44.analytics.track({ + eventName: "subscription_started", + properties: { + plan: "pro", + billing_cycle: "annual" + } +}); + +// Avoid: Vague event name, no context +base44.analytics.track({ + eventName: "click" +}); +``` + +## Type Definitions + +```typescript +/** Properties that can be attached to a tracked event. */ +type TrackEventProperties = { + [key: string]: string | number | boolean | null | undefined; +}; + +/** Parameters for the track() method. */ +interface TrackEventParams { + /** The name of the event to track. */ + eventName: string; + /** Optional properties to attach to the event. */ + properties?: TrackEventProperties; +} + +/** The analytics module interface. */ +interface AnalyticsModule { + /** Track a custom event with optional properties. */ + track(params: TrackEventParams): void; +} +``` diff --git a/evals/fixtures/agent-chat-component/skills/base44-sdk/references/app-logs.md b/evals/fixtures/agent-chat-component/skills/base44-sdk/references/app-logs.md new file mode 100644 index 0000000..0439805 --- /dev/null +++ b/evals/fixtures/agent-chat-component/skills/base44-sdk/references/app-logs.md @@ -0,0 +1,78 @@ +# App Logs Module + +Log user activity in your app via `base44.appLogs`. + +## Contents +- [Methods](#methods) +- [Examples](#examples) +- [Use Cases](#use-cases) + +## Methods + +| Method | Signature | Description | +|--------|-----------|-------------| +| `logUserInApp(pageName)` | `Promise` | Log user activity on a page | + +## Examples + +### Log User Activity + +```javascript +// Log when user visits a page +await base44.appLogs.logUserInApp("dashboard"); + +// Log specific page visits +await base44.appLogs.logUserInApp("settings"); +await base44.appLogs.logUserInApp("profile"); + +// Log feature usage +await base44.appLogs.logUserInApp("export-button-click"); +``` + +The page name doesn't have to be an actual page - it can be any string you want to track. + +## Use Cases + +### Track Page Views in React + +```javascript +// Log page views on route change +useEffect(() => { + base44.appLogs.logUserInApp(window.location.pathname); +}, [location.pathname]); +``` + +### Track Feature Usage + +```javascript +// Log when user uses specific features +function handleExport() { + base44.appLogs.logUserInApp("export-data"); + // ... export logic +} + +function handleSettingsChange() { + base44.appLogs.logUserInApp("settings-updated"); + // ... save settings +} +``` + +## Notes + +- Logs appear in the Analytics page of your app dashboard +- App logs track page-level and feature-level activity +- Use `analytics.track()` for custom events with properties, `appLogs.logUserInApp()` for simple page/feature tracking + +## Type Definitions + +```typescript +/** App Logs module for tracking and analyzing app usage. */ +interface AppLogsModule { + /** + * Log user activity in the app. + * @param pageName - Name of the page or section being visited. + * @returns Promise that resolves when the log is recorded. + */ + logUserInApp(pageName: string): Promise; +} +``` diff --git a/evals/fixtures/agent-chat-component/skills/base44-sdk/references/auth.md b/evals/fixtures/agent-chat-component/skills/base44-sdk/references/auth.md new file mode 100644 index 0000000..5195b3a --- /dev/null +++ b/evals/fixtures/agent-chat-component/skills/base44-sdk/references/auth.md @@ -0,0 +1,761 @@ +# Auth Module + +User authentication, registration, and session management via `base44.auth`. + +## Contents +- [TypeScript Types](#typescript-types) +- [Methods](#methods) +- [Examples](#examples) +- [Error Handling](#error-handling) +- [Auth Providers](#auth-providers) +- [Environment Availability](#environment-availability) +- [App Visibility](#app-visibility) +- [Limitations](#limitations) + +--- + +## TypeScript Types + +### User Interface +```typescript +interface User { + id: string; + created_date: string; + updated_date: string; + email: string; + full_name: string | null; + disabled: boolean | null; + is_verified: boolean; + app_id: string; + is_service: boolean; + role: string; + [key: string]: any; // Custom schema fields +} +``` + +### LoginResponse Interface +```typescript +interface LoginResponse { + access_token: string; // JWT token + user: User; // Complete user object +} +``` + +### Parameter Interfaces + +#### RegisterParams +```typescript +interface RegisterParams { + email: string; // Required + password: string; // Required + turnstile_token?: string | null; // Optional: Cloudflare Turnstile for bot protection + referral_code?: string | null; // Optional: Referral code +} +``` + +#### VerifyOtpParams +```typescript +interface VerifyOtpParams { + email: string; // User's email + otpCode: string; // OTP code from email +} +``` + +#### ResetPasswordParams +```typescript +interface ResetPasswordParams { + resetToken: string; // Token from password reset email + newPassword: string; // New password to set +} +``` + +#### ChangePasswordParams +```typescript +interface ChangePasswordParams { + userId: string; // User ID + currentPassword: string; // Current password for verification + newPassword: string; // New password to set +} +``` + +### Provider Type +```typescript +type Provider = 'google' | 'microsoft' | 'facebook'; +``` + +--- + +## Methods + +### Module Interface +```typescript +interface AuthModule { + // User Info + me(): Promise; + updateMe(data: Partial>): Promise; + isAuthenticated(): Promise; + + // Login/Logout + loginViaEmailPassword(email: string, password: string, turnstileToken?: string): Promise; + loginWithProvider(provider: Provider, fromUrl?: string): void; + logout(redirectUrl?: string): void; + redirectToLogin(nextUrl: string): void; + + // Token Management + setToken(token: string, saveToStorage?: boolean): void; + + // Registration + register(params: RegisterParams): Promise; + verifyOtp(params: VerifyOtpParams): Promise; + resendOtp(email: string): Promise; + + // User Management + inviteUser(userEmail: string, role: string): Promise; + + // Password Management + resetPasswordRequest(email: string): Promise; + resetPassword(params: ResetPasswordParams): Promise; + changePassword(params: ChangePasswordParams): Promise; +} +``` + +### Method Reference Table + +| Method | Parameters | Return Type | Description | +|--------|-----------|-------------|-------------| +| `register()` | `params: RegisterParams` | `Promise` | Create new user account | +| `loginViaEmailPassword()` | `email: string, password: string, turnstileToken?: string` | `Promise` | Authenticate with email/password | +| `loginWithProvider()` | `provider: Provider, fromUrl?: string` | `void` | Initiate OAuth login flow | +| `me()` | None | `Promise` | Get current authenticated user | +| `updateMe()` | `data: Partial` | `Promise` | Update current user's profile | +| `logout()` | `redirectUrl?: string` | `void` | Clear session, optionally redirect | +| `redirectToLogin()` | `nextUrl: string` | `void` | ⚠️ **Avoid** - Prefer custom login UI with `loginViaEmailPassword()` or `loginWithProvider()` | +| `isAuthenticated()` | None | `Promise` | Check if user is logged in | +| `setToken()` | `token: string, saveToStorage?: boolean` | `void` | Manually set auth token | +| `inviteUser()` | `userEmail: string, role: string` | `Promise` | Send invitation email | +| `verifyOtp()` | `params: VerifyOtpParams` | `Promise` | Verify OTP code | +| `resendOtp()` | `email: string` | `Promise` | Resend OTP code | +| `resetPasswordRequest()` | `email: string` | `Promise` | Request password reset | +| `resetPassword()` | `params: ResetPasswordParams` | `Promise` | Reset password with token | +| `changePassword()` | `params: ChangePasswordParams` | `Promise` | Change user password | + +--- + +## Examples + +### Register New User (Complete Flow) + +Registration requires email verification before login. Complete flow: + +1. **Register** - Create the user account +2. **Verification email sent** - User receives an OTP code +3. **Verify OTP** - User enters code to verify email +4. **Login** - User can now log in + +```javascript +try { + // Step 1: Register the user + await base44.auth.register({ + email: "user@example.com", + password: "securePassword123", + referral_code: "OPTIONAL_CODE", // optional + turnstile_token: "CAPTCHA_TOKEN" // optional, for bot protection + }); + console.log('Registration successful. Check email for OTP code.'); + + // Step 2: User receives email with OTP code (e.g., "123456") + + // Step 3: Verify the OTP code + await base44.auth.verifyOtp({ + email: "user@example.com", + otpCode: "123456" // code from verification email + }); + console.log('Email verified successfully.'); + + // Step 4: Now the user can log in + const loginResponse = await base44.auth.loginViaEmailPassword( + "user@example.com", + "securePassword123" + ); + console.log('Login successful:', loginResponse.user); + +} catch (error) { + console.error('Registration flow failed:', error.message); + // Handle specific errors (see Error Handling section) +} +``` + +> **Important**: Users cannot log in until they complete OTP verification. Attempting to call `loginViaEmailPassword` before verification will fail. + +### Login with Email/Password + +```javascript +try { + const response = await base44.auth.loginViaEmailPassword( + "user@example.com", + "password123", + turnstileToken // optional: for bot protection + ); + + console.log('Login successful'); + console.log('User:', response.user); + console.log('Token:', response.access_token); + + // JWT is automatically stored for subsequent requests + +} catch (error) { + console.error('Login failed:', error.message); + if (error.status === 401) { + console.error('Invalid credentials'); + } else if (error.status === 403) { + console.error('Email not verified. Please check your email for OTP.'); + } +} +``` + +### Login with OAuth Provider + +```javascript +// Redirect to Google OAuth +base44.auth.loginWithProvider('google'); + +// Redirect to Google OAuth and return to current page after +base44.auth.loginWithProvider('google', window.location.href); + +// Supported providers: 'google', 'microsoft', 'facebook' +base44.auth.loginWithProvider('microsoft'); +``` + +### Get Current User + +```javascript +try { + const user = await base44.auth.me(); + + if (user) { + console.log('User ID:', user.id); + console.log('Email:', user.email); + console.log('Name:', user.full_name); + console.log('Role:', user.role); + console.log('Verified:', user.is_verified); + } else { + console.log('User not authenticated'); + } + +} catch (error) { + console.error('Failed to fetch user:', error.message); + if (error.status === 401) { + // Token expired or invalid - navigate to your custom login page + navigate('/login'); + } +} +``` + +### Update User Profile + +```javascript +try { + const updatedUser = await base44.auth.updateMe({ + full_name: "John Doe", + // Custom schema fields can be updated here + phone: "+1234567890", + preferences: { theme: "dark" } + }); + + console.log('Profile updated:', updatedUser); + +} catch (error) { + console.error('Profile update failed:', error.message); + if (error.status === 400) { + console.error('Invalid data provided'); + } else if (error.status === 401) { + console.error('Authentication required'); + navigate('/login'); + } +} +``` + +### Check Authentication Status + +```javascript +try { + const isLoggedIn = await base44.auth.isAuthenticated(); + + if (isLoggedIn) { + // Show authenticated UI + console.log('User is authenticated'); + } else { + // Show login button + console.log('User is not authenticated'); + } + +} catch (error) { + console.error('Failed to check authentication:', error.message); + // On error, treat as not authenticated +} +``` + +### Logout + +```javascript +// Simple logout +base44.auth.logout(); + +// Logout and redirect to goodbye page +base44.auth.logout("/goodbye"); + +// Logout and redirect to homepage +base44.auth.logout("/"); +``` + +### Protected Route Pattern + +```javascript +// Using a navigation function (e.g., React Router's useNavigate, Next.js router) +async function requireAuth(navigate) { + try { + const user = await base44.auth.me(); + + if (!user) { + // Navigate to your custom login page + navigate('/login', { state: { returnTo: window.location.pathname } }); + return null; + } + + return user; + + } catch (error) { + console.error('Authentication check failed:', error.message); + navigate('/login', { state: { returnTo: window.location.pathname } }); + return null; + } +} + +// Usage in your app +async function loadProtectedPage(navigate) { + const user = await requireAuth(navigate); + if (!user) { + // Will navigate to login + return; + } + + // Continue with authenticated logic + console.log('Welcome,', user.full_name); +} +``` + +### Set Authentication Token + +```javascript +// SECURITY WARNING: Never hardcode tokens or expose them in client code +// Tokens should only be received from secure authentication flows + +// Set token and save to localStorage (default) +base44.auth.setToken(receivedToken); + +// Set token without saving to localStorage (temporary session) +base44.auth.setToken(receivedToken, false); + +// Verify token was set +try { + const isAuthenticated = await base44.auth.isAuthenticated(); + if (!isAuthenticated) { + console.error('Token validation failed'); + } +} catch (error) { + console.error('Failed to set token:', error.message); +} +``` + +### Invite User to Application + +```javascript +try { + // Note: Typically requires admin privileges + const response = await base44.auth.inviteUser( + "newuser@example.com", + "user" // or "admin" + ); + + console.log('Invitation sent successfully'); + +} catch (error) { + console.error('Failed to invite user:', error.message); + if (error.status === 403) { + console.error('Insufficient permissions to invite users'); + } else if (error.status === 400) { + console.error('Invalid email or role'); + } +} +``` + +### OTP Verification + +```javascript +try { + // Verify OTP code sent to user's email + await base44.auth.verifyOtp({ + email: "user@example.com", + otpCode: "123456" + }); + + console.log('OTP verified successfully'); + +} catch (error) { + console.error('OTP verification failed:', error.message); + if (error.status === 400) { + console.error('Invalid or expired OTP code'); + } else if (error.status === 429) { + console.error('Too many attempts. Please try again later.'); + } +} + +// Resend OTP if needed +try { + await base44.auth.resendOtp("user@example.com"); + console.log('OTP resent successfully'); + +} catch (error) { + console.error('Failed to resend OTP:', error.message); + if (error.status === 429) { + console.error('Too many requests. Please wait before trying again.'); + } +} +``` + +### Password Reset Flow + +```javascript +// Step 1: Request password reset +try { + await base44.auth.resetPasswordRequest("user@example.com"); + console.log('Password reset email sent. Check your inbox.'); + +} catch (error) { + console.error('Password reset request failed:', error.message); + if (error.status === 429) { + console.error('Too many requests. Please try again later.'); + } + // Note: For security, don't reveal if email exists +} + +// Step 2: Reset password with token from email +try { + await base44.auth.resetPassword({ + resetToken: "token-from-email", + newPassword: "newSecurePassword123" + }); + + console.log('Password reset successfully. You can now log in.'); + +} catch (error) { + console.error('Password reset failed:', error.message); + if (error.status === 400) { + console.error('Invalid or expired reset token'); + } else if (error.status === 422) { + console.error('Password does not meet requirements'); + } +} +``` + +### Change Password + +```javascript +try { + const currentUser = await base44.auth.me(); + + if (!currentUser) { + throw new Error('User must be authenticated to change password'); + } + + await base44.auth.changePassword({ + userId: currentUser.id, + currentPassword: "oldPassword123", + newPassword: "newSecurePassword456" + }); + + console.log('Password changed successfully'); + +} catch (error) { + console.error('Password change failed:', error.message); + if (error.status === 401) { + console.error('Current password is incorrect'); + } else if (error.status === 422) { + console.error('New password does not meet requirements'); + } else if (error.status === 403) { + console.error('Not authorized to change this password'); + } +} +``` + +--- + +## Error Handling + +### Common Error Scenarios + +The auth module can throw various errors. Here are common scenarios and how to handle them: + +#### Authentication Errors (401/403) +```javascript +try { + const user = await base44.auth.me(); +} catch (error) { + if (error.status === 401) { + // Token expired or invalid - navigate to your custom login page + navigate('/login'); + } else if (error.status === 403) { + // Email not verified or insufficient permissions + console.error('Access denied:', error.message); + } +} +``` + +#### Validation Errors (400/422) +```javascript +try { + await base44.auth.register({ + email: "invalid-email", + password: "weak" + }); +} catch (error) { + if (error.status === 400) { + console.error('Invalid input:', error.message); + // Handle validation errors + } else if (error.status === 422) { + console.error('Data validation failed:', error.details); + } +} +``` + +#### Rate Limiting (429) +```javascript +try { + await base44.auth.resendOtp("user@example.com"); +} catch (error) { + if (error.status === 429) { + console.error('Too many requests. Please wait before trying again.'); + // Show countdown or disable button temporarily + } +} +``` + +#### Generic Error Handler +```javascript +function handleAuthError(error) { + switch (error.status) { + case 400: + return 'Invalid input. Please check your data.'; + case 401: + return 'Authentication required. Redirecting to login...'; + case 403: + return 'Access denied. You may need to verify your email.'; + case 404: + return 'Resource not found.'; + case 422: + return 'Validation failed. Please check your input.'; + case 429: + return 'Too many requests. Please try again later.'; + case 500: + return 'Server error. Please try again.'; + default: + return 'An unexpected error occurred.'; + } +} + +// Usage +try { + await base44.auth.loginViaEmailPassword(email, password); +} catch (error) { + console.error(handleAuthError(error)); +} +``` + +--- + +## Auth Providers + +Configure authentication providers in your app dashboard: + +### Available Providers + +**Built-in (All Plans):** +- **Email/Password** - Default, always enabled +- **Google** - OAuth authentication +- **Microsoft** - OAuth authentication +- **Facebook** - OAuth authentication + +**SSO Providers (Elite Plan):** +- **Okta** +- **Azure AD** +- **GitHub** + +### Using OAuth Providers + +```javascript +// Initiate OAuth login flow +base44.auth.loginWithProvider('google'); + +// Return to specific page after authentication +base44.auth.loginWithProvider('microsoft', '/dashboard'); + +// Supported values: 'google', 'microsoft', 'facebook' +``` + +--- + +## Environment Availability + +| Environment | Availability | Notes | +|------------|--------------|-------| +| **Frontend** | ✅ Yes | All methods available | +| **Backend Functions** | ✅ Yes | Use `createClientFromRequest(req)` for authenticated client | +| **Service Role** | ❌ No | Auth methods not available in service role context | + +### Frontend Usage +```javascript +// Standard browser usage +const user = await base44.auth.me(); +``` + +### Backend Functions Usage +```javascript +import { createClientFromRequest } from "@base44/sdk"; + +Deno.serve(async (req) => { + const base44 = createClientFromRequest(req); + + // Get user from request context + const user = await base44.auth.me(); + + // User operations here... + return Response.json({ user }); +}); +``` + +--- + +## App Visibility + +Control who can access your app in the app settings: + +### Public Apps +- No login required for basic access +- Users can view public content without authentication +- Authenticated users get additional features/data + +### Private Apps +- Login required to access any content +- Unauthenticated users are automatically redirected to login +- All content is protected by default + +--- + +## Limitations + +### Authentication UI Options +- **Recommended:** Build custom login/signup UI using `loginViaEmailPassword()` and `loginWithProvider()` for full control over user experience and branding +- **Alternative:** `redirectToLogin()` uses Base44's hosted authentication pages with limited customization + +### Hosted Login (via redirectToLogin) +- `redirectToLogin()` shows both login and signup options on the same page +- No separate `redirectToSignup()` method +- Users can switch between login/signup on the hosted page +- ⚠️ **Note:** Prefer building custom login UI for better user experience + +### Password Requirements +- Minimum length and complexity requirements enforced +- Requirements not exposed via API +- Validation errors returned when requirements not met + +### Rate Limiting +- OTP requests are rate-limited to prevent abuse +- Password reset requests are rate-limited +- Login attempts may be rate-limited with Turnstile protection + +### Token Management +- JWTs are automatically stored in localStorage by default +- Token expiration and refresh not exposed in API +- Call `me()` or `isAuthenticated()` to verify token validity + +--- + +## Best Practices + +### 1. Always Handle Errors +```javascript +try { + await base44.auth.loginViaEmailPassword(email, password); +} catch (error) { + // Always handle authentication errors + console.error('Login failed:', error.message); +} +``` + +### 2. Verify Authentication Before Protected Actions +```javascript +const user = await requireAuth(); +if (!user) return; // Will redirect to login +// Proceed with authenticated action +``` + +### 3. Use Type Safety with TypeScript +```typescript +import type { User, LoginResponse } from '@base44/sdk'; + +const user: User = await base44.auth.me(); +const response: LoginResponse = await base44.auth.loginViaEmailPassword(email, password); +``` + +### 4. Don't Hardcode Credentials +```javascript +// ❌ BAD +base44.auth.setToken("hardcoded-token-here"); + +// ✅ GOOD +const token = await secureAuthFlow(); +base44.auth.setToken(token); +``` + +### 5. Provide User Feedback +```javascript +try { + await base44.auth.register(params); + showSuccessMessage('Registration successful! Check your email for verification code.'); +} catch (error) { + showErrorMessage('Registration failed: ' + error.message); +} +``` + +### 6. Handle Token Expiration Gracefully +```javascript +try { + const user = await base44.auth.me(); +} catch (error) { + if (error.status === 401) { + // Clear local state and navigate to login + localStorage.clear(); + navigate('/login'); + } +} +``` + +### 7. Build Custom Login UI (Recommended) +```javascript +// ✅ RECOMMENDED - Custom login form with direct methods +const handleLogin = async (email, password) => { + try { + const { user } = await base44.auth.loginViaEmailPassword(email, password); + navigate('/dashboard'); + } catch (error) { + setError(error.message); + } +}; + +const handleGoogleLogin = () => { + base44.auth.loginWithProvider('google', '/dashboard'); +}; + +// ❌ AVOID - Redirecting to hosted login page +// base44.auth.redirectToLogin(window.location.href); +``` diff --git a/evals/fixtures/agent-chat-component/skills/base44-sdk/references/base44-agents.md b/evals/fixtures/agent-chat-component/skills/base44-sdk/references/base44-agents.md new file mode 100644 index 0000000..ee55dee --- /dev/null +++ b/evals/fixtures/agent-chat-component/skills/base44-sdk/references/base44-agents.md @@ -0,0 +1,364 @@ +# Agents Module + +AI agent conversations and messages via `base44.agents`. + +> **Note:** This module requires a logged-in user. All agent methods work in the context of the authenticated user. + +## Contents +- [Concepts](#concepts) +- [Methods](#methods) +- [Examples](#examples) (Create, Get Conversations, List, Subscribe, Send Message, WhatsApp) +- [Message Structure](#message-structure) +- [Conversation Structure](#conversation-structure) +- [Common Patterns](#common-patterns) + +## Concepts + +- **Conversation**: A dialogue between user and an AI agent. Has unique ID, agent name, user reference, and metadata. +- **Message**: Single message in a conversation. Has role (`user`, `assistant`, `system`), content, timestamps, and optional metadata. + +## Methods + +| Method | Signature | Description | +|--------|-----------|-------------| +| `createConversation(params)` | `Promise` | Create a new conversation with an agent | +| `getConversations()` | `Promise` | Get all user's conversations | +| `getConversation(id)` | `Promise` | Get conversation with messages | +| `listConversations(filterParams)` | `Promise` | Filter/sort/paginate conversations | +| `subscribeToConversation(id, onUpdate?)` | `() => void` | Real-time updates via WebSocket (returns unsubscribe function) | +| `addMessage(conversation, message)` | `Promise` | Send a message | +| `getWhatsAppConnectURL(agentName)` | `string` | Get WhatsApp connection URL for agent | + +## Examples + +### Create Conversation + +```javascript +const conversation = await base44.agents.createConversation({ + agent_name: "support-agent", + metadata: { + order_id: "ORD-123", + category: "billing" + } +}); + +console.log(conversation.id); +``` + +### Get All Conversations + +```javascript +const conversations = await base44.agents.getConversations(); + +conversations.forEach(conv => { + console.log(conv.id, conv.agent_name, conv.created_date); +}); +``` + +### Get Single Conversation (with messages) + +```javascript +const conversation = await base44.agents.getConversation("conv-id-123"); + +console.log(conversation.messages); +``` + +### List with Filters + +```javascript +// Using filterParams object with q, sort, limit, skip, fields +const recent = await base44.agents.listConversations({ + q: { agent_name: "support-agent" }, + sort: "-created_date", + limit: 10, + skip: 0 +}); + +// Filter by metadata +const highPriority = await base44.agents.listConversations({ + q: { + agent_name: "support-agent", + "metadata.priority": "high" + }, + sort: "-updated_date", + limit: 20 +}); +``` + +### Subscribe to Updates (Real-time) + +```javascript +const unsubscribe = base44.agents.subscribeToConversation( + "conv-id-123", + (updatedConversation) => { + // Called when new messages arrive + console.log("New messages:", updatedConversation.messages); + } +); + +// Later: unsubscribe +unsubscribe(); +``` + +### Send a Message + +```javascript +const conversation = await base44.agents.getConversation("conv-id-123"); + +await base44.agents.addMessage(conversation, { + role: "user", + content: "What's the weather like today?" +}); +``` + +### Get WhatsApp Connection URL + +```javascript +const whatsappUrl = base44.agents.getWhatsAppConnectURL("support-agent"); +// Returns URL for users to connect with agent via WhatsApp +console.log(whatsappUrl); +``` + +## Message Structure + +```javascript +{ + role: "user" | "assistant" | "system", + content: "Message text or structured object", + created_date: "2024-01-15T10:30:00Z", + updated_date: "2024-01-15T10:30:00Z", + + // Optional fields + reasoning: { + content: "Agent's reasoning process", + timing: 1500 + }, + tool_calls: [{ + name: "search", + arguments: { query: "weather" }, + result: { ... }, + status: "success" + }], + file_urls: ["https://..."], + usage: { + prompt_tokens: 150, + completion_tokens: 50 + }, + metadata: { ... }, + custom_context: { ... } +} +``` + +## Conversation Structure + +```javascript +{ + id: "conv-id-123", + app_id: "app-id", + agent_name: "support-agent", + created_by_id: "user-id", + created_date: "2024-01-15T10:00:00Z", + updated_date: "2024-01-15T10:30:00Z", + messages: [ ... ], + metadata: { ... } +} +``` + +## Common Patterns + +### Chat Interface + +```javascript +// Load conversation +const conv = await base44.agents.getConversation(conversationId); +setMessages(conv.messages); + +// Subscribe to updates +const unsubscribe = base44.agents.subscribeToConversation(conversationId, (updated) => { + setMessages(updated.messages); +}); + +// Send message +async function sendMessage(text) { + await base44.agents.addMessage(conv, { role: "user", content: text }); +} + +// Cleanup on unmount +return () => unsubscribe(); +``` + +## Type Definitions + +### AgentConversation + +```typescript +/** An agent conversation containing messages exchanged with an AI agent. */ +interface AgentConversation { + /** Unique identifier for the conversation. */ + id: string; + /** Application ID. */ + app_id: string; + /** Name of the agent in this conversation. */ + agent_name: string; + /** ID of the user who created the conversation. */ + created_by_id: string; + /** When the conversation was created. */ + created_date: string; + /** When the conversation was last updated. */ + updated_date: string; + /** Array of messages in the conversation. */ + messages: AgentMessage[]; + /** Optional metadata associated with the conversation. */ + metadata?: Record; +} +``` + +### AgentMessage + +```typescript +/** A message in an agent conversation. */ +interface AgentMessage { + /** Unique identifier for the message. */ + id: string; + /** Role of the message sender. */ + role: "user" | "assistant" | "system"; + /** When the message was created. */ + created_date: string; + /** When the message was last updated. */ + updated_date: string; + /** Message content. */ + content?: string | Record; + /** Optional reasoning information for the message. */ + reasoning?: AgentMessageReasoning | null; + /** URLs to files attached to the message. */ + file_urls?: string[]; + /** Tool calls made by the agent. */ + tool_calls?: AgentMessageToolCall[]; + /** Token usage statistics. */ + usage?: AgentMessageUsage; + /** Whether the message is hidden from the user. */ + hidden?: boolean; + /** Custom context provided with the message. */ + custom_context?: AgentMessageCustomContext[]; + /** Model used to generate the message. */ + model?: string; + /** Checkpoint ID for the message. */ + checkpoint_id?: string; + /** Metadata about when and by whom the message was created. */ + metadata?: AgentMessageMetadata; +} +``` + +### Supporting Types + +```typescript +/** Reasoning information for an agent message. */ +interface AgentMessageReasoning { + /** When reasoning started. */ + start_date: string; + /** When reasoning ended. */ + end_date?: string; + /** Reasoning content. */ + content: string; +} + +/** A tool call made by the agent. */ +interface AgentMessageToolCall { + /** Tool call ID. */ + id: string; + /** Name of the tool called. */ + name: string; + /** Arguments passed to the tool as JSON string. */ + arguments_string: string; + /** Status of the tool call. */ + status: "running" | "success" | "error" | "stopped"; + /** Results from the tool call. */ + results?: string; +} + +/** Token usage statistics for an agent message. */ +interface AgentMessageUsage { + /** Number of tokens in the prompt. */ + prompt_tokens?: number; + /** Number of tokens in the completion. */ + completion_tokens?: number; +} + +/** Custom context provided with an agent message. */ +interface AgentMessageCustomContext { + /** Context message. */ + message: string; + /** Associated data for the context. */ + data: Record; + /** Type of context. */ + type: string; +} + +/** Metadata about when and by whom a message was created. */ +interface AgentMessageMetadata { + /** When the message was created. */ + created_date: string; + /** Email of the user who created the message. */ + created_by_email: string; + /** Full name of the user who created the message. */ + created_by_full_name: string; +} +``` + +### CreateConversationParams + +```typescript +/** Parameters for creating a new conversation. */ +interface CreateConversationParams { + /** The name of the agent to create a conversation with. */ + agent_name: string; + /** Optional metadata to attach to the conversation. */ + metadata?: Record; +} +``` + +### ModelFilterParams + +```typescript +/** Parameters for filtering, sorting, and paginating conversations. */ +interface ModelFilterParams { + /** Query object with field-value pairs for filtering. */ + q?: Record; + /** Sort parameter (e.g., "-created_date" for descending). */ + sort?: string | null; + /** Maximum number of results to return. */ + limit?: number | null; + /** Number of results to skip for pagination. */ + skip?: number | null; + /** Array of field names to include in the response. */ + fields?: string[] | null; +} +``` + +### AgentsModule + +```typescript +/** Agents module for managing AI agent conversations. */ +interface AgentsModule { + /** Gets all conversations from all agents in the app. */ + getConversations(): Promise; + + /** Gets a specific conversation by ID. */ + getConversation(conversationId: string): Promise; + + /** Lists conversations with filtering, sorting, and pagination. */ + listConversations(filterParams: ModelFilterParams): Promise; + + /** Creates a new conversation with an agent. */ + createConversation(conversation: CreateConversationParams): Promise; + + /** Adds a message to a conversation. */ + addMessage(conversation: AgentConversation, message: Partial): Promise; + + /** Subscribes to real-time updates for a conversation. Returns unsubscribe function. */ + subscribeToConversation(conversationId: string, onUpdate?: (conversation: AgentConversation) => void): () => void; + + /** Gets WhatsApp connection URL for an agent. */ + getWhatsAppConnectURL(agentName: string): string; +} +``` diff --git a/evals/fixtures/agent-chat-component/skills/base44-sdk/references/client.md b/evals/fixtures/agent-chat-component/skills/base44-sdk/references/client.md new file mode 100644 index 0000000..554bf2b --- /dev/null +++ b/evals/fixtures/agent-chat-component/skills/base44-sdk/references/client.md @@ -0,0 +1,257 @@ +# Client Setup + +How to create and configure the Base44 client. + +## Contents +- [In Base44-Generated Apps](#in-base44-generated-apps) +- [In External Apps](#in-external-apps) +- [In Backend Functions](#in-backend-functions) +- [Authentication Modes](#authentication-modes) (Anonymous, User, Service Role) +- [Available Modules](#available-modules) +- [Client Methods](#client-methods) +- [Client Configuration Options](#client-configuration-options) + +## In Base44-Generated Apps + +The client is pre-configured and available as `base44`. Just use it: + +```javascript +const tasks = await base44.entities.Task.list(); +``` + +## In External Apps + +Install the SDK and create a client: + +```bash +npm install @base44/sdk +``` + +```javascript +import { createClient } from "@base44/sdk"; + +// IMPORTANT: The parameter name is 'appId' (NOT 'clientId', NOT 'id') +// IMPORTANT: onError must be nested inside 'options' object +const base44 = createClient({ + appId: "your-app-id", // Required: Use 'appId' parameter + token: "optional-user-token", // Optional: for pre-authenticated requests + options: { // Optional: configuration options + onError: (error) => { // Optional: error handler (must be in options) + console.error("Base44 error:", error); + } + } +}); +``` + +**Common Mistakes:** +- ❌ `createClient({ clientId: "..." })` - WRONG parameter name +- ❌ `createClient({ id: "..." })` - WRONG parameter name +- ❌ `createClient({ appId: "...", onError: ... })` - WRONG: onError must be in options +- ✅ `createClient({ appId: "..." })` - CORRECT parameter name +- ✅ `createClient({ appId: "...", options: { onError: ... } })` - CORRECT: onError in options + +## In Backend Functions + +Use `createClientFromRequest` to get a client with the caller's auth context: + +```javascript +import { createClientFromRequest } from "@base44/sdk"; + +Deno.serve(async (req) => { + const base44 = createClientFromRequest(req); + + // Client inherits authentication from the request + const user = await base44.auth.me(); + + return Response.json({ user }); +}); +``` + +## Authentication Modes + +| Mode | How to Get | Permissions | +|------|-----------|-------------| +| **Anonymous** | `createClient({ appId })` without token | Public data only | +| **User** | After `loginViaEmailPassword()` or via `createClientFromRequest` | User's own data | +| **Service Role** | `base44.asServiceRole.*` in backend | Full admin access | + +## Anonymous Mode + +No authentication. Can only access public resources. + +```javascript +const base44 = createClient({ appId: "your-app-id" }); + +// Only works if Task entity allows anonymous read +const publicTasks = await base44.entities.Task.list(); +``` + +## User Mode + +After user logs in, the client automatically includes their token. + +```javascript +const base44 = createClient({ appId: "your-app-id" }); + +// Login sets the token +await base44.auth.loginViaEmailPassword("user@example.com", "password"); + +// Subsequent requests are authenticated +const user = await base44.auth.me(); +const myTasks = await base44.entities.Task.list(); // filtered by permissions +``` + +## Service Role Mode + +Admin-level access. **Backend only.** + +```javascript +// Inside a backend function +Deno.serve(async (req) => { + const base44 = createClientFromRequest(req); + + // User mode - respects permissions + const myTasks = await base44.entities.Task.list(); + + // Service role - bypasses permissions + const allTasks = await base44.asServiceRole.entities.Task.list(); + const allUsers = await base44.asServiceRole.entities.User.list(); + const oauthToken = await base44.asServiceRole.connectors.getAccessToken("slack"); + + return Response.json({ myTasks, allTasks }); +}); +``` + +## Available Modules + +The client exposes these modules: + +```javascript +base44.entities // CRUD operations +base44.auth // Authentication +base44.agents // AI conversations +base44.functions // Backend function invocation +base44.integrations // Third-party services +base44.analytics // Event tracking +base44.appLogs // App usage logging +base44.users // User invitations + +// Service role only (backend) +base44.asServiceRole.entities +base44.asServiceRole.agents +base44.asServiceRole.functions +base44.asServiceRole.integrations +base44.asServiceRole.appLogs +base44.asServiceRole.connectors +``` + +## Client Methods + +The client provides these methods: + +```javascript +// Set authentication token for all subsequent requests +base44.setToken(newToken); + +// Cleanup WebSocket connections (call when done with client) +base44.cleanup(); +``` + +### setToken + +Updates the authentication token for all subsequent API requests and WebSocket connections. + +```javascript +// After receiving a token (e.g., from external auth) +base44.setToken("new-jwt-token"); +``` + +### cleanup + +Disconnects WebSocket connections. Call when you're done with the client or when the component unmounts. + +```javascript +// Cleanup on component unmount (React example) +useEffect(() => { + return () => base44.cleanup(); +}, []); +``` + +## Client Configuration Options + +```javascript +createClient({ + appId: "your-app-id", // Required: MUST use 'appId' (not 'clientId' or 'id') + token: "jwt-token", // Optional: pre-set auth token + options: { // Optional: configuration options + onError: (error) => {} // Optional: global error handler (must be in options) + } +}); +``` + +**⚠️ Critical:** +- The parameter name is `appId`, not `clientId` or `id`. Using the wrong parameter name will cause errors. +- The `onError` handler must be nested inside the `options` object, not at the top level. + +## Type Definitions + +### CreateClientConfig + +```typescript +/** Configuration for creating a Base44 client. */ +interface CreateClientConfig { + /** The Base44 app ID (required). */ + appId: string; + /** User authentication token. Used to authenticate as a specific user. */ + token?: string; + /** Service role authentication token (backend only). */ + serviceToken?: string; + /** Additional client options. */ + options?: CreateClientOptions; +} + +/** Options for creating a Base44 client. */ +interface CreateClientOptions { + /** Optional error handler called whenever an API error occurs. */ + onError?: (error: Error) => void; +} +``` + +### Base44Client + +```typescript +/** The Base44 client instance. */ +interface Base44Client { + /** Entities module for CRUD operations on your data models. */ + entities: EntitiesModule; + /** Integrations module for calling pre-built integration endpoints. */ + integrations: IntegrationsModule; + /** Auth module for user authentication and management. */ + auth: AuthModule; + /** Functions module for invoking custom backend functions. */ + functions: FunctionsModule; + /** Agents module for managing AI agent conversations. */ + agents: AgentsModule; + /** App logs module for tracking app usage. */ + appLogs: AppLogsModule; + /** Analytics module for tracking custom events. */ + analytics: AnalyticsModule; + + /** Cleanup function to disconnect WebSocket connections. */ + cleanup(): void; + + /** Sets a new authentication token for all subsequent requests. */ + setToken(newToken: string): void; + + /** Provides access to modules with elevated service role permissions (backend only). */ + readonly asServiceRole: { + entities: EntitiesModule; + integrations: IntegrationsModule; + connectors: ConnectorsModule; + functions: FunctionsModule; + agents: AgentsModule; + appLogs: AppLogsModule; + cleanup(): void; + }; +} +``` diff --git a/evals/fixtures/agent-chat-component/skills/base44-sdk/references/connectors.md b/evals/fixtures/agent-chat-component/skills/base44-sdk/references/connectors.md new file mode 100644 index 0000000..f3fbcb4 --- /dev/null +++ b/evals/fixtures/agent-chat-component/skills/base44-sdk/references/connectors.md @@ -0,0 +1,113 @@ +# Connectors Module + +OAuth token management for external services. **Service role only** (backend functions). + +## Method + +```javascript +base44.asServiceRole.connectors.getAccessToken(integrationType): Promise +``` + +Returns a raw OAuth access token for the specified service. + +## Supported Services + +| Integration Type | Service | +|-----------------|---------| +| `"googlecalendar"` | Google Calendar | +| `"googledrive"` | Google Drive | +| `"slack"` | Slack | +| `"notion"` | Notion | +| `"salesforce"` | Salesforce | +| `"hubspot"` | HubSpot | +| `"linkedin"` | LinkedIn | +| `"tiktok"` | TikTok | +| `"github"` | GitHub | + +## Example Usage + +```javascript +// Backend function only +Deno.serve(async (req) => { + const base44 = createClientFromRequest(req); + + // Get OAuth token for Slack + const slackToken = await base44.asServiceRole.connectors.getAccessToken("slack"); + + // Use token directly with Slack API + const response = await fetch("https://slack.com/api/chat.postMessage", { + method: "POST", + headers: { + "Authorization": `Bearer ${slackToken}`, + "Content-Type": "application/json" + }, + body: JSON.stringify({ + channel: "#general", + text: "Hello from Base44!" + }) + }); + + return Response.json(await response.json()); +}); +``` + +## Google Calendar Example + +```javascript +Deno.serve(async (req) => { + const base44 = createClientFromRequest(req); + + const token = await base44.asServiceRole.connectors.getAccessToken("googlecalendar"); + + // List upcoming events + const response = await fetch( + "https://www.googleapis.com/calendar/v3/calendars/primary/events?" + + new URLSearchParams({ + maxResults: "10", + orderBy: "startTime", + singleEvents: "true", + timeMin: new Date().toISOString() + }), + { + headers: { "Authorization": `Bearer ${token}` } + } + ); + + const events = await response.json(); + return Response.json(events); +}); +``` + +## Setup Requirements + +1. **Builder plan** or higher +2. **Backend functions** enabled +3. **Connector configured** in Base44 dashboard (OAuth flow completed) + +## Important Notes + +- **One account per connector per app**: All users share the same connected account +- **Backend only**: `connectors` module not available in frontend code +- **Service role required**: Must use `base44.asServiceRole.connectors` +- **You handle the API calls**: Base44 only provides the token; you make the actual API requests +- **Token refresh**: Base44 handles token refresh automatically + +## Type Definitions + +```typescript +/** + * The type of external integration/connector. + * Examples: 'googlecalendar', 'slack', 'github', 'notion', etc. + */ +type ConnectorIntegrationType = string; + +/** Connectors module for managing OAuth tokens for external services. */ +interface ConnectorsModule { + /** + * Retrieves an OAuth access token for a specific external integration type. + * @param integrationType - The type of integration (e.g., 'googlecalendar', 'slack'). + * @returns Promise resolving to the access token string. + */ + getAccessToken(integrationType: ConnectorIntegrationType): Promise; +} +``` diff --git a/evals/fixtures/agent-chat-component/skills/base44-sdk/references/entities.md b/evals/fixtures/agent-chat-component/skills/base44-sdk/references/entities.md new file mode 100644 index 0000000..a7bdf35 --- /dev/null +++ b/evals/fixtures/agent-chat-component/skills/base44-sdk/references/entities.md @@ -0,0 +1,253 @@ +# Entities Module + +CRUD operations on data models. Access via `base44.entities.EntityName.method()`. + +## Contents +- [Methods](#methods) +- [Examples](#examples) (Create, Bulk Create, List, Filter, Get, Update, Delete, Subscribe) +- [User Entity](#user-entity) +- [Service Role Access](#service-role-access) +- [Permissions](#permissions) + +## Methods + +| Method | Signature | Description | +|--------|-----------|-------------| +| `create(data)` | `Promise` | Create one record | +| `bulkCreate(dataArray)` | `Promise` | Create multiple records | +| `list(sort?, limit?, skip?, fields?)` | `Promise` | Get all records (paginated) | +| `filter(query, sort?, limit?, skip?, fields?)` | `Promise` | Get records matching conditions | +| `get(id)` | `Promise` | Get single record by ID | +| `update(id, data)` | `Promise` | Update record (partial update) | +| `delete(id)` | `Promise` | Delete record by ID | +| `deleteMany(query)` | `Promise` | Delete all matching records | +| `importEntities(file)` | `Promise` | Import from CSV (frontend only) | +| `subscribe(callback)` | `() => void` | Subscribe to realtime updates (returns unsubscribe function) | + +## Examples + +### Create + +```javascript +const task = await base44.entities.Task.create({ + title: "Complete documentation", + status: "pending", + dueDate: "2024-12-31" +}); +``` + +### Bulk Create + +```javascript +const tasks = await base44.entities.Task.bulkCreate([ + { title: "Task 1", status: "pending" }, + { title: "Task 2", status: "pending" } +]); +``` + +### List with Pagination + +```javascript +// Get first 10 records, sorted by created_date descending +const tasks = await base44.entities.Task.list( + "-created_date", // sort (string: prefix with - for descending) + 10, // limit + 0 // skip +); + +// Get next page +const page2 = await base44.entities.Task.list("-created_date", 10, 10); +``` + +### Filter + +```javascript +// Simple filter +const pending = await base44.entities.Task.filter({ status: "pending" }); + +// Multiple conditions +const myPending = await base44.entities.Task.filter({ + status: "pending", + assignedTo: userId +}); + +// With sort, limit, skip +const recent = await base44.entities.Task.filter( + { status: "pending" }, + "-created_date", // sort (string: prefix with - for descending) + 5, + 0 +); + +// Select specific fields +const titles = await base44.entities.Task.filter( + { status: "pending" }, + null, + null, + null, + ["id", "title"] +); +``` + +### Get by ID + +```javascript +const task = await base44.entities.Task.get("task-id-123"); +``` + +### Update + +```javascript +// Partial update - only specified fields change +await base44.entities.Task.update("task-id-123", { + status: "completed", + completedAt: new Date().toISOString() +}); +``` + +### Delete + +```javascript +// Single record +await base44.entities.Task.delete("task-id-123"); + +// Multiple records matching query +await base44.entities.Task.deleteMany({ status: "archived" }); +``` + +### Subscribe to Realtime Updates + +```javascript +// Subscribe to all changes on Task entity +const unsubscribe = base44.entities.Task.subscribe((event) => { + console.log(`Task ${event.id} was ${event.type}:`, event.data); + // event.type is "create", "update", or "delete" +}); + +// Later: unsubscribe to stop receiving updates +unsubscribe(); +``` + +**Event structure:** +```javascript +{ + type: "create" | "update" | "delete", + data: { ... }, // the entity data + id: "entity-id", // the affected entity's ID + timestamp: "2024-01-15T10:30:00Z" +} +``` + +## User Entity + +Every app has a built-in `User` entity with special rules: + +- Regular users can only read/update **their own** record +- Cannot create users via `entities.create()` - use `auth.register()` instead +- Service role has full access to all user records + +```javascript +// Get current user's record +const me = await base44.entities.User.get(currentUserId); + +// Service role: get any user +const anyUser = await base44.asServiceRole.entities.User.get(userId); +``` + +## Service Role Access + +For admin-level operations (bypass user permissions): + +```javascript +// Backend only +const allTasks = await base44.asServiceRole.entities.Task.list(); +const allUsers = await base44.asServiceRole.entities.User.list(); +``` + +## Permissions (RLS & FLS) + +Data access is controlled by **Row Level Security (RLS)** and **Field Level Security (FLS)** rules defined in entity schemas. + +1. **Authentication level**: anonymous, authenticated, or service role +2. **RLS rules**: Control which records (rows) users can create/read/update/delete +3. **FLS rules**: Control which fields users can read/write within accessible records + +Operations succeed or fail based on these rules - no partial results. + +RLS and FLS are configured in entity schema files (`base44/entities/*.jsonc`). See [entities-create.md](../../base44-cli/references/entities-create.md#row-level-security-rls) for configuration details. + +**Note:** `asServiceRole` sets the user's role to `"admin"` but does NOT bypass RLS. Your RLS rules must include admin access (e.g., `{ "user_condition": { "role": "admin" } }`) for service role operations to succeed. + +## Type Definitions + +### RealtimeEvent + +```typescript +/** Event types for realtime entity updates. */ +type RealtimeEventType = "create" | "update" | "delete"; + +/** Payload received when a realtime event occurs. */ +interface RealtimeEvent { + /** The type of change that occurred. */ + type: RealtimeEventType; + /** The entity data. */ + data: any; + /** The unique identifier of the affected entity. */ + id: string; + /** ISO 8601 timestamp of when the event occurred. */ + timestamp: string; +} + +/** Callback function invoked when a realtime event occurs. */ +type RealtimeCallback = (event: RealtimeEvent) => void; + +/** Function returned from subscribe, call it to unsubscribe. */ +type Subscription = () => void; +``` + +### EntityHandler + +```typescript +/** Entity handler providing CRUD operations for a specific entity type. */ +interface EntityHandler { + /** Lists records with optional pagination and sorting. */ + list(sort?: string, limit?: number, skip?: number, fields?: string[]): Promise; + + /** Filters records based on a query. */ + filter(query: Record, sort?: string, limit?: number, skip?: number, fields?: string[]): Promise; + + /** Gets a single record by ID. */ + get(id: string): Promise; + + /** Creates a new record. */ + create(data: Record): Promise; + + /** Updates an existing record. */ + update(id: string, data: Record): Promise; + + /** Deletes a single record by ID. */ + delete(id: string): Promise; + + /** Deletes multiple records matching a query. */ + deleteMany(query: Record): Promise; + + /** Creates multiple records in a single request. */ + bulkCreate(data: Record[]): Promise; + + /** Imports records from a file (frontend only). */ + importEntities(file: File): Promise; + + /** Subscribes to realtime updates. Returns unsubscribe function. */ + subscribe(callback: RealtimeCallback): Subscription; +} +``` + +### EntitiesModule + +```typescript +/** Entities module for managing app data. */ +interface EntitiesModule { + /** Access any entity by name dynamically. */ + [entityName: string]: EntityHandler; +} +``` diff --git a/evals/fixtures/agent-chat-component/skills/base44-sdk/references/functions.md b/evals/fixtures/agent-chat-component/skills/base44-sdk/references/functions.md new file mode 100644 index 0000000..8072465 --- /dev/null +++ b/evals/fixtures/agent-chat-component/skills/base44-sdk/references/functions.md @@ -0,0 +1,216 @@ +# Functions Module + +Invoke custom backend functions via `base44.functions`. + +## Contents +- [Method](#method) +- [Invoking Functions](#invoking-functions) (Frontend, File Upload, Service Role, REST API) +- [Writing Backend Functions](#writing-backend-functions) (Basic, Service Role, Secrets, Errors) +- [Setup Requirements](#setup-requirements) +- [Authentication Modes](#authentication-modes) + +## Method + +```javascript +base44.functions.invoke(functionName, data): Promise +``` + +- `functionName`: Name of the backend function +- `data`: Object of parameters (sent as JSON, or multipart if contains File objects) +- Returns: Whatever the function returns + +## Invoking Functions + +### From Frontend + +```javascript +const result = await base44.functions.invoke("processOrder", { + orderId: "order-123", + action: "ship" +}); + +console.log(result); +``` + +### With File Upload + +```javascript +const fileInput = document.querySelector('input[type="file"]'); +const file = fileInput.files[0]; + +// Automatically uses multipart/form-data when File objects present +const result = await base44.functions.invoke("uploadDocument", { + file: file, + category: "invoices" +}); +``` + +### With Service Role (Backend) + +```javascript +// Inside another backend function +const result = await base44.asServiceRole.functions.invoke("adminTask", { + userId: "user-123" +}); +``` + +### Via REST API (curl) + +Functions can be called via HTTP POST to your app domain: + +```bash +curl -X POST "https:///functions/" \ + -H "Content-Type: application/json" \ + -d '{"key": "value"}' +``` + +## Writing Backend Functions + +Backend functions run on Deno. Must export using `Deno.serve()`. + +### Required Directory Structure + +Each function must be in its own subdirectory under `base44/functions/` with a configuration file: + +``` +base44/ + functions/ + process-order/ # kebab-case directory name + function.jsonc # required configuration + index.ts # entry point +``` + +**function.jsonc:** +```jsonc +{ + "name": "process-order", + "entry": "index.ts" +} +``` + +For complete setup and deployment instructions, see [functions-create.md](../../base44-cli/references/functions-create.md) in base44-cli. + +### Basic Structure + +```javascript +// base44/functions/process-order/index.ts +import { createClientFromRequest } from "npm:@base44/sdk"; + +Deno.serve(async (req) => { + // Get authenticated client from request + const base44 = createClientFromRequest(req); + + // Parse input + const { orderId, action } = await req.json(); + + // Your logic here + const order = await base44.entities.Orders.get(orderId); + + // Return response + return Response.json({ + success: true, + order: order + }); +}); +``` + +### With Service Role Access + +```javascript +import { createClientFromRequest } from "npm:@base44/sdk"; + +Deno.serve(async (req) => { + const base44 = createClientFromRequest(req); + + // Check user is authenticated + const user = await base44.auth.me(); + if (!user) { + return Response.json({ error: "Unauthorized" }, { status: 401 }); + } + + // Use service role for admin operations + const allOrders = await base44.asServiceRole.entities.Orders.list(); + + return Response.json({ orders: allOrders }); +}); +``` + +### Using Secrets + +```javascript +Deno.serve(async (req) => { + // Access environment variables (configured in app settings) + const apiKey = Deno.env.get("STRIPE_API_KEY"); + + const response = await fetch("https://api.stripe.com/v1/charges", { + headers: { + "Authorization": `Bearer ${apiKey}` + } + }); + + return Response.json(await response.json()); +}); +``` + +### Error Handling + +```javascript +import { createClientFromRequest } from "npm:@base44/sdk"; + +Deno.serve(async (req) => { + try { + const base44 = createClientFromRequest(req); + const { orderId } = await req.json(); + + const order = await base44.entities.Orders.get(orderId); + if (!order) { + return Response.json( + { error: "Order not found" }, + { status: 404 } + ); + } + + return Response.json({ order }); + + } catch (error) { + return Response.json( + { error: error.message }, + { status: 500 } + ); + } +}); +``` + +## Setup Requirements + +1. Enable Backend Functions in app settings (requires appropriate plan) +2. Create function files in `/functions` folder +3. Configure secrets via app dashboard for API keys + +## Authentication Modes + +| Mode | Context | Permissions | +|------|---------|-------------| +| User | `base44.functions.invoke()` | Runs under calling user's permissions | +| Service Role | `base44.asServiceRole.functions.invoke()` | Admin-level access | + +Inside the function, use `createClientFromRequest(req)` to get a client that inherits the caller's auth context. + +## Type Definitions + +```typescript +/** Functions module for invoking custom backend functions. */ +interface FunctionsModule { + /** + * Invokes a custom backend function by name. + * + * If any parameter is a File object, the request will automatically be + * sent as multipart/form-data. Otherwise, it will be sent as JSON. + * + * @param functionName - The name of the function to invoke. + * @param data - An object containing named parameters for the function. + * @returns Promise resolving to the function's response. + */ + invoke(functionName: string, data: Record): Promise; +} +``` diff --git a/evals/fixtures/agent-chat-component/skills/base44-sdk/references/integrations.md b/evals/fixtures/agent-chat-component/skills/base44-sdk/references/integrations.md new file mode 100644 index 0000000..f356a18 --- /dev/null +++ b/evals/fixtures/agent-chat-component/skills/base44-sdk/references/integrations.md @@ -0,0 +1,377 @@ +# Integrations Module + +Access third-party services via `base44.integrations`. + +## Types of Integrations + +1. **Core/Built-in**: AI, email, file uploads (available by default) +2. **Catalog integrations**: Pre-built connectors from Base44 catalog +3. **Custom integrations**: Your own OpenAPI-based integrations + +## Accessing Integrations + +```javascript +// Core integrations +base44.integrations.Core.FunctionName(params) + +// Custom integrations +base44.integrations.custom.call(slug, operationId, params) +``` + +## Core Integrations + +### InvokeLLM (AI Text Generation) + +Generate text or structured JSON data using AI models. + +```javascript +// Basic prompt - returns string +const response = await base44.integrations.Core.InvokeLLM({ + prompt: "Summarize this text: ..." +}); + +// With internet context (uses Google Search, Maps, News) +const response = await base44.integrations.Core.InvokeLLM({ + prompt: "What's the current weather in NYC?", + add_context_from_internet: true +}); + +// Structured JSON response - returns object +const response = await base44.integrations.Core.InvokeLLM({ + prompt: "Analyze the sentiment of: 'Great product but slow shipping'", + response_json_schema: { + type: "object", + properties: { + sentiment: { type: "string", enum: ["positive", "negative", "mixed"] }, + score: { type: "number", description: "Score from 1-10" }, + key_points: { type: "array", items: { type: "string" } } + } + } +}); +// Returns: { sentiment: "mixed", score: 7, key_points: ["great product", "slow shipping"] } + +// With file attachments (uploaded via UploadFile) +const response = await base44.integrations.Core.InvokeLLM({ + prompt: "Describe what's in this image", + file_urls: ["https://...uploaded_image.png"] +}); +``` + +**Parameters:** +- `prompt` (string, required): The prompt text to send to the model +- `add_context_from_internet` (boolean, optional): If true, uses Google Search/Maps/News for real-time context +- `response_json_schema` (object, optional): JSON schema for structured output +- `file_urls` (string[], optional): URLs of uploaded files for context + +### GenerateImage + +Create AI-generated images from text prompts. + +```javascript +const { url } = await base44.integrations.Core.GenerateImage({ + prompt: "A serene mountain landscape with a lake" +}); +console.log(url); // https://...generated_image.png +``` + +### SendEmail + +Send emails to registered users. Every app gets this integration (no plan upgrade required). + +```javascript +await base44.integrations.Core.SendEmail({ + to: "user@example.com", + subject: "Welcome!", + body: "

Hello

Welcome to our app.

", + from_name: "My App" // optional, defaults to app name +}); +``` + +**Parameters:** +- `to` (string, required): Recipient email address +- `subject` (string, required): Email subject line +- `body` (string, required): Plain text or HTML email body +- `from_name` (string, optional): Sender name displayed to recipient + +**Limitations:** +- 1 credit per email (2 credits with custom domain) + +### UploadFile (Public) + +Upload files to public storage. + +```javascript +const fileInput = document.querySelector('input[type="file"]'); +const { file_url } = await base44.integrations.Core.UploadFile({ + file: fileInput.files[0] +}); +console.log(file_url); // https://...uploaded_file.pdf +``` + +### UploadPrivateFile + +Upload files to private storage that requires a signed URL to access. + +```javascript +const { file_uri } = await base44.integrations.Core.UploadPrivateFile({ + file: fileInput.files[0] +}); +console.log(file_uri); // "private/user123/document.pdf" + +// Create a signed URL to access the file +const { signed_url } = await base44.integrations.Core.CreateFileSignedUrl({ + file_uri, + expires_in: 3600 // URL expires in 1 hour (default: 300 seconds) +}); +console.log(signed_url); // Temporary URL to access the private file +``` + +### CreateFileSignedUrl + +Generate temporary access links for private files. + +```javascript +const { signed_url } = await base44.integrations.Core.CreateFileSignedUrl({ + file_uri: "private/user123/document.pdf", + expires_in: 7200 // 2 hours +}); +``` + +**Parameters:** +- `file_uri` (string, required): URI from UploadPrivateFile +- `expires_in` (number, optional): Expiration time in seconds (default: 300) + +### ExtractDataFromUploadedFile + +Extract structured data from uploaded files using AI. + +```javascript +// First upload the file +const { file_url } = await base44.integrations.Core.UploadFile({ file }); + +// Then extract structured data +const result = await base44.integrations.Core.ExtractDataFromUploadedFile({ + file_url, + json_schema: { + type: "object", + properties: { + invoice_number: { type: "string" }, + total_amount: { type: "number" }, + date: { type: "string" }, + vendor_name: { type: "string" } + } + } +}); +console.log(result); // { invoice_number: "INV-12345", total_amount: 1250.00, ... } +``` + +## Custom Integrations + +Custom integrations allow workspace administrators to connect any external API by importing an OpenAPI specification. Use `base44.integrations.custom.call()` to invoke them. + +### Syntax + +```javascript +const response = await base44.integrations.custom.call( + slug, // Integration identifier (set by admin) + operationId, // Endpoint in "method:path" format + params // Optional: payload, pathParams, queryParams +); +``` + +### Examples + +```javascript +// GET request with query params +const response = await base44.integrations.custom.call( + "my-crm", + "get:/contacts", + { queryParams: { limit: 10, status: "active" } } +); + +// POST request with body +const response = await base44.integrations.custom.call( + "my-crm", + "post:/contacts", + { payload: { name: "John Doe", email: "john@example.com" } } +); + +// Request with path parameters +const response = await base44.integrations.custom.call( + "github", + "post:/repos/{owner}/{repo}/issues", + { + pathParams: { owner: "myorg", repo: "myrepo" }, + payload: { title: "Bug report", body: "Something is broken" } + } +); +``` + +### Response Structure + +```javascript +{ + success: true, // Whether external API returned 2xx + status_code: 200, // HTTP status code + data: { ... } // Response from external API +} +``` + +## Requirements + +- **Core integrations**: Available on all plans +- **Catalog/Custom integrations**: Require Builder plan or higher + +## Type Definitions + +### Core Integration Parameters + +```typescript +/** Parameters for the InvokeLLM function. */ +interface InvokeLLMParams { + /** The prompt text to send to the model. */ + prompt: string; + /** If true, uses Google Search/Maps/News for real-time context. */ + add_context_from_internet?: boolean; + /** JSON schema for structured output. If provided, returns object instead of string. */ + response_json_schema?: object; + /** File URLs (from UploadFile) to provide as context. */ + file_urls?: string[]; +} + +/** Parameters for the GenerateImage function. */ +interface GenerateImageParams { + /** Description of the image to generate. */ + prompt: string; +} + +/** Result from GenerateImage. */ +interface GenerateImageResult { + /** URL of the generated image. */ + url: string; +} + +/** Parameters for the UploadFile function. */ +interface UploadFileParams { + /** The file object to upload. */ + file: File; +} + +/** Result from UploadFile. */ +interface UploadFileResult { + /** URL of the uploaded file. */ + file_url: string; +} + +/** Parameters for the SendEmail function. */ +interface SendEmailParams { + /** Recipient email address. */ + to: string; + /** Email subject line. */ + subject: string; + /** Plain text or HTML email body. */ + body: string; + /** Sender name (defaults to app name). */ + from_name?: string; +} + +/** Parameters for ExtractDataFromUploadedFile. */ +interface ExtractDataFromUploadedFileParams { + /** URL of the uploaded file. */ + file_url: string; + /** JSON schema defining fields to extract. */ + json_schema: object; +} + +/** Parameters for UploadPrivateFile. */ +interface UploadPrivateFileParams { + /** The file object to upload. */ + file: File; +} + +/** Result from UploadPrivateFile. */ +interface UploadPrivateFileResult { + /** URI of the private file (used for signed URLs). */ + file_uri: string; +} + +/** Parameters for CreateFileSignedUrl. */ +interface CreateFileSignedUrlParams { + /** URI from UploadPrivateFile. */ + file_uri: string; + /** Expiration time in seconds (default: 300). */ + expires_in?: number; +} + +/** Result from CreateFileSignedUrl. */ +interface CreateFileSignedUrlResult { + /** Temporary signed URL to access the file. */ + signed_url: string; +} +``` + +### CoreIntegrations + +```typescript +/** Core package containing built-in Base44 integration functions. */ +interface CoreIntegrations { + InvokeLLM(params: InvokeLLMParams): Promise; + GenerateImage(params: GenerateImageParams): Promise; + UploadFile(params: UploadFileParams): Promise; + SendEmail(params: SendEmailParams): Promise; + ExtractDataFromUploadedFile(params: ExtractDataFromUploadedFileParams): Promise; + UploadPrivateFile(params: UploadPrivateFileParams): Promise; + CreateFileSignedUrl(params: CreateFileSignedUrlParams): Promise; +} +``` + +### Custom Integrations + +```typescript +/** Parameters for calling a custom integration endpoint. */ +interface CustomIntegrationCallParams { + /** Request body payload. */ + payload?: Record; + /** Path parameters to substitute in the URL. */ + pathParams?: Record; + /** Query string parameters. */ + queryParams?: Record; + /** Additional headers for this request. */ + headers?: Record; +} + +/** Response from a custom integration call. */ +interface CustomIntegrationCallResponse { + /** Whether the external API returned a 2xx status code. */ + success: boolean; + /** The HTTP status code from the external API. */ + status_code: number; + /** The response data from the external API. */ + data: any; +} + +/** Module for calling custom workspace-level API integrations. */ +interface CustomIntegrationsModule { + /** + * Call a custom integration endpoint. + * @param slug - The integration's unique identifier. + * @param operationId - The operation ID from the OpenAPI spec. + * @param params - Optional payload, pathParams, queryParams, headers. + */ + call(slug: string, operationId: string, params?: CustomIntegrationCallParams): Promise; +} +``` + +### IntegrationsModule + +```typescript +/** Integrations module for calling integration endpoints. */ +type IntegrationsModule = { + /** Core package with built-in integrations. */ + Core: CoreIntegrations; + /** Custom integrations module. */ + custom: CustomIntegrationsModule; + /** Additional integration packages (dynamic). */ + [packageName: string]: any; +}; +``` diff --git a/evals/fixtures/agent-chat-component/skills/base44-sdk/references/users.md b/evals/fixtures/agent-chat-component/skills/base44-sdk/references/users.md new file mode 100644 index 0000000..4205b06 --- /dev/null +++ b/evals/fixtures/agent-chat-component/skills/base44-sdk/references/users.md @@ -0,0 +1,82 @@ +# Users Module + +Invite users to the app via `base44.users`. + +## Contents +- [Methods](#methods) +- [Examples](#examples) (Invite User) +- [Roles](#roles) +- [Notes](#notes) + +## Methods + +| Method | Signature | Description | +|--------|-----------|-------------| +| `inviteUser(user_email, role)` | `Promise` | Invite a user to the app | + +## Examples + +### Invite User + +```javascript +// Invite a user with "user" role +await base44.users.inviteUser("newuser@example.com", "user"); + +// Invite an admin +await base44.users.inviteUser("admin@example.com", "admin"); +``` + +### Invite Multiple Users + +```javascript +const usersToInvite = [ + { email: "user1@example.com", role: "user" }, + { email: "user2@example.com", role: "user" }, + { email: "manager@example.com", role: "admin" } +]; + +for (const user of usersToInvite) { + await base44.users.inviteUser(user.email, user.role); + console.log(`Invited ${user.email} as ${user.role}`); +} +``` + +## Roles + +The `role` parameter must be one of: + +| Role | Description | +|------|-------------| +| `"user"` | Standard user with default permissions | +| `"admin"` | Administrator with elevated permissions | + +**Note:** Only `"user"` and `"admin"` are valid role values. An error will be thrown if you pass any other value. + +## Notes + +- **Email invitation**: The invited user receives an email with a link to join the app +- **Duplicate handling**: Inviting an existing user will re-send the invitation +- **Also available in auth**: `base44.auth.inviteUser()` provides the same functionality +- **Role validation**: Only `"user"` or `"admin"` are accepted + +```javascript +// These are equivalent: +await base44.users.inviteUser("newuser@example.com", "user"); +await base44.auth.inviteUser("newuser@example.com", "user"); +``` + +## Type Definitions + +```typescript +/** Users module for inviting users to the app. */ +interface UsersModule { + /** + * Invite a user to the application. + * @param user_email - User's email address. + * @param role - User's role ('user' or 'admin'). + * @returns Promise resolving when the invitation is sent. + * @throws Error if role is not 'user' or 'admin'. + */ + inviteUser(user_email: string, role: "user" | "admin"): Promise; +} +``` diff --git a/evals/fixtures/agent-with-function/AGENTS.md b/evals/fixtures/agent-with-function/AGENTS.md new file mode 100644 index 0000000..35ba3e2 --- /dev/null +++ b/evals/fixtures/agent-with-function/AGENTS.md @@ -0,0 +1,15 @@ +# Agent with Function Fixture + +This fixture tests creating an AI agent that has access to a backend function. + +## Project Context + +This is an e-commerce app that needs an order assistant agent capable of: +- Reading order information +- Canceling orders via a backend function + +## Expected Outcome + +Create both: +1. An agent config at `base44/agents/order_assistant.jsonc` with both entity and function tool access +2. A backend function at `base44/functions/cancel-order/` that handles order cancellation diff --git a/evals/fixtures/agent-with-function/eval.json b/evals/fixtures/agent-with-function/eval.json new file mode 100644 index 0000000..2789c35 --- /dev/null +++ b/evals/fixtures/agent-with-function/eval.json @@ -0,0 +1,82 @@ +{ + "$schema": "../../eval.schema.json", + "name": "agent-with-function", + "description": "Test creating an agent that can call a backend function", + "prompt": "Create an order assistant agent called 'order_assistant' that can help users track and cancel their orders. The agent should have: 1) Read access to the Order entity, 2) Access to a backend function called 'cancel_order' that cancels an order. Also create the cancel_order backend function that accepts order_id and cancellation_reason.", + "expectedSkills": ["base44-cli"], + "checks": [ + { + "type": "file-exists", + "description": "Agent config created", + "filePath": "base44/agents/order_assistant.jsonc" + }, + { + "type": "valid-json", + "description": "Agent config is valid JSON", + "filePath": "base44/agents/order_assistant.jsonc" + }, + { + "type": "file-content", + "description": "Has entity tool config", + "filePath": "base44/agents/order_assistant.jsonc", + "pattern": "\"entity_name\"\\s*:\\s*\"Order\"" + }, + { + "type": "file-content", + "description": "Has function tool config", + "filePath": "base44/agents/order_assistant.jsonc", + "pattern": "\"function_name\"\\s*:\\s*\"cancel_order\"" + }, + { + "type": "file-exists", + "description": "Function config created", + "filePath": "base44/functions/cancel-order/function.jsonc" + }, + { + "type": "file-exists", + "description": "Function entry point created", + "filePath": "base44/functions/cancel-order/index.ts" + }, + { + "type": "file-content", + "description": "Function uses Deno.serve", + "filePath": "base44/functions/cancel-order/index.ts", + "pattern": "Deno\\.serve" + }, + { + "type": "contains", + "description": "Mentions agents", + "value": "agent" + }, + { + "type": "agent-config", + "description": "Agent config validates against Base44 agent schema", + "filePath": "base44/agents/order_assistant.jsonc", + "target": "file" + }, + { + "type": "function-def", + "description": "Function config validates against Base44 function schema", + "filePath": "base44/functions/cancel-order/function.jsonc", + "target": "file" + }, + { + "type": "file-content", + "description": "Function imports createClientFromRequest from npm:@base44/sdk", + "filePath": "base44/functions/cancel-order/index.ts", + "pattern": "createClientFromRequest.*from.*@base44/sdk|npm:@base44/sdk" + }, + { + "type": "file-content", + "description": "Function parses request body", + "filePath": "base44/functions/cancel-order/index.ts", + "pattern": "await\\s+req\\.json\\(\\)" + }, + { + "type": "file-content", + "description": "Function returns Response.json()", + "filePath": "base44/functions/cancel-order/index.ts", + "pattern": "Response\\.json\\(" + } + ] +} diff --git a/evals/fixtures/agent-with-function/project/.claude/settings.json b/evals/fixtures/agent-with-function/project/.claude/settings.json new file mode 100644 index 0000000..ab60505 --- /dev/null +++ b/evals/fixtures/agent-with-function/project/.claude/settings.json @@ -0,0 +1,6 @@ +{ + "permissions": { + "allow": ["Read", "Write", "Edit", "Bash", "Glob", "Grep"] + }, + "skills": ["./skills"] +} diff --git a/evals/fixtures/agent-with-function/project/AGENTS.md b/evals/fixtures/agent-with-function/project/AGENTS.md new file mode 100644 index 0000000..6c0f2be --- /dev/null +++ b/evals/fixtures/agent-with-function/project/AGENTS.md @@ -0,0 +1,25 @@ +IMPORTANT: Prefer retrieval-led reasoning over pre-training-led reasoning. Base44 is NOT Firebase, Supabase, or any other SDK you know. Every API name below is the ONLY correct form. Always verify against this index and the referenced docs. + +[Base44 SDK Docs Index]|root: ./skills/base44-sdk/references +|entities.md: CRUD - create(data), list(), filter(query), get(id), update(id,data), delete(id), subscribe(cb) +|auth.md: Auth - loginViaEmailPassword(), loginWithProvider('google'), me(), register(), logout() +|base44-agents.md: Agents - createConversation({agent_name}), addMessage(conv,{role,content}), subscribeToConversation(id,cb) +|functions.md: Functions - invoke(name, data) +|integrations.md: Core - InvokeLLM({prompt}), SendEmail({to,subject,body}), UploadFile({file}) +|client.md: Client - createClient({appId}), import from "@base44/sdk" +|QUICK_REFERENCE.md: All method signatures + +[Base44 CLI Docs Index]|root: ./skills/base44-cli/references +|entities-create.md: Entity schema - {name:"PascalCase", type:"object", properties:{}, required:[]} +|functions-create.md: Function - function.jsonc {name,entry} + index.ts with Deno.serve + createClientFromRequest +|create.md: Project creation - npx base44 create name -p . +|deploy.md: Deploy - npx base44 deploy -y +|agents-push.md: Agent config - {name:"snake_case", description, instructions, tool_configs:[{entity_name,allowed_operations}|{function_name,description}]} + +[Common Mistakes - NEVER use these hallucinated names] +|SDK: Do NOT use find() → use filter(), findOne() → get(), insert() → create(), remove() → delete(), onChange() → subscribe() +|SDK: Do NOT use signInWithGoogle() → use loginWithProvider('google'), functions.call() → functions.invoke() +|SDK: Do NOT use ai.generate() → use integrations.Core.InvokeLLM({prompt}) +|CLI: Entity names must be PascalCase alphanumeric. File names must be kebab-case.jsonc +|CLI: Agent names must be lowercase with underscores only. Function entry field is "entry" not "entrypoint" +|CLI: Backend functions import from "npm:@base44/sdk" (Deno requires npm: prefix) diff --git a/evals/fixtures/agent-with-function/project/CLAUDE.md b/evals/fixtures/agent-with-function/project/CLAUDE.md new file mode 100644 index 0000000..724ce4d --- /dev/null +++ b/evals/fixtures/agent-with-function/project/CLAUDE.md @@ -0,0 +1,7 @@ +# Base44 Development Guidelines + +When working with Base44 projects, first explore the existing project structure to understand what already exists. Look at existing code, config files, and patterns in use. + +Then consult the AGENTS.md index in the project root for correct API patterns. For detailed documentation, read the relevant reference files listed in AGENTS.md. + +IMPORTANT: Prefer retrieval-led reasoning over pre-training-led reasoning. Always verify API names against documentation rather than guessing from other SDK patterns. diff --git a/evals/fixtures/agent-with-function/project/base44/config.jsonc b/evals/fixtures/agent-with-function/project/base44/config.jsonc new file mode 100644 index 0000000..5e061a3 --- /dev/null +++ b/evals/fixtures/agent-with-function/project/base44/config.jsonc @@ -0,0 +1,4 @@ +{ + "name": "Order Management App", + "description": "E-commerce order management with AI assistant" +} diff --git a/evals/fixtures/agent-with-function/project/package.json b/evals/fixtures/agent-with-function/project/package.json new file mode 100644 index 0000000..29d668d --- /dev/null +++ b/evals/fixtures/agent-with-function/project/package.json @@ -0,0 +1,9 @@ +{ + "name": "order-management", + "version": "1.0.0", + "type": "module", + "dependencies": { + "@base44/sdk": "latest", + "react": "^18.2.0" + } +} diff --git a/evals/fixtures/agent-with-function/skills/base44-cli/SKILL.md b/evals/fixtures/agent-with-function/skills/base44-cli/SKILL.md new file mode 100644 index 0000000..3c33f05 --- /dev/null +++ b/evals/fixtures/agent-with-function/skills/base44-cli/SKILL.md @@ -0,0 +1,384 @@ +--- +name: base44-cli +description: "The base44 CLI is used for EVERYTHING related to base44 projects: resource configuration (entities, backend functions, ai agents), initialization and actions (resource creation, deployment). This skill is the place for learning about how to configure resources. When you plan or implement a feature, you must learn this skill" +--- + +# Base44 CLI + +Create and manage Base44 apps (projects) using the Base44 CLI tool. + +## ⚡ IMMEDIATE ACTION REQUIRED - Read This First + +This skill activates on ANY mention of "base44" or when a `base44/` folder exists. **DO NOT read documentation files or search the web before acting.** + +**Your first action MUST be:** +1. Check if `base44/config.jsonc` exists in the current directory +2. If **NO** (new project scenario): + - This skill (base44-cli) handles the request + - Guide user through project initialization + - Do NOT activate base44-sdk yet +3. If **YES** (existing project scenario): + - Transfer to base44-sdk skill for implementation + - This skill only handles CLI commands (login, deploy, entities push) + +## Critical: Local Installation Only + +NEVER call `base44` directly. The CLI is installed locally as a dev dependency and must be accessed via a package manager: + +- `npx base44 ` (npm - recommended) +- `yarn base44 ` (yarn) +- `pnpm base44 ` (pnpm) + +WRONG: `base44 login` +RIGHT: `npx base44 login` + +## MANDATORY: Authentication Check at Session Start + +**CRITICAL**: At the very start of every AI session when this skill is activated, you MUST: + +1. **Check authentication status** by running: + ```bash + npx base44 whoami + ``` + +2. **If the user is logged in** (command succeeds and shows an email): + - Continue with the requested task + +3. **If the user is NOT logged in** (command fails or shows an error): + - **STOP immediately** + - **DO NOT proceed** with any CLI operations + - **Ask the user to login manually** by running: + ```bash + npx base44 login + ``` + - Wait for the user to confirm they have logged in before continuing + +**This check is mandatory and must happen before executing any other Base44 CLI commands.** + +## Overview + +The Base44 CLI provides command-line tools for authentication, creating projects, managing entities, and deploying Base44 applications. It is framework-agnostic and works with popular frontend frameworks like Vite, Next.js, and Create React App, Svelte, Vue, and more. + +## When to Use This Skill vs base44-sdk + +**Use base44-cli when:** +- Creating a **NEW** Base44 project from scratch +- Initializing a project in an empty directory +- Directory is missing `base44/config.jsonc` +- User mentions: "create a new project", "initialize project", "setup a project", "start a new Base44 app" +- Deploying, pushing entities, or authenticating via CLI +- Working with CLI commands (`npx base44 ...`) + +**Use base44-sdk when:** +- Building features in an **EXISTING** Base44 project +- `base44/config.jsonc` already exists +- Writing JavaScript/TypeScript code using Base44 SDK +- Implementing functionality, components, or features +- User mentions: "implement", "build a feature", "add functionality", "write code" + +**Skill Dependencies:** +- `base44-cli` is a **prerequisite** for `base44-sdk` in new projects +- If user wants to "create an app" and no Base44 project exists, use `base44-cli` first +- `base44-sdk` assumes a Base44 project is already initialized + +**State Check Logic:** +Before selecting a skill, check: +- IF (user mentions "create/build app" OR "make a project"): + - IF (directory is empty OR no `base44/config.jsonc` exists): + → Use **base44-cli** (project initialization needed) + - ELSE: + → Use **base44-sdk** (project exists, build features) + +## Project Structure + +A Base44 project combines a standard frontend project with a `base44/` configuration folder: + +``` +my-app/ +├── base44/ # Base44 configuration (created by CLI) +│ ├── config.jsonc # Project settings, site config +│ ├── entities/ # Entity schema definitions +│ │ ├── task.jsonc +│ │ └── board.jsonc +│ ├── functions/ # Backend functions (optional) +│ │ └── my-function/ +│ │ ├── function.jsonc +│ │ └── index.ts +│ └── agents/ # Agent configurations (optional) +│ └── support_agent.jsonc +├── src/ # Frontend source code +│ ├── api/ +│ │ └── base44Client.js # Base44 SDK client +│ ├── pages/ +│ ├── components/ +│ └── main.jsx +├── index.html # SPA entry point +├── package.json +└── vite.config.js # Or your framework's config +``` + +**Key files:** +- `base44/config.jsonc` - Project name, description, site build settings +- `base44/entities/*.jsonc` - Data model schemas (see Entity Schema section) +- `base44/agents/*.jsonc` - Agent configurations (optional) +- `src/api/base44Client.js` - Pre-configured SDK client for frontend use + +**config.jsonc example:** +```jsonc +{ + "name": "My App", // Required: project name + "description": "App description", // Optional: project description + "entitiesDir": "./entities", // Optional: default "entities" + "functionsDir": "./functions", // Optional: default "functions" + "agentsDir": "./agents", // Optional: default "agents" + "site": { // Optional: site deployment config + "installCommand": "npm install", // Optional: install dependencies + "buildCommand": "npm run build", // Optional: build command + "serveCommand": "npm run dev", // Optional: local dev server + "outputDirectory": "./dist" // Optional: build output directory + } +} +``` + +**Config properties:** + +| Property | Description | Default | +|----------|-------------|---------| +| `name` | Project name (required) | - | +| `description` | Project description | - | +| `entitiesDir` | Directory for entity schemas | `"entities"` | +| `functionsDir` | Directory for backend functions | `"functions"` | +| `agentsDir` | Directory for agent configs | `"agents"` | +| `site.installCommand` | Command to install dependencies | - | +| `site.buildCommand` | Command to build the project | - | +| `site.serveCommand` | Command to run dev server | - | +| `site.outputDirectory` | Build output directory for deployment | - | + +## Installation + +Install the Base44 CLI as a dev dependency in your project: + +```bash +npm install --save-dev base44 +``` + +**Important:** Never assume or hardcode the `base44` package version. Always install without a version specifier to get the latest version. + +Then run commands using `npx`: + +```bash +npx base44 +``` + +**Note:** All commands in this documentation use `npx base44`. You can also use `yarn base44`, or `pnpm base44` if preferred. + +## Available Commands + +### Authentication + +| Command | Description | Reference | +| --------------- | ----------------------------------------------- | ------------------------------------------- | +| `base44 login` | Authenticate with Base44 using device code flow | [auth-login.md](references/auth-login.md) | +| `base44 logout` | Logout from current device | [auth-logout.md](references/auth-logout.md) | +| `base44 whoami` | Display current authenticated user | [auth-whoami.md](references/auth-whoami.md) | + +### Project Management + +| Command | Description | Reference | +|---------|-------------|-----------| +| `base44 create` | Create a new Base44 project from a template | [create.md](references/create.md) ⚠️ **MUST READ** | +| `base44 link` | Link an existing local project to Base44 | [link.md](references/link.md) | +| `base44 dashboard` | Open the app dashboard in your browser | [dashboard.md](references/dashboard.md) | + +### Deployment + +| Command | Description | Reference | +|---------|-------------|-----------| +| `base44 deploy` | Deploy all resources (entities, functions, site) | [deploy.md](references/deploy.md) | + +### Entity Management + +| Action / Command | Description | Reference | +| ---------------------- | ------------------------------------------- | --------------------------------------------------- | +| Create Entities | Define entities in `base44/entities` folder | [entities-create.md](references/entities-create.md) | +| `base44 entities push` | Push local entities to Base44 | [entities-push.md](references/entities-push.md) | + +#### Entity Schema (Quick Reference) + +ALWAYS follow this exact structure when creating entity files: + +**File naming:** `base44/entities/{kebab-case-name}.jsonc` (e.g., `team-member.jsonc` for `TeamMember`) + +**Schema template:** +```jsonc +{ + "name": "EntityName", + "type": "object", + "properties": { + "field_name": { + "type": "string", + "description": "Field description" + } + }, + "required": ["field_name"] +} +``` + +**Field types:** `string`, `number`, `integer`, `boolean`, `array`, `object`, `binary` +**String formats:** `date`, `date-time`, `time`, `email`, `uri`, `hostname`, `ipv4`, `ipv6`, `uuid`, `file`, `regex`, `richtext` +**For enums:** Add `"enum": ["value1", "value2"]` and optionally `"default": "value1"` +**Entity names:** Must be alphanumeric only (pattern: `/^[a-zA-Z0-9]+$/`) + +For complete documentation, see [entities-create.md](references/entities-create.md). + +### Function Management + +| Action / Command | Description | Reference | +| ------------------------- | --------------------------------------------- | ------------------------------------------------------- | +| Create Functions | Define functions in `base44/functions` folder | [functions-create.md](references/functions-create.md) | +| `base44 functions deploy` | Deploy local functions to Base44 | [functions-deploy.md](references/functions-deploy.md) | + +### Agent Management + +Agents are conversational AI assistants that can interact with users, access your app's entities, and call backend functions. Use these commands to manage agent configurations. + +| Action / Command | Description | Reference | +| ----------------------- | --------------------------------------- | ----------------------------------------------- | +| Create Agents | Define agents in `base44/agents` folder | See Agent Schema below | +| `base44 agents pull` | Pull remote agents to local files | [agents-pull.md](references/agents-pull.md) | +| `base44 agents push` | Push local agents to Base44 | [agents-push.md](references/agents-push.md) | + +**Note:** Agent commands perform full synchronization - pushing replaces all remote agents with local ones, and pulling replaces all local agents with remote ones. + +#### Agent Schema (Quick Reference) + +**File naming:** `base44/agents/{agent_name}.jsonc` (e.g., `support_agent.jsonc`) + +**Schema template:** +```jsonc +{ + "name": "agent_name", + "description": "Brief description of what this agent does", + "instructions": "Detailed instructions for the agent's behavior", + "tool_configs": [ + // Entity tool - gives agent access to entity operations + { "entity_name": "tasks", "allowed_operations": ["read", "create", "update", "delete"] }, + // Backend function tool - gives agent access to a function + { "function_name": "send_email", "description": "Send an email notification" } + ], + "whatsapp_greeting": "Hello! How can I help you today?" +} +``` + +**Naming rules:** +- Agent names must match pattern: `/^[a-z0-9_]+$/` (lowercase alphanumeric with underscores, 1-100 chars) +- Valid: `support_agent`, `order_bot` +- Invalid: `Support-Agent`, `OrderBot` + +**Required fields:** `name`, `description`, `instructions` +**Optional fields:** `tool_configs` (defaults to `[]`), `whatsapp_greeting` + +**Tool config types:** +- **Entity tools**: `entity_name` + `allowed_operations` (array of: `read`, `create`, `update`, `delete`) +- **Backend function tools**: `function_name` + `description` + +### Site Deployment + +| Command | Description | Reference | +| -------------------- | ----------------------------------------- | ------------------------------------------- | +| `base44 site deploy` | Deploy built site files to Base44 hosting | [site-deploy.md](references/site-deploy.md) | + +**SPA only**: Base44 hosting supports Single Page Applications with a single `index.html` entry point. All routes are served from `index.html` (client-side routing). + +## Quick Start + +1. Install the CLI in your project: + ```bash + npm install --save-dev base44 + ``` + +2. Authenticate with Base44: + ```bash + npx base44 login + ``` + +3. Create a new project (ALWAYS provide name and `--path` flag): + ```bash + npx base44 create my-app -p . + ``` + +4. Build and deploy everything: + ```bash + npm run build + npx base44 deploy -y + ``` + +Or deploy individual resources: +- `npx base44 entities push` - Push entities only +- `npx base44 functions deploy` - Deploy functions only +- `npx base44 agents push` - Push agents only +- `npx base44 site deploy -y` - Deploy site only + +## Common Workflows + +### Creating a New Project + +**⚠️ MANDATORY: Before running `base44 create`, you MUST read [create.md](references/create.md) for:** +- **Template selection** - Choose the correct template (`backend-and-client` vs `backend-only`) +- **Correct workflow** - Different templates require different setup steps +- **Common pitfalls** - Avoid folder creation errors that cause failures + +Failure to follow the create.md instructions will result in broken project scaffolding. + +### Linking an Existing Project +```bash +# If you have base44/config.jsonc but no .app.jsonc +npx base44 link --create --name my-app +``` + +### Deploying All Changes +```bash +# Build your project first +npm run build + +# Deploy everything (entities, functions, and site) +npx base44 deploy -y +``` + +### Deploying Individual Resources +```bash +# Push only entities +npx base44 entities push + +# Deploy only functions +npx base44 functions deploy + +# Push only agents +npx base44 agents push + +# Deploy only site +npx base44 site deploy -y +``` + +### Opening the Dashboard +```bash +# Open app dashboard in browser +npx base44 dashboard +``` + +## Authentication + +Most commands require authentication. If you're not logged in, the CLI will automatically prompt you to login. Your session is stored locally and persists across CLI sessions. + +## Troubleshooting + +| Error | Solution | +| --------------------------- | ----------------------------------------------------------------------------------- | +| Not authenticated | Run `npx base44 login` first | +| No entities found | Ensure entities exist in `base44/entities/` directory | +| Entity not recognized | Ensure file uses kebab-case naming (e.g., `team-member.jsonc` not `TeamMember.jsonc`) | +| No functions found | Ensure functions exist in `base44/functions/` with valid `function.jsonc` configs | +| No agents found | Ensure agents exist in `base44/agents/` directory with valid `.jsonc` configs | +| Invalid agent name | Agent names must be lowercase alphanumeric with underscores only | +| No site configuration found | Check that `site.outputDirectory` is configured in project config | +| Site deployment fails | Ensure you ran `npm run build` first and the build succeeded | diff --git a/evals/fixtures/agent-with-function/skills/base44-cli/references/agents-pull.md b/evals/fixtures/agent-with-function/skills/base44-cli/references/agents-pull.md new file mode 100644 index 0000000..6a700f0 --- /dev/null +++ b/evals/fixtures/agent-with-function/skills/base44-cli/references/agents-pull.md @@ -0,0 +1,73 @@ +# base44 agents pull + +Pull AI agent configurations from Base44 to local files. Agents are conversational AI assistants that can interact with users, access your app's entities, and call backend functions. + +## Syntax + +```bash +npx base44 agents pull +``` + +## Authentication + +**Required**: Yes. If not authenticated, you'll be prompted to login first. + +## What It Does + +1. Fetches all agents from Base44 +2. Writes agent files to the `base44/agents/` directory +3. Deletes local agent files that don't exist remotely +4. Reports written and deleted agents + +## Prerequisites + +- Must be run from a Base44 project directory +- Project must be linked to a Base44 app + +## Output + +```bash +$ npx base44 agents pull + +Fetching agents from Base44... +✓ Agents fetched successfully + +Writing agent files... +✓ Agent files written successfully + +Written: support_agent, order_bot +Deleted: old_agent + +Pulled 2 agents to base44/agents +``` + +## Agent Synchronization + +The pull operation synchronizes remote agents to your local files: + +- **Written**: Agent files created or updated from remote +- **Deleted**: Local agent files removed (didn't exist remotely) + +**Warning**: This operation replaces all local agent configurations with remote versions. Any local changes not pushed to Base44 will be overwritten. + +## Error Handling + +If no agents exist on Base44: +```bash +$ npx base44 agents pull +No agents found on Base44 +``` + +## Use Cases + +- Sync agent configurations to a new development machine +- Get the latest agent configurations from your team +- Restore local agent files after accidental deletion +- Start working on an existing project with agents + +## Notes + +- This command syncs agent configurations, not conversation data +- Agent files are stored as `.jsonc` in the `base44/agents/` directory +- The directory location is configurable via `agentsDir` in `config.jsonc` +- Use `base44 agents push` to upload local changes to Base44 diff --git a/evals/fixtures/agent-with-function/skills/base44-cli/references/agents-push.md b/evals/fixtures/agent-with-function/skills/base44-cli/references/agents-push.md new file mode 100644 index 0000000..fa2795b --- /dev/null +++ b/evals/fixtures/agent-with-function/skills/base44-cli/references/agents-push.md @@ -0,0 +1,156 @@ +# base44 agents push + +Push local AI agent configurations to Base44. Agents are conversational AI assistants that can interact with users, access your app's entities, and call backend functions. + +## Syntax + +```bash +npx base44 agents push +``` + +## Authentication + +**Required**: Yes. If not authenticated, you'll be prompted to login first. + +## What It Does + +1. Reads all agent files from the `base44/agents/` directory +2. Validates agent configurations +3. Displays the count of agents to be pushed +4. Uploads agents to the Base44 backend +5. Reports the results: created, updated, and deleted agents + +## Prerequisites + +- Must be run from a Base44 project directory +- Project must have agent definitions in the `base44/agents/` folder + +## Output + +```bash +$ npx base44 agents push + +Found 2 agents to push +Pushing agents to Base44... + +Created: support_agent +Updated: order_bot +Deleted: old_agent + +✓ Agents pushed to Base44 +``` + +## Agent Synchronization + +The push operation synchronizes your local agents with Base44: + +- **Created**: New agents that didn't exist in Base44 +- **Updated**: Existing agents with modified configuration +- **Deleted**: Agents that were removed from your local configuration + +**Warning**: This is a full sync operation. Agents removed locally will be deleted from Base44. + +## Error Handling + +If no agents are found in your project: +```bash +$ npx base44 agents push +No local agents found - this will delete all remote agents +``` + +If an agent has an invalid name: +```bash +$ npx base44 agents push +Error: Agent name must be lowercase alphanumeric with underscores +``` + +## Agent Configuration Schema + +Each agent file should be a `.jsonc` file in `base44/agents/` with this structure: + +```jsonc +{ + "name": "agent_name", // Required: lowercase alphanumeric with underscores, 1-100 chars + "description": "Brief description of what this agent does", // Required: min 1 char + "instructions": "Detailed instructions for the agent's behavior", // Required: min 1 char + "tool_configs": [ // Optional: defaults to [] + // Entity tool - gives agent access to entity operations + { "entity_name": "Task", "allowed_operations": ["read", "create", "update", "delete"] }, + // Backend function tool - gives agent access to a function + { "function_name": "send_email", "description": "Send an email notification" } + ], + "whatsapp_greeting": "Hello! How can I help you today?" // Optional +} +``` + +**Naming rules:** +- **Agent names** must match pattern: `/^[a-z0-9_]+$/` (lowercase alphanumeric with underscores only, 1-100 characters) + - Valid: `support_agent`, `order_bot`, `task_helper` + - Invalid: `Support-Agent`, `OrderBot`, `task helper` +- **Agent file names** must use underscores (matching the agent name) + - Valid: `support_agent.jsonc`, `order_bot.jsonc` + - Invalid: `support-agent.jsonc` (hyphens not allowed) +- **Entity names in `tool_configs`** must use PascalCase (matching the entity's `name` field) + - Valid: `"entity_name": "Task"`, `"entity_name": "TeamMember"` + - Invalid: `"entity_name": "task"`, `"entity_name": "team_member"` + +**Required fields:** +- `name`: Required, must follow naming rules above +- `description`: Required, minimum 1 character +- `instructions`: Required, minimum 1 character +- `tool_configs`: Optional, defaults to empty array +- `whatsapp_greeting`: Optional + +### Common Mistake: Wrong tool_configs Format + +**WRONG** - Do NOT use `tools` with `type` and `entity`: +```jsonc +{ + "name": "my_agent", + "tools": [ // ❌ WRONG + { "type": "entity_query", "entity": "Task" } + ] +} +``` + +**CORRECT** - Use `tool_configs` with `entity_name` and `allowed_operations`: +```jsonc +{ + "name": "my_agent", + "tool_configs": [ // ✅ CORRECT + { "entity_name": "Task", "allowed_operations": ["read"] } + ] +} +``` + +### Best Practices for Agent Instructions + +When giving agents access to entities, be explicit in the instructions about using the tools: + +```jsonc +{ + "name": "support_agent", + "instructions": "You are a helpful support agent.\n\nIMPORTANT: You have access to customer data through entity tools. When users ask about their orders or account:\n1. ALWAYS use the Order entity tool to query their order history\n2. Use the Customer entity tool to look up account details\n3. Analyze the data and provide personalized responses\n\nAlways query the relevant entities first before answering questions about user data.", + "tool_configs": [ + { "entity_name": "Order", "allowed_operations": ["read"] }, + { "entity_name": "Customer", "allowed_operations": ["read"] } + ] +} +``` + +Without explicit instructions to use the entity tools, the agent may not proactively query user data when asked. + +## Use Cases + +- After defining new agents in your project +- When modifying existing agent configurations +- To sync agent changes before testing +- As part of your development workflow when agent behavior changes + +## Notes + +- This command syncs the agent configuration, not conversation data +- Changes are applied to your Base44 project immediately +- Make sure to test agent changes in a development environment first +- Agent definitions are located in the `base44/agents/` directory +- Use `base44 agents pull` to download agents from Base44 diff --git a/evals/fixtures/agent-with-function/skills/base44-cli/references/auth-login.md b/evals/fixtures/agent-with-function/skills/base44-cli/references/auth-login.md new file mode 100644 index 0000000..adebd27 --- /dev/null +++ b/evals/fixtures/agent-with-function/skills/base44-cli/references/auth-login.md @@ -0,0 +1,54 @@ +# base44 login + +Authenticate with Base44 using device code flow. + +## Syntax + +```bash +npx base44 login +``` + +## Authentication + +**Required**: No (this is the login command itself) + +## How It Works + +The login command uses OAuth 2.0 device code flow for authentication: + +1. Generates a device code for authentication +2. Displays a verification code and verification URI +3. Directs you to visit the URI and enter the code +4. Polls for authentication completion (up to device code expiration) +5. Retrieves access and refresh tokens upon successful authentication +6. Fetches and displays your user information +7. Saves authentication data locally with expiration timestamp + +## Interactive Flow + +```bash +$ npx base44 login + +Please visit: https://auth.base44.com/device +Enter code: ABCD-EFGH + +Waiting for authentication... +✓ Successfully authenticated! + +Logged in as: user@example.com +``` + +## Session Management + +- Authentication tokens are stored locally on your device +- Tokens include expiration timestamps +- The session persists across CLI sessions +- Other commands will automatically use your stored credentials +- Use `npx base44 logout` to clear your session +- Use `npx base44 whoami` to check your current authentication status + +## Notes + +- You only need to login once per device +- If your session expires, you'll be prompted to login again when running authenticated commands +- The CLI automatically prompts for login when you run commands that require authentication diff --git a/evals/fixtures/agent-with-function/skills/base44-cli/references/auth-logout.md b/evals/fixtures/agent-with-function/skills/base44-cli/references/auth-logout.md new file mode 100644 index 0000000..33c2ddf --- /dev/null +++ b/evals/fixtures/agent-with-function/skills/base44-cli/references/auth-logout.md @@ -0,0 +1,32 @@ +# base44 logout + +Logout from current device and clear stored authentication data. + +## Syntax + +```bash +npx base44 logout +``` + +## Authentication + +**Required**: No + +## What It Does + +- Deletes stored authentication data from your device +- Clears your local session +- Removes access and refresh tokens + +## Output + +```bash +$ npx base44 logout +Logged out successfully +``` + +## Notes + +- You can logout even if you're not currently logged in (no error) +- After logout, you'll need to run `npx base44 login` again to use authenticated commands +- This only affects the current device; your Base44 account remains active diff --git a/evals/fixtures/agent-with-function/skills/base44-cli/references/auth-whoami.md b/evals/fixtures/agent-with-function/skills/base44-cli/references/auth-whoami.md new file mode 100644 index 0000000..abe450c --- /dev/null +++ b/evals/fixtures/agent-with-function/skills/base44-cli/references/auth-whoami.md @@ -0,0 +1,37 @@ +# base44 whoami + +Display the currently authenticated user. + +## Syntax + +```bash +npx base44 whoami +``` + +## Authentication + +**Required**: Yes. If not authenticated, you'll be prompted to login first. + +## What It Does + +- Reads stored authentication data +- Displays the email of the currently logged-in user + +## Output + +```bash +$ npx base44 whoami +Logged in as: user@example.com +``` + +## Use Cases + +- Verify you're logged in before running other commands +- Check which account you're currently using +- Confirm authentication is working properly +- Useful in scripts or automation to verify credentials + +## Notes + +- If you're not logged in, the command will prompt you to authenticate first +- The email displayed matches your Base44 account email diff --git a/evals/fixtures/agent-with-function/skills/base44-cli/references/create.md b/evals/fixtures/agent-with-function/skills/base44-cli/references/create.md new file mode 100644 index 0000000..7580645 --- /dev/null +++ b/evals/fixtures/agent-with-function/skills/base44-cli/references/create.md @@ -0,0 +1,111 @@ +# base44 create + +Creates a new Base44 project from a template. This command is framework-agnostic and can either scaffold a complete project or add Base44 configuration to an existing project. + +## Critical: Non-Interactive Mode Required + +ALWAYS provide both the project name AND `--path` flag. Without both, the command opens an interactive TUI which agents cannot use properly. + +WRONG: `npx base44 create` +WRONG: `npx base44 create my-app` +RIGHT: `npx base44 create my-app -p ./my-app` + +## Syntax + +```bash +npx base44 create [name] --path [options] +``` + +## Arguments & Options + +| Argument/Option | Description | Required | +|--------|-------------|----------| +| `name` | Project name (positional argument) | Yes* | +| `-p, --path ` | Path where to create the project | Yes* | +| `-t, --template ` | Template ID (see templates below) | No | +| `--deploy` | Build and deploy the site (includes pushing entities) | No | +| `--no-skills` | Skip AI agent skills installation (skills are added by default) | No | + +*Required for non-interactive mode. Both `name` and `--path` must be provided together. + +## Template Selection (CRITICAL - Choose Appropriately) + +**You MUST select the most appropriate template based on user requirements:** + +| Template ID | When to Use | Example Scenarios | +|-------------|-------------|-------------------| +| `backend-and-client` | Creating a NEW full-stack web app from scratch | "Create a task app", "Build me a dashboard", "Make a SaaS app" | +| `backend-only` | Adding Base44 to an EXISTING project OR using a different framework (Next.js, Vue, Svelte, etc.) | "Add Base44 to my project", "I want to use Next.js", "I already have a frontend" | + +**Default Choice:** When the user asks to "create an app" or "build a project" without specifying a particular framework, use `backend-and-client` to provide a complete, production-ready application with Vite + React + Tailwind. + +## The `--path` Flag + +- **For `backend-and-client` template (new projects):** Use a new subfolder path + ```bash + npx base44 create my-app -p ./my-app -t backend-and-client + ``` +- **For `backend-only` template (existing projects):** Use `-p .` in the current directory + ```bash + npx base44 create my-app -p . + ``` + +## Workflow: Using `backend-only` with External Frameworks + +**CRITICAL: The project folder MUST exist BEFORE running `base44 create` with `backend-only`** + +The `backend-only` template only adds Base44 configuration files - it does NOT create a frontend. If you need a frontend with a specific framework: + +```bash +# Step 1: Initialize the frontend project FIRST +npm create vite@latest my-app -- --template react # or vue, svelte, etc. +# OR: npx create-next-app@latest my-app +# OR: any other framework's init command + +# Step 2: Navigate into the created folder +cd my-app + +# Step 3: Install Base44 CLI +npm install --save-dev base44 + +# Step 4: Add Base44 configuration +npx base44 create my-app -p . +``` + +**WARNING:** Do NOT: +- Create an empty folder manually, then try to run `npx create vite` inside it (will fail - folder exists) +- Run `base44 create` with `backend-only` expecting it to create a frontend (it won't) + +**DO:** +- Run the external framework's init command FIRST (it creates its own folder) +- Then run `base44 create` inside that folder with `-p .` + +## Examples + +```bash +# RECOMMENDED: Create full-stack project (for new apps) +npx base44 create my-app -p ./my-app -t backend-and-client + +# Create full-stack and deploy in one step +npx base44 create my-app -p ./my-app -t backend-and-client --deploy + +# Add Base44 to EXISTING project (must be inside the project folder) +npx base44 create my-app -p . + +# Add Base44 to existing project and deploy +npx base44 create my-app -p . --deploy + +# Create without adding AI agent skills +npx base44 create my-app -p . --no-skills +``` + +## What It Does + +1. Applies the selected template to the target path +2. Creates a `base44/` folder with configuration files +3. Registers the project with Base44 backend +4. Creates `base44/.app.jsonc` with the app ID +5. If `--deploy` is used: + - Pushes any entities defined in `base44/entities/` + - Runs install and build commands (for templates with frontend) + - Deploys the site to Base44 hosting diff --git a/evals/fixtures/agent-with-function/skills/base44-cli/references/dashboard.md b/evals/fixtures/agent-with-function/skills/base44-cli/references/dashboard.md new file mode 100644 index 0000000..95a534a --- /dev/null +++ b/evals/fixtures/agent-with-function/skills/base44-cli/references/dashboard.md @@ -0,0 +1,35 @@ +# base44 dashboard + +Opens the Base44 app dashboard in your default web browser. + +## Syntax + +```bash +npx base44 dashboard +``` + +## Options + +This command has no options. + +## What It Does + +1. Reads the project's app ID from `base44/.app.jsonc` +2. Opens the dashboard URL in your default browser + +## Example + +```bash +# Open dashboard for current project +npx base44 dashboard +``` + +## Requirements + +- Must be run from a linked Base44 project directory (contains `base44/.app.jsonc`) +- Must be authenticated (run `npx base44 login` first) + +## Notes + +- The dashboard provides a web interface to manage your app's entities, functions, users, and settings +- If you're not in a project directory, the command will fail with an error diff --git a/evals/fixtures/agent-with-function/skills/base44-cli/references/deploy.md b/evals/fixtures/agent-with-function/skills/base44-cli/references/deploy.md new file mode 100644 index 0000000..500526f --- /dev/null +++ b/evals/fixtures/agent-with-function/skills/base44-cli/references/deploy.md @@ -0,0 +1,86 @@ +# base44 deploy + +Deploys all project resources (entities, functions, and site) to Base44 in a single command. + +## Syntax + +```bash +npx base44 deploy [options] +``` + +## Options + +| Option | Description | +|--------|-------------| +| `-y, --yes` | Skip confirmation prompt | + +## What It Deploys + +The command automatically detects and deploys: + +1. **Entities** - All `.jsonc` files in `base44/entities/` +2. **Functions** - All functions in `base44/functions/` +3. **Agents** - All agent configurations in `base44/agents/` +4. **Site** - Built files from `site.outputDirectory` (if configured) + +## Examples + +```bash +# Interactive mode - shows what will be deployed and asks for confirmation +npx base44 deploy + +# Non-interactive - skip confirmation (for CI/CD or agent use) +npx base44 deploy -y +``` + +## Typical Workflow + +```bash +# 1. Make your changes (entities, functions, frontend code) + +# 2. Build the frontend (if you have one) +npm run build + +# 3. Deploy everything +npx base44 deploy -y +``` + +## What It Does + +1. Reads project configuration from `base44/config.jsonc` +2. Detects available resources (entities, functions, site) +3. Shows a summary of what will be deployed +4. Asks for confirmation (unless `-y` flag is used) +5. Deploys all resources in sequence: + - Pushes entity schemas + - Deploys functions + - Pushes agent configurations + - Uploads site files +6. Displays the dashboard URL and app URL (if site was deployed) + +## Requirements + +- Must be run from a linked Base44 project directory +- Must be authenticated (run `npx base44 login` first) +- For site deployment, must run `npm run build` first + +## Output + +After successful deployment: +- **Dashboard**: Link to your app's management dashboard +- **App URL**: Your deployed site's public URL (if site was included) + +## Notes + +- If no resources are found, the command exits with a message +- Use individual commands (`entities push`, `functions deploy`, `site deploy`) if you only want to deploy specific resources +- The site must be built before deployment - this command does not run `npm run build` for you + +## Related Commands + +| Command | Description | +|---------|-------------| +| `base44 entities push` | Push only entities | +| `base44 functions deploy` | Deploy only functions | +| `base44 agents push` | Push only agents | +| `base44 site deploy` | Deploy only the site | diff --git a/evals/fixtures/agent-with-function/skills/base44-cli/references/entities-create.md b/evals/fixtures/agent-with-function/skills/base44-cli/references/entities-create.md new file mode 100644 index 0000000..29b40aa --- /dev/null +++ b/evals/fixtures/agent-with-function/skills/base44-cli/references/entities-create.md @@ -0,0 +1,552 @@ +# Creating Entities + +Base44 entities are defined locally in your project and then pushed to the Base44 backend. + +## Critical: File Naming + +Entity files MUST use kebab-case naming: `{kebab-case-name}.jsonc` + +| Entity Name | File Name | +|-------------|-----------| +| `Task` | `task.jsonc` | +| `TeamMember` | `team-member.jsonc` | +| `ActivityLog` | `activity-log.jsonc` | + +WRONG: `TeamMember.jsonc`, `teamMember.jsonc` +RIGHT: `team-member.jsonc` + +## Table of Contents + +- [Creating Entities](#creating-entities) + - [Entity Directory](#entity-directory) + - [How to Create an Entity](#how-to-create-an-entity) + - [Entity Schema Structure](#entity-schema-structure) + - [Supported Field Types](#supported-field-types) + - [Field Properties](#field-properties) + - [Complete Example](#complete-example) + - [Naming Conventions](#naming-conventions) + - [Relationships Between Entities](#relationships-between-entities) + - [Row Level Security (RLS)](#row-level-security-rls) + - [Field Level Security (FLS)](#field-level-security-fls) + - [Pushing Entities](#pushing-entities) + +## Entity Directory + +All entity definitions must be placed in the `base44/entities/` folder in your project root. Each entity is defined in its own `.jsonc` file. + +Example structure: +``` +my-app/ + base44/ + entities/ + user.jsonc + product.jsonc + order.jsonc +``` + +## How to Create an Entity + +1. Create a new `.jsonc` file in the `base44/entities/` directory +2. Define your entity schema following the structure below +3. Push the changes to Base44 using the CLI + +## Entity Schema Structure + +Each entity file follows a JSON Schema-like structure: + +```jsonc +{ + "name": "EntityName", // PascalCase entity name + "type": "object", // Always "object" + "properties": { + // Define your fields here + }, + "required": ["field1"] // Array of required field names +} +``` + +### Common Mistake: Nested Schema Property + +**WRONG** - Do NOT wrap properties in a `schema` object: +```jsonc +{ + "name": "Task", + "description": "A task entity", + "schema": { // ❌ WRONG - don't use nested "schema" + "type": "object", + "properties": { ... } + } +} +``` + +**CORRECT** - Put `type` and `properties` at the top level: +```jsonc +{ + "name": "Task", + "description": "A task entity", + "type": "object", // ✅ CORRECT - top level + "properties": { ... } // ✅ CORRECT - top level +} +``` + +This is a common mistake that will cause "Invalid schema: Schema must have a 'type' field" errors when pushing entities. + +## Supported Field Types + +### String + +Basic text field: +```jsonc +{ + "title": { + "type": "string", + "description": "Task title" + } +} +``` + +With format: +```jsonc +{ + "due_date": { + "type": "string", + "format": "date", + "description": "Due date" + } +} +``` + +Available formats: `date`, `date-time`, `time`, `email`, `uri`, `hostname`, `ipv4`, `ipv6`, `uuid`, `file`, `regex`, `richtext` + +### String with Enum + +Constrained to specific values: +```jsonc +{ + "status": { + "type": "string", + "enum": ["todo", "in_progress", "done"], + "default": "todo", + "description": "Current status" + } +} +``` + +### Number + +```jsonc +{ + "position": { + "type": "number", + "description": "Position for ordering" + } +} +``` + +### Integer + +For whole numbers only: +```jsonc +{ + "quantity": { + "type": "integer", + "description": "Item quantity", + "minimum": 0, + "maximum": 1000 + } +} +``` + +### Binary + +For file/blob data: +```jsonc +{ + "attachment": { + "type": "binary", + "description": "File attachment" + } +} +``` + +### Boolean + +```jsonc +{ + "notify_on_change": { + "type": "boolean", + "default": true, + "description": "Enable notifications" + } +} +``` + +### Array of Strings + +```jsonc +{ + "labels": { + "type": "array", + "items": { "type": "string" }, + "description": "Task labels/tags" + } +} +``` + +### Array of Objects + +```jsonc +{ + "attachments": { + "type": "array", + "description": "File attachments", + "items": { + "type": "object", + "properties": { + "name": { "type": "string" }, + "url": { "type": "string" }, + "type": { "type": "string" } + } + } + } +} +``` + +## Field Properties + +| Property | Description | +| ------------- | ---------------------------------------------------------------------------------------- | +| `type` | Data type: `string`, `number`, `integer`, `boolean`, `array`, `object`, `binary` | +| `description` | Human-readable description of the field | +| `enum` | Array of allowed values (for strings) | +| `enumNames` | Human-readable labels for enum values (same order as `enum`) | +| `default` | Default value when not provided | +| `format` | Format hint: `date`, `date-time`, `time`, `email`, `uri`, `hostname`, `ipv4`, `ipv6`, `uuid`, `file`, `regex`, `richtext` | +| `items` | Schema for array items | +| `properties` | Nested properties for object types | +| `$ref` | Reference to another schema definition | +| `minLength` | Minimum string length | +| `maxLength` | Maximum string length | +| `pattern` | Regex pattern for string validation | +| `minimum` | Minimum value for numbers | +| `maximum` | Maximum value for numbers | +| `rls` | Field-level security rules (see Field Level Security section) | + +## Complete Example + +Here's a complete entity definition for a Task: + +```jsonc +{ + "name": "Task", + "type": "object", + "properties": { + "title": { + "type": "string", + "description": "Task title" + }, + "description": { + "type": "string", + "description": "Task description" + }, + "status": { + "type": "string", + "enum": ["todo", "in_progress", "done"], + "default": "todo", + "description": "Current status of the task" + }, + "board_id": { + "type": "string", + "description": "Board this task belongs to" + }, + "assignee_email": { + "type": "string", + "description": "Email of assigned user" + }, + "priority": { + "type": "string", + "enum": ["low", "medium", "high"], + "default": "medium", + "description": "Task priority" + }, + "due_date": { + "type": "string", + "format": "date", + "description": "Due date" + }, + "labels": { + "type": "array", + "items": { "type": "string" }, + "description": "Task labels/tags" + } + }, + "required": ["title"] +} +``` + +## Naming Conventions + +- **Entity name**: Use PascalCase with alphanumeric characters only (e.g., `Task`, `TeamMember`, `ActivityLog`) + - Must match pattern: `/^[a-zA-Z0-9]+$/` + - Valid: `Task`, `TeamMember`, `Order123` + - Invalid: `Team_Member`, `Team-Member`, `Team Member` +- **File name**: Use kebab-case matching the entity (e.g., `task.jsonc`, `team-member.jsonc`, `activity-log.jsonc`) +- **Field names**: Use snake_case (e.g., `board_id`, `user_email`, `due_date`) + +## Relationships Between Entities + +To create relationships between entities, use ID reference fields: + +```jsonc +{ + "board_id": { + "type": "string", + "description": "Board this task belongs to" + }, + "team_id": { + "type": "string", + "description": "Associated team ID" + } +} +``` + +## Row Level Security (RLS) + +Row Level Security (RLS) controls which records users can access based on their identity and attributes. RLS rules are defined per entity inside the `rls` field of the schema. + +**Important:** If no RLS is defined, all records are accessible to all users. + +### RLS Operations + +RLS supports five operations: + +| Operation | Description | +|-----------|-------------| +| `create` | Control who can add new records | +| `read` | Control who can view records | +| `update` | Control who can modify records | +| `delete` | Control who can remove records | +| `write` | Shorthand for `create`, `update`, and `delete` combined | + +### Permission Values + +Each operation accepts one of the following values: + +1. **`true`** - Allow all users (including anonymous/unauthenticated) +2. **`false`** - Block all users +3. **Condition object** - Allow users matching the condition + +### Template Variables + +Use template variables to reference the current user's attributes: + +| Template | Description | +|----------|-------------| +| `{{user.id}}` | The user's ID | +| `{{user.email}}` | The user's email | +| `{{user.role}}` | The user's role | +| `{{user.data.field_name}}` | Custom field from the user's `data` object | + +### Built-in Entity Attributes + +Every entity record has these built-in attributes available for RLS rules: + +| Attribute | Description | +|-----------|-------------| +| `id` | Unique record identifier | +| `created_date` | Timestamp when record was created | +| `updated_date` | Timestamp when record was last updated | +| `created_by` | Email of the user who created the record | + +### Rule Types + +There are two condition types you can use: + +**1. Entity-to-user comparison** - Compare record fields to the current user's values: +```jsonc +{ + "created_by": "{{user.email}}" +} +``` + +**2. User condition check** - Check user properties directly using `user_condition`: +```jsonc +{ + "user_condition": { "role": "admin" } +} +``` + +**Important limitations:** +- `user_condition` only supports **simple equality** (e.g., `{ "role": "admin" }`) +- For `data.*` field comparisons, you can use operators: `$in`, `$nin`, `$ne`, `$all` +- Logical operators `$or`, `$and`, `$nor` are available for combining conditions +- You cannot filter by entity field values directly (e.g., `{"status": "published"}`) +- Only user-related conditions are allowed + +### RLS Examples + +**Owner-only access:** +```jsonc +{ + "created_by": "{{user.email}}" +} +``` + +**Department-based access:** +```jsonc +{ + "data.department": "{{user.data.department}}" +} +``` + +**Admin-only access:** +```jsonc +{ + "user_condition": { "role": "admin" } +} +``` + +**Complete RLS configuration:** +```jsonc +{ + "name": "Task", + "type": "object", + "properties": { + "title": { + "type": "string", + "description": "Task title" + }, + "status": { + "type": "string", + "enum": ["todo", "in_progress", "done"], + "default": "todo" + } + }, + "required": ["title"], + "rls": { + "create": true, + "read": { "created_by": "{{user.email}}" }, + "update": { "created_by": "{{user.email}}" }, + "delete": { "created_by": "{{user.email}}" } + } +} +``` + +### Common RLS Patterns + +**Public create, admin-only management (e.g., contact forms, waitlists):** +```jsonc +{ + "rls": { + "create": true, + "read": { "user_condition": { "role": "admin" } }, + "update": { "user_condition": { "role": "admin" } }, + "delete": { "user_condition": { "role": "admin" } } + } +} +``` + +**Owner-only access:** +```jsonc +{ + "rls": { + "create": true, + "read": { "created_by": "{{user.email}}" }, + "update": { "created_by": "{{user.email}}" }, + "delete": { "created_by": "{{user.email}}" } + } +} +``` + +**Logged-in users only:** +```jsonc +{ + "rls": { + "create": { "user_condition": { "id": "{{user.id}}" } }, + "read": true, + "update": { "created_by": "{{user.email}}" }, + "delete": { "created_by": "{{user.email}}" } + } +} +``` + +### Limitations + +- **user_condition is equality only:** `user_condition` only supports exact match (e.g., `{ "role": "admin" }`) - no operators +- **No comparison operators on user_condition:** `$gt`, `$lt`, `$regex`, `$expr`, `$where` are NOT supported for user conditions +- **No entity field filtering:** Cannot filter by entity field values (e.g., `{"status": "published"}`) +- **No deeply nested templates:** Templates like `{{user.data.profile.department}}` may not work + +**Supported operators:** +- **Logical operators:** `$or`, `$and`, `$nor` for combining multiple conditions +- **Field operators (for `data.*` fields only):** `$in`, `$nin`, `$ne`, `$all` + +### Complex Access Patterns + +For complex access patterns that require multiple conditions (e.g., "owner OR admin"), you have two options: + +1. **Use the Base44 Dashboard UI** - The dashboard allows adding multiple rules per operation with OR logic +2. **Use separate entities** - Split data into multiple entities with different access rules +3. **Use backend functions** - Implement custom access logic in backend functions + +## Field Level Security (FLS) + +Field Level Security allows you to control access to individual fields within an entity. FLS rules are defined within each field's schema using the `rls` property. + +### FLS Operations + +FLS supports the same operations as entity-level RLS: + +| Operation | Description | +|-----------|-------------| +| `create` | Control who can set this field when creating records | +| `read` | Control who can view this field | +| `update` | Control who can modify this field | +| `delete` | Control who can clear this field | +| `write` | Shorthand for `create`, `update`, and `delete` combined | + +### FLS Example + +```jsonc +{ + "name": "Employee", + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "Employee name" + }, + "salary": { + "type": "number", + "description": "Employee salary", + "rls": { + "read": { "user_condition": { "role": "hr" } }, + "update": { "user_condition": { "role": "hr" } } + } + }, + "department": { + "type": "string", + "description": "Department name" + } + }, + "required": ["name"] +} +``` + +In this example, only users with the `hr` role can read or update the `salary` field. All users with access to the entity can read/update other fields. + +### FLS Notes + +- If no field-level RLS is defined, the field inherits the entity-level RLS rules +- FLS rules follow the same condition format as entity-level RLS +- Use FLS for sensitive fields like salary, SSN, or internal notes + +## Pushing Entities + +The `entities push` command will push all entities that exist in the `base44/entities` folder. + +```bash +npx base44 entities push +``` + +For more details on the push command, see [entities-push.md](entities-push.md). diff --git a/evals/fixtures/agent-with-function/skills/base44-cli/references/entities-push.md b/evals/fixtures/agent-with-function/skills/base44-cli/references/entities-push.md new file mode 100644 index 0000000..63b92d7 --- /dev/null +++ b/evals/fixtures/agent-with-function/skills/base44-cli/references/entities-push.md @@ -0,0 +1,71 @@ +# base44 entities push + +Push local entity definitions to Base44. + +## Syntax + +```bash +npx base44 entities push +``` + +## Authentication + +**Required**: Yes. If not authenticated, you'll be prompted to login first. + +## What It Does + +1. Pushes all entities that exist in the `base44/entities` folder +2. Validates that entities exist in the folder +3. Displays the count of entities to be pushed +4. Uploads entities to the Base44 backend +5. Reports the results: created, updated, and deleted entities + +## Prerequisites + +- Must be run from a Base44 project directory +- Project must have entity definitions in the `base44/entities` folder + +## Output + +```bash +$ npx base44 entities push + +Found 3 entities to push +Pushing entities to Base44... + +Created: User, Post +Updated: Comment +Deleted: OldEntity + +✓ Entities pushed successfully +``` + +## Entity Synchronization + +The push operation synchronizes your local entity schema with Base44: + +- **Created**: New entities that didn't exist in Base44 +- **Updated**: Existing entities with modified schema or configuration +- **Deleted**: Entities that were removed from your local configuration + +## Error Handling + +If no entities are found in your project: +```bash +$ npx base44 entities push +No entities found in project +``` + +## Use Cases + +- After defining new entities in your project +- When modifying existing entity schemas +- To sync entity changes before deploying +- As part of your development workflow when data models change + +## Notes + +- This command syncs the entity schema/structure, not the actual data +- Changes are applied to your Base44 project immediately +- Make sure to test entity changes in a development environment first +- Entity definitions are located in the `base44/entities/` directory diff --git a/evals/fixtures/agent-with-function/skills/base44-cli/references/functions-create.md b/evals/fixtures/agent-with-function/skills/base44-cli/references/functions-create.md new file mode 100644 index 0000000..c523cdc --- /dev/null +++ b/evals/fixtures/agent-with-function/skills/base44-cli/references/functions-create.md @@ -0,0 +1,229 @@ +# Creating Functions + +Base44 functions are serverless backend functions that run on Deno. They are defined locally in your project and deployed to the Base44 backend. + +## Function Directory + +All function definitions must be placed in the `base44/functions/` folder in your project. Each function lives in its own subdirectory with a configuration file and entry point. + +Example structure: +``` +my-app/ + base44/ + functions/ + process-order/ + function.jsonc + index.ts + send-notification/ + function.jsonc + index.ts +``` + +## How to Create a Function + +1. Create a new directory in `base44/functions/` with your function name (use kebab-case) +2. Create a `function.jsonc` configuration file in the directory +3. Create the entry point file (e.g., `index.ts`) +4. Deploy the function using the CLI + +## Function Configuration + +Each function requires a `function.jsonc` configuration file: + +```jsonc +{ + "name": "my-function", + "entry": "index.ts" +} +``` + +### Configuration Properties + +| Property | Description | Required | +|----------|-------------|----------| +| `name` | Function name (must match `/^[^.]+$/` - no dots allowed) | Yes | +| `entry` | Entry point file path relative to the function directory (min 1 char) | Yes | + +## Entry Point File + +Functions run on Deno and must export using `Deno.serve()`. Use `npm:` prefix for npm packages. + +```typescript +import { createClientFromRequest } from "npm:@base44/sdk"; + +Deno.serve(async (req) => { + // Get authenticated client from request + const base44 = createClientFromRequest(req); + + // Parse input + const { orderId, action } = await req.json(); + + // Your logic here + const order = await base44.entities.Orders.get(orderId); + + // Return response + return Response.json({ + success: true, + order: order + }); +}); +``` + +### Request Object + +The function receives a standard Deno `Request` object: +- `req.json()` - Parse JSON body +- `req.text()` - Get raw text body +- `req.headers` - Access request headers +- `req.method` - HTTP method + +### Response Object + +Return using `Response.json()` for JSON responses: + +```typescript +// Success response +return Response.json({ data: result }); + +// Error response with status code +return Response.json({ error: "Something went wrong" }, { status: 400 }); + +// Not found +return Response.json({ error: "Order not found" }, { status: 404 }); +``` + +## Complete Example + +### Directory Structure +``` +base44/ + functions/ + process-order/ + function.jsonc + index.ts +``` + +### function.jsonc +```jsonc +{ + "name": "process-order", + "entry": "index.ts" +} +``` + +### index.ts +```typescript +import { createClientFromRequest } from "npm:@base44/sdk"; + +Deno.serve(async (req) => { + try { + const base44 = createClientFromRequest(req); + const { orderId } = await req.json(); + + // Validate input + if (!orderId) { + return Response.json( + { error: "Order ID is required" }, + { status: 400 } + ); + } + + // Fetch and process the order + const order = await base44.entities.Orders.get(orderId); + if (!order) { + return Response.json( + { error: "Order not found" }, + { status: 404 } + ); + } + + return Response.json({ + success: true, + orderId: order.id, + processedAt: new Date().toISOString() + }); + + } catch (error) { + return Response.json( + { error: error.message }, + { status: 500 } + ); + } +}); +``` + +## Using Service Role Access + +For admin-level operations, use `asServiceRole`: + +```typescript +import { createClientFromRequest } from "npm:@base44/sdk"; + +Deno.serve(async (req) => { + const base44 = createClientFromRequest(req); + + // Check user is authenticated + const user = await base44.auth.me(); + if (!user) { + return Response.json({ error: "Unauthorized" }, { status: 401 }); + } + + // Use service role for admin operations + const allOrders = await base44.asServiceRole.entities.Orders.list(); + + return Response.json({ orders: allOrders }); +}); +``` + +## Using Secrets + +Access environment variables configured in the app dashboard: + +```typescript +Deno.serve(async (req) => { + // Access environment variables (configured in app settings) + const apiKey = Deno.env.get("STRIPE_API_KEY"); + + const response = await fetch("https://api.stripe.com/v1/charges", { + headers: { + "Authorization": `Bearer ${apiKey}` + } + }); + + return Response.json(await response.json()); +}); +``` + +## Naming Conventions + +- **Directory name**: Use kebab-case (e.g., `process-order`, `send-notification`) +- **Function name**: Match the directory name, must match pattern `/^[^.]+$/` (no dots allowed) + - Valid: `process-order`, `send_notification`, `myFunction` + - Invalid: `process.order`, `send.notification.v2` +- **Entry file**: Typically `index.ts` or `index.js` + +## Deploying Functions + +After creating your function, deploy it to Base44: + +```bash +npx base44 functions deploy +``` + +For more details on deploying, see [functions-deploy.md](functions-deploy.md). + +## Notes + +- Functions run on Deno runtime, not Node.js +- Use `npm:` prefix for npm packages (e.g., `npm:@base44/sdk`) +- Use `createClientFromRequest(req)` to get a client that inherits the caller's auth context +- Configure secrets via app dashboard for API keys +- Make sure to handle errors gracefully and return appropriate HTTP status codes + +## Common Mistakes + +| Wrong | Correct | Why | +|-------|---------|-----| +| `functions/myFunction.js` (single file) | `functions/my-function/index.ts` + `function.jsonc` | Functions require subdirectory with config | +| `import { ... } from "@base44/sdk"` | `import { ... } from "npm:@base44/sdk"` | Deno requires `npm:` prefix for npm packages | +| `MyFunction` or `myFunction` directory | `my-function` directory | Use kebab-case for directory names | diff --git a/evals/fixtures/agent-with-function/skills/base44-cli/references/functions-deploy.md b/evals/fixtures/agent-with-function/skills/base44-cli/references/functions-deploy.md new file mode 100644 index 0000000..f89d796 --- /dev/null +++ b/evals/fixtures/agent-with-function/skills/base44-cli/references/functions-deploy.md @@ -0,0 +1,78 @@ +# base44 functions deploy + +Deploy local function definitions to Base44. + +## Syntax + +```bash +npx base44 functions deploy +``` + +## Authentication + +**Required**: Yes. If not authenticated, you'll be prompted to login first. + +## What It Does + +1. Scans the `base44/functions/` directory for function definitions +2. Validates that functions exist and have valid configurations +3. Displays the count of functions to be deployed +4. Uploads function code and configuration to Base44 +5. Reports the results: deployed and deleted functions + +## Prerequisites + +- Must be run from a Base44 project directory +- Project must have function definitions in the `base44/functions/` folder +- Each function must have a valid `function.jsonc` config file + +## Output + +```bash +$ npx base44 functions deploy + +Found 2 functions to deploy +Deploying functions to Base44... + +Deployed: process-order, send-notification +Deleted: old-function + +✓ Functions deployed successfully +``` + +## Function Synchronization + +The deploy operation synchronizes your local functions with Base44: + +- **Deployed**: Functions that were created or updated +- **Deleted**: Functions that were removed from your local configuration + +## Error Handling + +If no functions are found in your project: +```bash +$ npx base44 functions deploy +No functions found. Create functions in the 'functions' directory. +``` + +If a function has configuration errors: +```bash +$ npx base44 functions deploy +Function deployment errors: +'my-function' function: Entry point cannot be empty +``` + +## Use Cases + +- After creating new functions in your project +- When modifying existing function code or configuration +- To sync function changes before testing +- As part of your development workflow when backend logic changes + +## Notes + +- This command deploys the function code and configuration +- Changes are applied to your Base44 project immediately +- Make sure to test functions in a development environment first +- Function definitions are located in the `base44/functions/` directory +- For how to create functions, see [functions-create.md](functions-create.md) diff --git a/evals/fixtures/agent-with-function/skills/base44-cli/references/link.md b/evals/fixtures/agent-with-function/skills/base44-cli/references/link.md new file mode 100644 index 0000000..0b009ce --- /dev/null +++ b/evals/fixtures/agent-with-function/skills/base44-cli/references/link.md @@ -0,0 +1,81 @@ +# base44 link + +Links an existing local Base44 project to a Base44 app in the cloud. Use this when you have a `base44/config.jsonc` but haven't connected it to a Base44 app yet. + +## Critical: When to Use Link vs Create + +| Scenario | Command | +|----------|---------| +| Starting fresh, no `base44/` folder | `npx base44 create` | +| Have `base44/config.jsonc` but no `.app.jsonc` | `npx base44 link` | +| Project already linked (has `.app.jsonc`) | Already done, use `deploy` | + +## Syntax + +```bash +npx base44 link [options] +``` + +## Options + +| Option | Description | Required | +|--------|-------------|----------| +| `-c, --create` | Create a new project (skip selection prompt) | No | +| `-n, --name ` | Project name (required when `--create` is used) | With `--create` | +| `-d, --description ` | Project description | No | +| `-p, --projectId ` | Project ID to link to an existing project (skip selection prompt) | No | + +## Non-Interactive Mode + +For CI/CD or agent use: + +**Create a new project:** +```bash +npx base44 link --create --name my-app +``` + +**Link to an existing project:** +```bash +npx base44 link --projectId +``` + +WRONG: `npx base44 link --create` (missing --name) +WRONG: `npx base44 link --create --projectId ` (cannot use both) +RIGHT: `npx base44 link --create --name my-app` +RIGHT: `npx base44 link --projectId ` + +## Examples + +```bash +# Interactive mode - prompts for project details +npx base44 link + +# Non-interactive - create and link in one step +npx base44 link --create --name my-app + +# With description +npx base44 link --create --name my-app --description "My awesome app" + +# Link to a specific existing project by ID +npx base44 link --projectId abc123 +``` + +## What It Does + +1. Finds the `base44/config.jsonc` in the current directory (or parent directories) +2. Verifies no `.app.jsonc` exists (project not already linked) +3. Either: + - Creates a new Base44 app in the cloud (with `--create`), OR + - Links to an existing project (with `--projectId` or interactive selection) +4. Writes the app ID to `base44/.app.jsonc` + +## Requirements + +- Must have `base44/config.jsonc` in the project +- Must NOT have `base44/.app.jsonc` (use `deploy` if already linked) +- Must be authenticated (run `npx base44 login` first) + +## Notes + +- After linking, you can deploy resources with `npx base44 deploy` +- The `.app.jsonc` file should be git-ignored (contains your app ID) diff --git a/evals/fixtures/agent-with-function/skills/base44-cli/references/rls-examples.md b/evals/fixtures/agent-with-function/skills/base44-cli/references/rls-examples.md new file mode 100644 index 0000000..7d3ef42 --- /dev/null +++ b/evals/fixtures/agent-with-function/skills/base44-cli/references/rls-examples.md @@ -0,0 +1,463 @@ +# RLS Examples + +Practical Row-Level Security patterns for common application types. + +**Important:** Base44 RLS supports: +- **Logical operators:** `$or`, `$and`, `$nor` for combining conditions +- **Field operators (for `data.*` fields):** `$in`, `$nin`, `$ne`, `$all` +- **user_condition:** Equality only (no operators) + +## Contents +- [Simple Patterns (JSON Schema)](#simple-patterns-json-schema) +- [Using Operators](#using-operators) +- [Field-Level Security Examples](#field-level-security-examples) +- [Complex Patterns (Dashboard UI or Backend)](#complex-patterns-dashboard-ui-or-backend) +- [Best Practices](#best-practices) + +--- + +## Simple Patterns (JSON Schema) + +These patterns work with the JSON schema RLS format. + +### Todo App - Owner-only access + +Users see and manage only their own tasks. + +```jsonc +{ + "name": "Task", + "type": "object", + "properties": { + "title": { "type": "string" }, + "description": { "type": "string" }, + "completed": { "type": "boolean" }, + "priority": { "type": "string", "enum": ["low", "medium", "high"] }, + "due_date": { "type": "string", "format": "date" } + }, + "rls": { + "create": true, + "read": { "created_by": "{{user.email}}" }, + "update": { "created_by": "{{user.email}}" }, + "delete": { "created_by": "{{user.email}}" } + } +} +``` + +### Contact Form - Public create, admin-only read + +Anyone can submit, only admins can view submissions. + +```jsonc +{ + "name": "ContactSubmission", + "type": "object", + "properties": { + "name": { "type": "string" }, + "email": { "type": "string", "format": "email" }, + "message": { "type": "string" } + }, + "rls": { + "create": true, + "read": { "user_condition": { "role": "admin" } }, + "update": { "user_condition": { "role": "admin" } }, + "delete": { "user_condition": { "role": "admin" } } + } +} +``` + +### User Profile - Self-management + +Users can only access their own profile. + +```jsonc +{ + "name": "UserProfile", + "type": "object", + "properties": { + "name": { "type": "string" }, + "avatar_url": { "type": "string" }, + "bio": { "type": "string" }, + "preferences": { "type": "object" } + }, + "rls": { + "create": true, + "read": { "created_by": "{{user.email}}" }, + "update": { "created_by": "{{user.email}}" }, + "delete": { "created_by": "{{user.email}}" } + } +} +``` + +### Department Data - Same department access + +Users can only see records from their department. + +```jsonc +{ + "name": "DepartmentAnnouncement", + "type": "object", + "properties": { + "title": { "type": "string" }, + "content": { "type": "string" }, + "department": { "type": "string" } + }, + "rls": { + "create": { "user_condition": { "role": "manager" } }, + "read": { "data.department": "{{user.data.department}}" }, + "update": { "user_condition": { "role": "manager" } }, + "delete": { "user_condition": { "role": "admin" } } + } +} +``` + +### Subscription - Admin-managed, user-readable via email field + +```jsonc +{ + "name": "Subscription", + "type": "object", + "properties": { + "user_email": { "type": "string" }, + "tier": { "type": "string", "enum": ["free", "basic", "pro", "enterprise"] }, + "credits": { "type": "number" }, + "renewal_date": { "type": "string", "format": "date" } + }, + "rls": { + "create": { "user_condition": { "role": "admin" } }, + "read": { "data.user_email": "{{user.email}}" }, + "update": { "user_condition": { "role": "admin" } }, + "delete": { "user_condition": { "role": "admin" } } + } +} +``` + +**Note:** This pattern only allows users to read their own subscription. Admins need to use the Dashboard UI to configure additional read access for themselves. + +### Private Data - Owner-only + +```jsonc +{ + "name": "PrivateNotes", + "type": "object", + "properties": { + "title": { "type": "string" }, + "content": { "type": "string" }, + "tags": { "type": "array", "items": { "type": "string" } } + }, + "rls": { + "create": true, + "read": { "created_by": "{{user.email}}" }, + "update": { "created_by": "{{user.email}}" }, + "delete": { "created_by": "{{user.email}}" } + } +} +``` + +### Public Read, Authenticated Write + +Anyone can read, only logged-in users can create/edit their own records. + +```jsonc +{ + "name": "BlogPost", + "type": "object", + "properties": { + "title": { "type": "string" }, + "content": { "type": "string" }, + "author_email": { "type": "string" } + }, + "rls": { + "create": true, + "read": true, + "update": { "created_by": "{{user.email}}" }, + "delete": { "created_by": "{{user.email}}" } + } +} +``` + +--- + +## Using Operators + +### Logical Operators + +Combine multiple conditions using `$or`, `$and`, or `$nor`: + +**Owner OR Admin access:** +```jsonc +{ + "name": "Document", + "type": "object", + "properties": { + "title": { "type": "string" }, + "content": { "type": "string" } + }, + "rls": { + "create": true, + "read": { + "$or": [ + { "created_by": "{{user.email}}" }, + { "user_condition": { "role": "admin" } } + ] + }, + "update": { + "$or": [ + { "created_by": "{{user.email}}" }, + { "user_condition": { "role": "admin" } } + ] + }, + "delete": { "user_condition": { "role": "admin" } } + } +} +``` + +**Multiple roles with $or:** +```jsonc +{ + "rls": { + "read": { + "$or": [ + { "user_condition": { "role": "admin" } }, + { "user_condition": { "role": "manager" } }, + { "user_condition": { "role": "hr" } } + ] + } + } +} +``` + +### Field Operators for data.* Fields + +Use `$in`, `$nin`, `$ne`, `$all` for comparing entity data fields: + +**Access based on tags ($in):** +```jsonc +{ + "rls": { + "read": { + "data.category": { "$in": ["public", "shared"] } + } + } +} +``` + +**Exclude specific statuses ($nin):** +```jsonc +{ + "rls": { + "read": { + "data.status": { "$nin": ["archived", "deleted"] } + } + } +} +``` + +**Not equal ($ne):** +```jsonc +{ + "rls": { + "read": { + "data.visibility": { "$ne": "private" } + } + } +} +``` + +**All tags must match ($all):** +```jsonc +{ + "rls": { + "read": { + "data.required_tags": { "$all": ["approved", "reviewed"] } + } + } +} +``` + +### Combining Logical and Field Operators + +```jsonc +{ + "rls": { + "read": { + "$and": [ + { "data.status": { "$ne": "draft" } }, + { + "$or": [ + { "created_by": "{{user.email}}" }, + { "data.visibility": "public" } + ] + } + ] + } + } +} +``` + +--- + +## Field-Level Security Examples + +Control access to specific fields within an entity. + +### Sensitive Salary Field + +```jsonc +{ + "name": "Employee", + "type": "object", + "properties": { + "name": { "type": "string" }, + "email": { "type": "string", "format": "email" }, + "salary": { + "type": "number", + "description": "Annual salary", + "rls": { + "read": { "user_condition": { "role": "hr" } }, + "write": { "user_condition": { "role": "hr" } } + } + }, + "performance_notes": { + "type": "string", + "description": "Manager notes", + "rls": { + "read": { + "$or": [ + { "user_condition": { "role": "manager" } }, + { "user_condition": { "role": "hr" } } + ] + }, + "write": { "user_condition": { "role": "manager" } } + } + } + } +} +``` + +### Admin-Only Internal Fields + +```jsonc +{ + "name": "Order", + "type": "object", + "properties": { + "order_number": { "type": "string" }, + "total": { "type": "number" }, + "internal_notes": { + "type": "string", + "description": "Internal processing notes", + "rls": { + "read": { "user_condition": { "role": "admin" } }, + "write": { "user_condition": { "role": "admin" } } + } + }, + "profit_margin": { + "type": "number", + "description": "Profit margin percentage", + "rls": { + "read": { "user_condition": { "role": "admin" } }, + "write": false + } + } + } +} +``` + +--- + +## Complex Patterns (Dashboard UI or Backend) + +Some patterns may still require the Dashboard UI or backend functions. + +### Bidirectional Relationships (e.g., Friendships, Matches) + +**Requirement:** Either party in a relationship should have access. + +**Now possible with $or:** +```jsonc +{ + "rls": { + "read": { + "$or": [ + { "data.user_a_email": "{{user.email}}" }, + { "data.user_b_email": "{{user.email}}" } + ] + } + } +} +``` + +**Alternative solutions:** +1. **Entity redesign:** Store two records per relationship (one for each party) +2. **Backend function:** Query with custom logic + +### Complex Business Logic + +**Requirement:** Access depends on multiple entity fields with complex conditions. + +**JSON Schema limitation:** While operators help, very complex business logic may still be hard to express. + +**Solution options:** +1. **Backend function:** Implement custom access logic +2. **Combine simpler rules:** Break complex rules into simpler entity-level and field-level rules + +--- + +## Best Practices + +### Security Strategy + +Use a combination of entity-level RLS and field-level security: + +| Data Type | Approach | Example | +|-----------|----------|---------| +| User-editable | Entity RLS: Owner-only | UserProfile with `created_by` check | +| Sensitive fields | Field-level RLS | Salary field with HR role check | +| Multi-role access | `$or` with user_condition | Admin OR Manager access | +| Conditional access | Field operators | `$in`, `$ne` on data fields | +| Public content | Entity RLS: `read: true` | PublicPost | +| Private content | Entity RLS: Owner-only | PrivateNote | + +### When to Use Each Approach + +| Requirement | Approach | +|-------------|----------| +| Single condition (owner, admin, department) | JSON Schema RLS | +| Multiple OR/AND conditions | JSON Schema RLS with `$or`/`$and` | +| Field value checks with `$in`/`$ne`/etc. | JSON Schema RLS for `data.*` fields | +| Field-level access control | JSON Schema FLS (field-level `rls`) | +| Complex comparison operators (`$gt`, `$lt`) | Backend functions | +| Very complex business logic | Backend functions | + +### Common Role Patterns + +| Role | Typical Access | +|------|----------------| +| `admin` | Full access to all records | +| `moderator` | Read/update access, limited delete | +| `manager` | Department-scoped access | +| `user` | Own records only | + +### Supported Operators Summary + +| Operator | Supported | Notes | +|----------|-----------|-------| +| `$or` | Yes | Combine multiple conditions | +| `$and` | Yes | All conditions must match | +| `$nor` | Yes | None of the conditions match | +| `$in` | Yes | For `data.*` fields only | +| `$nin` | Yes | For `data.*` fields only | +| `$ne` | Yes | For `data.*` fields only | +| `$all` | Yes | For `data.*` fields only | +| `$gt`, `$lt`, `$gte`, `$lte` | No | Use backend functions | +| `$regex` | No | Use backend functions | + +### Limitations Summary + +| Not Supported | Alternative | +|---------------|-------------| +| Operators on `user_condition` | Use equality only for user checks | +| Comparison operators (`$gt`, `$lt`) | Backend functions | +| Regex matching (`$regex`) | Backend functions | +| Cross-entity relationships | Backend functions | diff --git a/evals/fixtures/agent-with-function/skills/base44-cli/references/site-deploy.md b/evals/fixtures/agent-with-function/skills/base44-cli/references/site-deploy.md new file mode 100644 index 0000000..929d302 --- /dev/null +++ b/evals/fixtures/agent-with-function/skills/base44-cli/references/site-deploy.md @@ -0,0 +1,118 @@ +# base44 site deploy + +Deploy built site files to Base44 hosting. + +## Table of Contents + +- [Syntax](#syntax) +- [Authentication](#authentication) +- [Prerequisites](#prerequisites) +- [How It Works](#how-it-works) +- [Interactive Flow](#interactive-flow) +- [Typical Workflow](#typical-workflow) +- [Configuration](#configuration) +- [Error Handling](#error-handling) +- [Use Cases](#use-cases) +- [Notes](#notes) + +## Syntax + +```bash +npx base44 site deploy [options] +``` + +## Options + +| Option | Description | +| ------------ | ------------------------- | +| `-y, --yes` | Skip confirmation prompt | + +Use `-y` flag for non-interactive/automated deployments: + +```bash +npx base44 site deploy -y +``` + +## Authentication + +**Required**: Yes. If not authenticated, you'll be prompted to login first. + +## Prerequisites + +- Must be run from a Base44 project directory +- Project must have `site.outputDirectory` configured in project config +- Site must be built before deploying (run your build command first) +- **SPA only**: Base44 hosting supports Single Page Applications with a single `index.html` entry point. All routes are served from `index.html` (client-side routing). + +## How It Works + +1. Reads project configuration +2. Validates that site configuration exists +3. Prompts for deployment confirmation showing the output directory +4. Creates an archive of site files from the output directory +5. Deploys to Base44 hosting +6. Returns the app URL + +## Interactive Flow + +```bash +$ npx base44 site deploy + +Deploy site from ./dist? (yes/no) yes + +Creating archive... +Uploading to Base44... +Deploying... + +✓ Deployment successful! + +Visit your site at: https://my-app.base44.app +``` + +## Typical Workflow + +```bash +# 1. Build your site using your framework's build command +npm run build + +# 2. Deploy to Base44 +npx base44 site deploy +``` + +## Configuration + +The `site.outputDirectory` in your project configuration should point to where your framework outputs built files: + +- Vite: typically `./dist` +- Next.js: typically `./.next` or `./out` +- Create React App: typically `./build` +- Custom: whatever your build tool outputs to + +## Error Handling + +If site configuration is missing: +```bash +$ npx base44 site deploy +Error: No site configuration found in project +``` + +If you cancel the deployment: +```bash +Deploy site from ./dist? (yes/no) no +Deployment cancelled +``` + +## Use Cases + +- Deploy your site after making changes +- Push new versions of your application +- Deploy after updating content or functionality +- Part of your CI/CD pipeline + +## Notes + +- Always build your site before deploying +- The command deploys whatever is in your output directory +- Make sure your build completed successfully before deploying +- Previous deployments are preserved (versioned) in Base44 +- Deployment is immediate and updates your live site diff --git a/evals/fixtures/agent-with-function/skills/base44-sdk/SKILL.md b/evals/fixtures/agent-with-function/skills/base44-sdk/SKILL.md new file mode 100644 index 0000000..01ddd7f --- /dev/null +++ b/evals/fixtures/agent-with-function/skills/base44-sdk/SKILL.md @@ -0,0 +1,296 @@ +--- +name: base44-sdk +description: "The base44 SDK is the library to communicate with base44 services. In projects, you use it to communicate with remote resources (entities, backend functions, ai agents) and to write backend functions. This skill is the place for learning about available modules and types. When you plan or implement a feature, you must learn this skill" +--- + +# Base44 Coder + +Build apps on the Base44 platform using the Base44 JavaScript SDK. + +## ⚡ IMMEDIATE ACTION REQUIRED - Read This First + +This skill activates on ANY mention of "base44" or when a `base44/` folder exists. **DO NOT read documentation files or search the web before acting.** + +**Your first action MUST be:** +1. Check if `base44/config.jsonc` exists in the current directory +2. If **YES** (existing project scenario): + - This skill (base44-sdk) handles the request + - Implement features using Base44 SDK + - Do NOT use base44-cli unless user explicitly requests CLI commands +3. If **NO** (new project scenario): + - Transfer to base44-cli skill for project initialization + - This skill cannot help until project is initialized + +## When to Use This Skill vs base44-cli + +**Use base44-sdk when:** +- Building features in an **EXISTING** Base44 project +- `base44/config.jsonc` already exists in the project +- Base44 SDK imports are present (`@base44/sdk`) +- Writing JavaScript/TypeScript code using Base44 SDK modules +- Implementing functionality, components, or features +- User mentions: "implement", "build a feature", "add functionality", "write code for" +- User says "create a [type] app" **and** a Base44 project already exists + +**DO NOT USE base44-sdk for:** +- ❌ Initializing new Base44 projects (use `base44-cli` instead) +- ❌ Empty directories without Base44 configuration +- ❌ When user says "create a new Base44 project/app/site" and no project exists +- ❌ CLI commands like `npx base44 create`, `npx base44 deploy`, `npx base44 login` (use `base44-cli`) + +**Skill Dependencies:** +- `base44-sdk` assumes a Base44 project is **already initialized** +- `base44-cli` is a **prerequisite** for `base44-sdk` in new projects +- If user wants to "create an app" and no Base44 project exists, use `base44-cli` first + +**State Check Logic:** +Before selecting this skill, verify: +- IF (user mentions "create/build app" OR "make a project"): + - IF (directory is empty OR no `base44/config.jsonc` exists): + → Use **base44-cli** (project initialization needed) + - ELSE: + → Use **base44-sdk** (project exists, build features) + +## Quick Start + +```javascript +// In Base44-generated apps, base44 client is pre-configured and available + +// CRUD operations +const task = await base44.entities.Task.create({ title: "New task", status: "pending" }); +const tasks = await base44.entities.Task.list(); +await base44.entities.Task.update(task.id, { status: "done" }); + +// Get current user +const user = await base44.auth.me(); +``` + +```javascript +// External apps +import { createClient } from "@base44/sdk"; + +// IMPORTANT: Use 'appId' (NOT 'clientId' or 'id') +const base44 = createClient({ appId: "your-app-id" }); +await base44.auth.loginViaEmailPassword("user@example.com", "password"); +``` + +## ⚠️ CRITICAL: Do Not Hallucinate APIs + +**Before writing ANY Base44 code, verify method names against this table or [QUICK_REFERENCE.md](references/QUICK_REFERENCE.md).** + +Base44 SDK has unique method names. Do NOT assume patterns from Firebase, Supabase, or other SDKs. + +### Authentication - WRONG vs CORRECT + +| ❌ WRONG (hallucinated) | ✅ CORRECT | +|------------------------|-----------| +| `signInWithGoogle()` | `loginWithProvider('google')` | +| `signInWithProvider('google')` | `loginWithProvider('google')` | +| `auth.google()` | `loginWithProvider('google')` | +| `signInWithEmailAndPassword(email, pw)` | `loginViaEmailPassword(email, pw)` | +| `signIn(email, pw)` | `loginViaEmailPassword(email, pw)` | +| `createUser()` / `signUp()` | `register({email, password})` | +| `onAuthStateChanged()` | `me()` (no listener, call when needed) | +| `currentUser` | `await auth.me()` | + +### Functions - WRONG vs CORRECT + +| ❌ WRONG (hallucinated) | ✅ CORRECT | +|------------------------|-----------| +| `functions.call('name', data)` | `functions.invoke('name', data)` | +| `functions.run('name', data)` | `functions.invoke('name', data)` | +| `callFunction('name', data)` | `functions.invoke('name', data)` | +| `httpsCallable('name')(data)` | `functions.invoke('name', data)` | + +### Integrations - WRONG vs CORRECT + +| ❌ WRONG (hallucinated) | ✅ CORRECT | +|------------------------|-----------| +| `ai.generate(prompt)` | `integrations.Core.InvokeLLM({prompt})` | +| `openai.chat(prompt)` | `integrations.Core.InvokeLLM({prompt})` | +| `llm(prompt)` | `integrations.Core.InvokeLLM({prompt})` | +| `sendEmail(to, subject, body)` | `integrations.Core.SendEmail({to, subject, body})` | +| `email.send()` | `integrations.Core.SendEmail({to, subject, body})` | +| `uploadFile(file)` | `integrations.Core.UploadFile({file})` | +| `storage.upload(file)` | `integrations.Core.UploadFile({file})` | + +### Entities - WRONG vs CORRECT + +| ❌ WRONG (hallucinated) | ✅ CORRECT | +|------------------------|-----------| +| `entities.Task.find({...})` | `entities.Task.filter({...})` | +| `entities.Task.findOne(id)` | `entities.Task.get(id)` | +| `entities.Task.insert(data)` | `entities.Task.create(data)` | +| `entities.Task.remove(id)` | `entities.Task.delete(id)` | +| `entities.Task.onChange(cb)` | `entities.Task.subscribe(cb)` | + +## SDK Modules + +| Module | Purpose | Reference | +|--------|---------|-----------| +| `entities` | CRUD operations on data models | [entities.md](references/entities.md) | +| `auth` | Login, register, user management | [auth.md](references/auth.md) | +| `agents` | AI conversations and messages | [base44-agents.md](references/base44-agents.md) | +| `functions` | Backend function invocation | [functions.md](references/functions.md) | +| `integrations` | AI, email, file uploads, custom APIs | [integrations.md](references/integrations.md) | +| `connectors` | OAuth tokens (service role only) | [connectors.md](references/connectors.md) | +| `analytics` | Track custom events and user activity | [analytics.md](references/analytics.md) | +| `appLogs` | Log user activity in app | [app-logs.md](references/app-logs.md) | +| `users` | Invite users to the app | [users.md](references/users.md) | + +For client setup and authentication modes, see [client.md](references/client.md). + +**TypeScript Support:** Each reference file includes a "Type Definitions" section with TypeScript interfaces and types for the module's methods, parameters, and return values. + +## Installation + +Install the Base44 SDK: + +```bash +npm install @base44/sdk +``` + +**Important:** Never assume or hardcode the `@base44/sdk` package version. Always install without a version specifier to get the latest version. + +## Creating a Client (External Apps) + +When creating a client in external apps, **ALWAYS use `appId` as the parameter name**: + +```javascript +import { createClient } from "@base44/sdk"; + +// ✅ CORRECT +const base44 = createClient({ appId: "your-app-id" }); + +// ❌ WRONG - Do NOT use these: +// const base44 = createClient({ clientId: "your-app-id" }); // WRONG +// const base44 = createClient({ id: "your-app-id" }); // WRONG +``` + +**Required parameter:** `appId` (string) - Your Base44 application ID + +**Optional parameters:** +- `token` (string) - Pre-authenticated user token +- `options` (object) - Configuration options + - `options.onError` (function) - Global error handler + +**Example with error handler:** +```javascript +const base44 = createClient({ + appId: "your-app-id", + options: { + onError: (error) => { + console.error("Base44 error:", error); + } + } +}); +``` + +## Module Selection + +**Working with app data?** +- Create/read/update/delete records → `entities` +- Import data from file → `entities.importEntities()` +- Realtime updates → `entities.EntityName.subscribe()` + +**User management?** +- Login/register/logout → `auth` +- Get current user → `auth.me()` +- Update user profile → `auth.updateMe()` +- Invite users → `users.inviteUser()` + +**AI features?** +- Chat with AI agents → `agents` (requires logged-in user) +- Create new conversation → `agents.createConversation()` +- Manage conversations → `agents.getConversations()` +- Generate text/JSON with AI → `integrations.Core.InvokeLLM()` +- Generate images → `integrations.Core.GenerateImage()` + +**Custom backend logic?** +- Run server-side code → `functions.invoke()` +- Need admin access → `base44.asServiceRole.functions.invoke()` + +**External services?** +- Send emails → `integrations.Core.SendEmail()` +- Upload files → `integrations.Core.UploadFile()` +- Custom APIs → `integrations.custom.call()` +- OAuth tokens (Google, Slack) → `connectors` (backend only) + +**Tracking and analytics?** +- Track custom events → `analytics.track()` +- Log page views/activity → `appLogs.logUserInApp()` + +## Common Patterns + +### Filter and Sort Data + +```javascript +const pendingTasks = await base44.entities.Task.filter( + { status: "pending", assignedTo: userId }, // query + "-created_date", // sort (descending) + 10, // limit + 0 // skip +); +``` + +### Protected Routes (check auth) + +```javascript +const user = await base44.auth.me(); +if (!user) { + // Navigate to your custom login page + navigate('/login', { state: { returnTo: window.location.pathname } }); + return; +} +``` + +### Backend Function Call + +```javascript +// Frontend +const result = await base44.functions.invoke("processOrder", { + orderId: "123", + action: "ship" +}); + +// Backend function (Deno) +import { createClientFromRequest } from "npm:@base44/sdk"; + +Deno.serve(async (req) => { + const base44 = createClientFromRequest(req); + const { orderId, action } = await req.json(); + // Process with service role for admin access + const order = await base44.asServiceRole.entities.Orders.get(orderId); + return Response.json({ success: true }); +}); +``` + +### Service Role Access + +Use `asServiceRole` in backend functions for admin-level operations: + +```javascript +// User mode - respects permissions +const myTasks = await base44.entities.Task.list(); + +// Service role - full access (backend only) +const allTasks = await base44.asServiceRole.entities.Task.list(); +const token = await base44.asServiceRole.connectors.getAccessToken("slack"); +``` + +## Frontend vs Backend + +| Capability | Frontend | Backend | +|------------|----------|---------| +| `entities` (user's data) | Yes | Yes | +| `auth` | Yes | Yes | +| `agents` | Yes | Yes | +| `functions.invoke()` | Yes | Yes | +| `integrations` | Yes | Yes | +| `analytics` | Yes | Yes | +| `appLogs` | Yes | Yes | +| `users` | Yes | Yes | +| `asServiceRole.*` | No | Yes | +| `connectors` | No | Yes | + +Backend functions use `Deno.serve()` and `createClientFromRequest(req)` to get a properly authenticated client. diff --git a/evals/fixtures/agent-with-function/skills/base44-sdk/references/QUICK_REFERENCE.md b/evals/fixtures/agent-with-function/skills/base44-sdk/references/QUICK_REFERENCE.md new file mode 100644 index 0000000..d357384 --- /dev/null +++ b/evals/fixtures/agent-with-function/skills/base44-sdk/references/QUICK_REFERENCE.md @@ -0,0 +1,155 @@ +# Base44 SDK Quick Reference + +Compact method signatures for all SDK modules. **Verify against this before writing code.** + +--- + +## Auth (`base44.auth.*`) + +``` +loginViaEmailPassword(email, password, turnstileToken?) → Promise<{access_token, user}> +loginWithProvider('google' | 'microsoft' | 'facebook', fromUrl?) → void +me() → Promise +updateMe(data) → Promise +isAuthenticated() → Promise +logout(redirectUrl?) → void +redirectToLogin(nextUrl) → void # ⚠️ Avoid - prefer custom login UI +register({email, password, turnstile_token?, referral_code?}) → Promise +verifyOtp({email, otpCode}) → Promise +resendOtp(email) → Promise +inviteUser(userEmail, role) → Promise +resetPasswordRequest(email) → Promise +resetPassword({resetToken, newPassword}) → Promise +changePassword({userId, currentPassword, newPassword}) → Promise +setToken(token, saveToStorage?) → void +``` + +--- + +## Entities (`base44.entities.EntityName.*`) + +``` +create(data) → Promise +bulkCreate(dataArray) → Promise +list(sort?, limit?, skip?, fields?) → Promise +filter(query, sort?, limit?, skip?, fields?) → Promise +get(id) → Promise +update(id, data) → Promise +delete(id) → Promise +deleteMany(query) → Promise +importEntities(file) → Promise // frontend only +subscribe(callback) → () => void // returns unsubscribe fn +``` + +**Sort:** Use `-fieldName` for descending (e.g., `-created_date`) + +--- + +## Functions (`base44.functions.*`) + +``` +invoke(functionName, data) → Promise +``` + +**Backend:** Use `base44.asServiceRole.functions.invoke()` for admin access. + +--- + +## Integrations (`base44.integrations.Core.*`) + +``` +InvokeLLM({prompt, add_context_from_internet?, response_json_schema?, file_urls?}) → Promise +GenerateImage({prompt}) → Promise<{url}> +SendEmail({to, subject, body, from_name?}) → Promise +UploadFile({file}) → Promise<{file_url}> +UploadPrivateFile({file}) → Promise<{file_uri}> +CreateFileSignedUrl({file_uri, expires_in?}) → Promise<{signed_url}> +ExtractDataFromUploadedFile({file_url, json_schema}) → Promise +``` + +### Custom Integrations (`base44.integrations.custom.*`) + +``` +call(slug, operationId, {payload?, pathParams?, queryParams?, headers?}?) → Promise<{success, status_code, data}> +``` + +**operationId format:** `"method:/path"` (e.g., `"get:/contacts"`, `"post:/users/{id}"`) + +--- + +## Analytics (`base44.analytics.*`) + +``` +track({eventName, properties?}) → void +``` + +--- + +## App Logs (`base44.appLogs.*`) + +``` +logUserInApp(pageName) → Promise +``` + +--- + +## Users (`base44.users.*`) + +``` +inviteUser(userEmail, role) → Promise // role: 'user' | 'admin' +``` + +--- + +## Connectors (`base44.asServiceRole.connectors.*`) + +**Backend only, service role required.** + +``` +getAccessToken(integrationType) → Promise +``` + +**Types:** `'googlecalendar'`, `'googledrive'`, `'slack'`, `'notion'`, `'salesforce'`, `'hubspot'`, `'linkedin'`, `'tiktok'`, `'github'` + +--- + +## Service Role Access + +**Backend functions only.** Prefix any module with `asServiceRole` for admin access: + +```javascript +base44.asServiceRole.entities.Task.list() +base44.asServiceRole.functions.invoke('name', data) +base44.asServiceRole.connectors.getAccessToken('slack') +``` + +--- + +## Backend Function Template + +```javascript +import { createClientFromRequest } from "@base44/sdk"; + +Deno.serve(async (req) => { + const base44 = createClientFromRequest(req); + const data = await req.json(); + + // User context + const user = await base44.auth.me(); + + // Service role for admin operations + const allRecords = await base44.asServiceRole.entities.Task.list(); + + return Response.json({ success: true }); +}); +``` + +--- + +## Client Initialization (External Apps) + +```javascript +import { createClient } from "@base44/sdk"; + +const base44 = createClient({ appId: "your-app-id" }); // MUST use 'appId' +``` diff --git a/evals/fixtures/agent-with-function/skills/base44-sdk/references/analytics.md b/evals/fixtures/agent-with-function/skills/base44-sdk/references/analytics.md new file mode 100644 index 0000000..d1b2c69 --- /dev/null +++ b/evals/fixtures/agent-with-function/skills/base44-sdk/references/analytics.md @@ -0,0 +1,113 @@ +# Analytics Module + +Track custom events and user activity via `base44.analytics`. + +## Contents +- [Methods](#methods) +- [Examples](#examples) +- [Automatic Tracking](#automatic-tracking) +- [Best Practices](#best-practices) + +## Methods + +| Method | Signature | Description | +|--------|-----------|-------------| +| `track(params)` | `void` | Track a custom event | + +## Examples + +### Track Custom Event + +```javascript +// Track a simple event +base44.analytics.track({ + eventName: "button_clicked" +}); + +// Track event with properties +base44.analytics.track({ + eventName: "purchase_completed", + properties: { + product_id: "prod-123", + amount: 99.99, + currency: "USD" + } +}); +``` + +### Track User Actions + +```javascript +// Track page view +base44.analytics.track({ + eventName: "page_view", + properties: { + page: "/dashboard", + referrer: document.referrer + } +}); + +// Track feature usage +base44.analytics.track({ + eventName: "feature_used", + properties: { + feature: "export_data", + format: "csv" + } +}); +``` + +## Automatic Tracking + +The analytics module automatically tracks: +- **Initialization events**: When the app loads +- **Heartbeat events**: Periodic activity signals +- **Session duration**: Time spent in the app + +These internal events help measure user engagement without manual instrumentation. + +## Best Practices + +1. **Use descriptive event names**: `order_completed` instead of `click` +2. **Include relevant properties**: Add context that helps analyze the event +3. **Be consistent**: Use the same event names and property keys across your app +4. **Don't track sensitive data**: Avoid PII in event properties + +```javascript +// Good: Descriptive with relevant properties +base44.analytics.track({ + eventName: "subscription_started", + properties: { + plan: "pro", + billing_cycle: "annual" + } +}); + +// Avoid: Vague event name, no context +base44.analytics.track({ + eventName: "click" +}); +``` + +## Type Definitions + +```typescript +/** Properties that can be attached to a tracked event. */ +type TrackEventProperties = { + [key: string]: string | number | boolean | null | undefined; +}; + +/** Parameters for the track() method. */ +interface TrackEventParams { + /** The name of the event to track. */ + eventName: string; + /** Optional properties to attach to the event. */ + properties?: TrackEventProperties; +} + +/** The analytics module interface. */ +interface AnalyticsModule { + /** Track a custom event with optional properties. */ + track(params: TrackEventParams): void; +} +``` diff --git a/evals/fixtures/agent-with-function/skills/base44-sdk/references/app-logs.md b/evals/fixtures/agent-with-function/skills/base44-sdk/references/app-logs.md new file mode 100644 index 0000000..0439805 --- /dev/null +++ b/evals/fixtures/agent-with-function/skills/base44-sdk/references/app-logs.md @@ -0,0 +1,78 @@ +# App Logs Module + +Log user activity in your app via `base44.appLogs`. + +## Contents +- [Methods](#methods) +- [Examples](#examples) +- [Use Cases](#use-cases) + +## Methods + +| Method | Signature | Description | +|--------|-----------|-------------| +| `logUserInApp(pageName)` | `Promise` | Log user activity on a page | + +## Examples + +### Log User Activity + +```javascript +// Log when user visits a page +await base44.appLogs.logUserInApp("dashboard"); + +// Log specific page visits +await base44.appLogs.logUserInApp("settings"); +await base44.appLogs.logUserInApp("profile"); + +// Log feature usage +await base44.appLogs.logUserInApp("export-button-click"); +``` + +The page name doesn't have to be an actual page - it can be any string you want to track. + +## Use Cases + +### Track Page Views in React + +```javascript +// Log page views on route change +useEffect(() => { + base44.appLogs.logUserInApp(window.location.pathname); +}, [location.pathname]); +``` + +### Track Feature Usage + +```javascript +// Log when user uses specific features +function handleExport() { + base44.appLogs.logUserInApp("export-data"); + // ... export logic +} + +function handleSettingsChange() { + base44.appLogs.logUserInApp("settings-updated"); + // ... save settings +} +``` + +## Notes + +- Logs appear in the Analytics page of your app dashboard +- App logs track page-level and feature-level activity +- Use `analytics.track()` for custom events with properties, `appLogs.logUserInApp()` for simple page/feature tracking + +## Type Definitions + +```typescript +/** App Logs module for tracking and analyzing app usage. */ +interface AppLogsModule { + /** + * Log user activity in the app. + * @param pageName - Name of the page or section being visited. + * @returns Promise that resolves when the log is recorded. + */ + logUserInApp(pageName: string): Promise; +} +``` diff --git a/evals/fixtures/agent-with-function/skills/base44-sdk/references/auth.md b/evals/fixtures/agent-with-function/skills/base44-sdk/references/auth.md new file mode 100644 index 0000000..5195b3a --- /dev/null +++ b/evals/fixtures/agent-with-function/skills/base44-sdk/references/auth.md @@ -0,0 +1,761 @@ +# Auth Module + +User authentication, registration, and session management via `base44.auth`. + +## Contents +- [TypeScript Types](#typescript-types) +- [Methods](#methods) +- [Examples](#examples) +- [Error Handling](#error-handling) +- [Auth Providers](#auth-providers) +- [Environment Availability](#environment-availability) +- [App Visibility](#app-visibility) +- [Limitations](#limitations) + +--- + +## TypeScript Types + +### User Interface +```typescript +interface User { + id: string; + created_date: string; + updated_date: string; + email: string; + full_name: string | null; + disabled: boolean | null; + is_verified: boolean; + app_id: string; + is_service: boolean; + role: string; + [key: string]: any; // Custom schema fields +} +``` + +### LoginResponse Interface +```typescript +interface LoginResponse { + access_token: string; // JWT token + user: User; // Complete user object +} +``` + +### Parameter Interfaces + +#### RegisterParams +```typescript +interface RegisterParams { + email: string; // Required + password: string; // Required + turnstile_token?: string | null; // Optional: Cloudflare Turnstile for bot protection + referral_code?: string | null; // Optional: Referral code +} +``` + +#### VerifyOtpParams +```typescript +interface VerifyOtpParams { + email: string; // User's email + otpCode: string; // OTP code from email +} +``` + +#### ResetPasswordParams +```typescript +interface ResetPasswordParams { + resetToken: string; // Token from password reset email + newPassword: string; // New password to set +} +``` + +#### ChangePasswordParams +```typescript +interface ChangePasswordParams { + userId: string; // User ID + currentPassword: string; // Current password for verification + newPassword: string; // New password to set +} +``` + +### Provider Type +```typescript +type Provider = 'google' | 'microsoft' | 'facebook'; +``` + +--- + +## Methods + +### Module Interface +```typescript +interface AuthModule { + // User Info + me(): Promise; + updateMe(data: Partial>): Promise; + isAuthenticated(): Promise; + + // Login/Logout + loginViaEmailPassword(email: string, password: string, turnstileToken?: string): Promise; + loginWithProvider(provider: Provider, fromUrl?: string): void; + logout(redirectUrl?: string): void; + redirectToLogin(nextUrl: string): void; + + // Token Management + setToken(token: string, saveToStorage?: boolean): void; + + // Registration + register(params: RegisterParams): Promise; + verifyOtp(params: VerifyOtpParams): Promise; + resendOtp(email: string): Promise; + + // User Management + inviteUser(userEmail: string, role: string): Promise; + + // Password Management + resetPasswordRequest(email: string): Promise; + resetPassword(params: ResetPasswordParams): Promise; + changePassword(params: ChangePasswordParams): Promise; +} +``` + +### Method Reference Table + +| Method | Parameters | Return Type | Description | +|--------|-----------|-------------|-------------| +| `register()` | `params: RegisterParams` | `Promise` | Create new user account | +| `loginViaEmailPassword()` | `email: string, password: string, turnstileToken?: string` | `Promise` | Authenticate with email/password | +| `loginWithProvider()` | `provider: Provider, fromUrl?: string` | `void` | Initiate OAuth login flow | +| `me()` | None | `Promise` | Get current authenticated user | +| `updateMe()` | `data: Partial` | `Promise` | Update current user's profile | +| `logout()` | `redirectUrl?: string` | `void` | Clear session, optionally redirect | +| `redirectToLogin()` | `nextUrl: string` | `void` | ⚠️ **Avoid** - Prefer custom login UI with `loginViaEmailPassword()` or `loginWithProvider()` | +| `isAuthenticated()` | None | `Promise` | Check if user is logged in | +| `setToken()` | `token: string, saveToStorage?: boolean` | `void` | Manually set auth token | +| `inviteUser()` | `userEmail: string, role: string` | `Promise` | Send invitation email | +| `verifyOtp()` | `params: VerifyOtpParams` | `Promise` | Verify OTP code | +| `resendOtp()` | `email: string` | `Promise` | Resend OTP code | +| `resetPasswordRequest()` | `email: string` | `Promise` | Request password reset | +| `resetPassword()` | `params: ResetPasswordParams` | `Promise` | Reset password with token | +| `changePassword()` | `params: ChangePasswordParams` | `Promise` | Change user password | + +--- + +## Examples + +### Register New User (Complete Flow) + +Registration requires email verification before login. Complete flow: + +1. **Register** - Create the user account +2. **Verification email sent** - User receives an OTP code +3. **Verify OTP** - User enters code to verify email +4. **Login** - User can now log in + +```javascript +try { + // Step 1: Register the user + await base44.auth.register({ + email: "user@example.com", + password: "securePassword123", + referral_code: "OPTIONAL_CODE", // optional + turnstile_token: "CAPTCHA_TOKEN" // optional, for bot protection + }); + console.log('Registration successful. Check email for OTP code.'); + + // Step 2: User receives email with OTP code (e.g., "123456") + + // Step 3: Verify the OTP code + await base44.auth.verifyOtp({ + email: "user@example.com", + otpCode: "123456" // code from verification email + }); + console.log('Email verified successfully.'); + + // Step 4: Now the user can log in + const loginResponse = await base44.auth.loginViaEmailPassword( + "user@example.com", + "securePassword123" + ); + console.log('Login successful:', loginResponse.user); + +} catch (error) { + console.error('Registration flow failed:', error.message); + // Handle specific errors (see Error Handling section) +} +``` + +> **Important**: Users cannot log in until they complete OTP verification. Attempting to call `loginViaEmailPassword` before verification will fail. + +### Login with Email/Password + +```javascript +try { + const response = await base44.auth.loginViaEmailPassword( + "user@example.com", + "password123", + turnstileToken // optional: for bot protection + ); + + console.log('Login successful'); + console.log('User:', response.user); + console.log('Token:', response.access_token); + + // JWT is automatically stored for subsequent requests + +} catch (error) { + console.error('Login failed:', error.message); + if (error.status === 401) { + console.error('Invalid credentials'); + } else if (error.status === 403) { + console.error('Email not verified. Please check your email for OTP.'); + } +} +``` + +### Login with OAuth Provider + +```javascript +// Redirect to Google OAuth +base44.auth.loginWithProvider('google'); + +// Redirect to Google OAuth and return to current page after +base44.auth.loginWithProvider('google', window.location.href); + +// Supported providers: 'google', 'microsoft', 'facebook' +base44.auth.loginWithProvider('microsoft'); +``` + +### Get Current User + +```javascript +try { + const user = await base44.auth.me(); + + if (user) { + console.log('User ID:', user.id); + console.log('Email:', user.email); + console.log('Name:', user.full_name); + console.log('Role:', user.role); + console.log('Verified:', user.is_verified); + } else { + console.log('User not authenticated'); + } + +} catch (error) { + console.error('Failed to fetch user:', error.message); + if (error.status === 401) { + // Token expired or invalid - navigate to your custom login page + navigate('/login'); + } +} +``` + +### Update User Profile + +```javascript +try { + const updatedUser = await base44.auth.updateMe({ + full_name: "John Doe", + // Custom schema fields can be updated here + phone: "+1234567890", + preferences: { theme: "dark" } + }); + + console.log('Profile updated:', updatedUser); + +} catch (error) { + console.error('Profile update failed:', error.message); + if (error.status === 400) { + console.error('Invalid data provided'); + } else if (error.status === 401) { + console.error('Authentication required'); + navigate('/login'); + } +} +``` + +### Check Authentication Status + +```javascript +try { + const isLoggedIn = await base44.auth.isAuthenticated(); + + if (isLoggedIn) { + // Show authenticated UI + console.log('User is authenticated'); + } else { + // Show login button + console.log('User is not authenticated'); + } + +} catch (error) { + console.error('Failed to check authentication:', error.message); + // On error, treat as not authenticated +} +``` + +### Logout + +```javascript +// Simple logout +base44.auth.logout(); + +// Logout and redirect to goodbye page +base44.auth.logout("/goodbye"); + +// Logout and redirect to homepage +base44.auth.logout("/"); +``` + +### Protected Route Pattern + +```javascript +// Using a navigation function (e.g., React Router's useNavigate, Next.js router) +async function requireAuth(navigate) { + try { + const user = await base44.auth.me(); + + if (!user) { + // Navigate to your custom login page + navigate('/login', { state: { returnTo: window.location.pathname } }); + return null; + } + + return user; + + } catch (error) { + console.error('Authentication check failed:', error.message); + navigate('/login', { state: { returnTo: window.location.pathname } }); + return null; + } +} + +// Usage in your app +async function loadProtectedPage(navigate) { + const user = await requireAuth(navigate); + if (!user) { + // Will navigate to login + return; + } + + // Continue with authenticated logic + console.log('Welcome,', user.full_name); +} +``` + +### Set Authentication Token + +```javascript +// SECURITY WARNING: Never hardcode tokens or expose them in client code +// Tokens should only be received from secure authentication flows + +// Set token and save to localStorage (default) +base44.auth.setToken(receivedToken); + +// Set token without saving to localStorage (temporary session) +base44.auth.setToken(receivedToken, false); + +// Verify token was set +try { + const isAuthenticated = await base44.auth.isAuthenticated(); + if (!isAuthenticated) { + console.error('Token validation failed'); + } +} catch (error) { + console.error('Failed to set token:', error.message); +} +``` + +### Invite User to Application + +```javascript +try { + // Note: Typically requires admin privileges + const response = await base44.auth.inviteUser( + "newuser@example.com", + "user" // or "admin" + ); + + console.log('Invitation sent successfully'); + +} catch (error) { + console.error('Failed to invite user:', error.message); + if (error.status === 403) { + console.error('Insufficient permissions to invite users'); + } else if (error.status === 400) { + console.error('Invalid email or role'); + } +} +``` + +### OTP Verification + +```javascript +try { + // Verify OTP code sent to user's email + await base44.auth.verifyOtp({ + email: "user@example.com", + otpCode: "123456" + }); + + console.log('OTP verified successfully'); + +} catch (error) { + console.error('OTP verification failed:', error.message); + if (error.status === 400) { + console.error('Invalid or expired OTP code'); + } else if (error.status === 429) { + console.error('Too many attempts. Please try again later.'); + } +} + +// Resend OTP if needed +try { + await base44.auth.resendOtp("user@example.com"); + console.log('OTP resent successfully'); + +} catch (error) { + console.error('Failed to resend OTP:', error.message); + if (error.status === 429) { + console.error('Too many requests. Please wait before trying again.'); + } +} +``` + +### Password Reset Flow + +```javascript +// Step 1: Request password reset +try { + await base44.auth.resetPasswordRequest("user@example.com"); + console.log('Password reset email sent. Check your inbox.'); + +} catch (error) { + console.error('Password reset request failed:', error.message); + if (error.status === 429) { + console.error('Too many requests. Please try again later.'); + } + // Note: For security, don't reveal if email exists +} + +// Step 2: Reset password with token from email +try { + await base44.auth.resetPassword({ + resetToken: "token-from-email", + newPassword: "newSecurePassword123" + }); + + console.log('Password reset successfully. You can now log in.'); + +} catch (error) { + console.error('Password reset failed:', error.message); + if (error.status === 400) { + console.error('Invalid or expired reset token'); + } else if (error.status === 422) { + console.error('Password does not meet requirements'); + } +} +``` + +### Change Password + +```javascript +try { + const currentUser = await base44.auth.me(); + + if (!currentUser) { + throw new Error('User must be authenticated to change password'); + } + + await base44.auth.changePassword({ + userId: currentUser.id, + currentPassword: "oldPassword123", + newPassword: "newSecurePassword456" + }); + + console.log('Password changed successfully'); + +} catch (error) { + console.error('Password change failed:', error.message); + if (error.status === 401) { + console.error('Current password is incorrect'); + } else if (error.status === 422) { + console.error('New password does not meet requirements'); + } else if (error.status === 403) { + console.error('Not authorized to change this password'); + } +} +``` + +--- + +## Error Handling + +### Common Error Scenarios + +The auth module can throw various errors. Here are common scenarios and how to handle them: + +#### Authentication Errors (401/403) +```javascript +try { + const user = await base44.auth.me(); +} catch (error) { + if (error.status === 401) { + // Token expired or invalid - navigate to your custom login page + navigate('/login'); + } else if (error.status === 403) { + // Email not verified or insufficient permissions + console.error('Access denied:', error.message); + } +} +``` + +#### Validation Errors (400/422) +```javascript +try { + await base44.auth.register({ + email: "invalid-email", + password: "weak" + }); +} catch (error) { + if (error.status === 400) { + console.error('Invalid input:', error.message); + // Handle validation errors + } else if (error.status === 422) { + console.error('Data validation failed:', error.details); + } +} +``` + +#### Rate Limiting (429) +```javascript +try { + await base44.auth.resendOtp("user@example.com"); +} catch (error) { + if (error.status === 429) { + console.error('Too many requests. Please wait before trying again.'); + // Show countdown or disable button temporarily + } +} +``` + +#### Generic Error Handler +```javascript +function handleAuthError(error) { + switch (error.status) { + case 400: + return 'Invalid input. Please check your data.'; + case 401: + return 'Authentication required. Redirecting to login...'; + case 403: + return 'Access denied. You may need to verify your email.'; + case 404: + return 'Resource not found.'; + case 422: + return 'Validation failed. Please check your input.'; + case 429: + return 'Too many requests. Please try again later.'; + case 500: + return 'Server error. Please try again.'; + default: + return 'An unexpected error occurred.'; + } +} + +// Usage +try { + await base44.auth.loginViaEmailPassword(email, password); +} catch (error) { + console.error(handleAuthError(error)); +} +``` + +--- + +## Auth Providers + +Configure authentication providers in your app dashboard: + +### Available Providers + +**Built-in (All Plans):** +- **Email/Password** - Default, always enabled +- **Google** - OAuth authentication +- **Microsoft** - OAuth authentication +- **Facebook** - OAuth authentication + +**SSO Providers (Elite Plan):** +- **Okta** +- **Azure AD** +- **GitHub** + +### Using OAuth Providers + +```javascript +// Initiate OAuth login flow +base44.auth.loginWithProvider('google'); + +// Return to specific page after authentication +base44.auth.loginWithProvider('microsoft', '/dashboard'); + +// Supported values: 'google', 'microsoft', 'facebook' +``` + +--- + +## Environment Availability + +| Environment | Availability | Notes | +|------------|--------------|-------| +| **Frontend** | ✅ Yes | All methods available | +| **Backend Functions** | ✅ Yes | Use `createClientFromRequest(req)` for authenticated client | +| **Service Role** | ❌ No | Auth methods not available in service role context | + +### Frontend Usage +```javascript +// Standard browser usage +const user = await base44.auth.me(); +``` + +### Backend Functions Usage +```javascript +import { createClientFromRequest } from "@base44/sdk"; + +Deno.serve(async (req) => { + const base44 = createClientFromRequest(req); + + // Get user from request context + const user = await base44.auth.me(); + + // User operations here... + return Response.json({ user }); +}); +``` + +--- + +## App Visibility + +Control who can access your app in the app settings: + +### Public Apps +- No login required for basic access +- Users can view public content without authentication +- Authenticated users get additional features/data + +### Private Apps +- Login required to access any content +- Unauthenticated users are automatically redirected to login +- All content is protected by default + +--- + +## Limitations + +### Authentication UI Options +- **Recommended:** Build custom login/signup UI using `loginViaEmailPassword()` and `loginWithProvider()` for full control over user experience and branding +- **Alternative:** `redirectToLogin()` uses Base44's hosted authentication pages with limited customization + +### Hosted Login (via redirectToLogin) +- `redirectToLogin()` shows both login and signup options on the same page +- No separate `redirectToSignup()` method +- Users can switch between login/signup on the hosted page +- ⚠️ **Note:** Prefer building custom login UI for better user experience + +### Password Requirements +- Minimum length and complexity requirements enforced +- Requirements not exposed via API +- Validation errors returned when requirements not met + +### Rate Limiting +- OTP requests are rate-limited to prevent abuse +- Password reset requests are rate-limited +- Login attempts may be rate-limited with Turnstile protection + +### Token Management +- JWTs are automatically stored in localStorage by default +- Token expiration and refresh not exposed in API +- Call `me()` or `isAuthenticated()` to verify token validity + +--- + +## Best Practices + +### 1. Always Handle Errors +```javascript +try { + await base44.auth.loginViaEmailPassword(email, password); +} catch (error) { + // Always handle authentication errors + console.error('Login failed:', error.message); +} +``` + +### 2. Verify Authentication Before Protected Actions +```javascript +const user = await requireAuth(); +if (!user) return; // Will redirect to login +// Proceed with authenticated action +``` + +### 3. Use Type Safety with TypeScript +```typescript +import type { User, LoginResponse } from '@base44/sdk'; + +const user: User = await base44.auth.me(); +const response: LoginResponse = await base44.auth.loginViaEmailPassword(email, password); +``` + +### 4. Don't Hardcode Credentials +```javascript +// ❌ BAD +base44.auth.setToken("hardcoded-token-here"); + +// ✅ GOOD +const token = await secureAuthFlow(); +base44.auth.setToken(token); +``` + +### 5. Provide User Feedback +```javascript +try { + await base44.auth.register(params); + showSuccessMessage('Registration successful! Check your email for verification code.'); +} catch (error) { + showErrorMessage('Registration failed: ' + error.message); +} +``` + +### 6. Handle Token Expiration Gracefully +```javascript +try { + const user = await base44.auth.me(); +} catch (error) { + if (error.status === 401) { + // Clear local state and navigate to login + localStorage.clear(); + navigate('/login'); + } +} +``` + +### 7. Build Custom Login UI (Recommended) +```javascript +// ✅ RECOMMENDED - Custom login form with direct methods +const handleLogin = async (email, password) => { + try { + const { user } = await base44.auth.loginViaEmailPassword(email, password); + navigate('/dashboard'); + } catch (error) { + setError(error.message); + } +}; + +const handleGoogleLogin = () => { + base44.auth.loginWithProvider('google', '/dashboard'); +}; + +// ❌ AVOID - Redirecting to hosted login page +// base44.auth.redirectToLogin(window.location.href); +``` diff --git a/evals/fixtures/agent-with-function/skills/base44-sdk/references/base44-agents.md b/evals/fixtures/agent-with-function/skills/base44-sdk/references/base44-agents.md new file mode 100644 index 0000000..ee55dee --- /dev/null +++ b/evals/fixtures/agent-with-function/skills/base44-sdk/references/base44-agents.md @@ -0,0 +1,364 @@ +# Agents Module + +AI agent conversations and messages via `base44.agents`. + +> **Note:** This module requires a logged-in user. All agent methods work in the context of the authenticated user. + +## Contents +- [Concepts](#concepts) +- [Methods](#methods) +- [Examples](#examples) (Create, Get Conversations, List, Subscribe, Send Message, WhatsApp) +- [Message Structure](#message-structure) +- [Conversation Structure](#conversation-structure) +- [Common Patterns](#common-patterns) + +## Concepts + +- **Conversation**: A dialogue between user and an AI agent. Has unique ID, agent name, user reference, and metadata. +- **Message**: Single message in a conversation. Has role (`user`, `assistant`, `system`), content, timestamps, and optional metadata. + +## Methods + +| Method | Signature | Description | +|--------|-----------|-------------| +| `createConversation(params)` | `Promise` | Create a new conversation with an agent | +| `getConversations()` | `Promise` | Get all user's conversations | +| `getConversation(id)` | `Promise` | Get conversation with messages | +| `listConversations(filterParams)` | `Promise` | Filter/sort/paginate conversations | +| `subscribeToConversation(id, onUpdate?)` | `() => void` | Real-time updates via WebSocket (returns unsubscribe function) | +| `addMessage(conversation, message)` | `Promise` | Send a message | +| `getWhatsAppConnectURL(agentName)` | `string` | Get WhatsApp connection URL for agent | + +## Examples + +### Create Conversation + +```javascript +const conversation = await base44.agents.createConversation({ + agent_name: "support-agent", + metadata: { + order_id: "ORD-123", + category: "billing" + } +}); + +console.log(conversation.id); +``` + +### Get All Conversations + +```javascript +const conversations = await base44.agents.getConversations(); + +conversations.forEach(conv => { + console.log(conv.id, conv.agent_name, conv.created_date); +}); +``` + +### Get Single Conversation (with messages) + +```javascript +const conversation = await base44.agents.getConversation("conv-id-123"); + +console.log(conversation.messages); +``` + +### List with Filters + +```javascript +// Using filterParams object with q, sort, limit, skip, fields +const recent = await base44.agents.listConversations({ + q: { agent_name: "support-agent" }, + sort: "-created_date", + limit: 10, + skip: 0 +}); + +// Filter by metadata +const highPriority = await base44.agents.listConversations({ + q: { + agent_name: "support-agent", + "metadata.priority": "high" + }, + sort: "-updated_date", + limit: 20 +}); +``` + +### Subscribe to Updates (Real-time) + +```javascript +const unsubscribe = base44.agents.subscribeToConversation( + "conv-id-123", + (updatedConversation) => { + // Called when new messages arrive + console.log("New messages:", updatedConversation.messages); + } +); + +// Later: unsubscribe +unsubscribe(); +``` + +### Send a Message + +```javascript +const conversation = await base44.agents.getConversation("conv-id-123"); + +await base44.agents.addMessage(conversation, { + role: "user", + content: "What's the weather like today?" +}); +``` + +### Get WhatsApp Connection URL + +```javascript +const whatsappUrl = base44.agents.getWhatsAppConnectURL("support-agent"); +// Returns URL for users to connect with agent via WhatsApp +console.log(whatsappUrl); +``` + +## Message Structure + +```javascript +{ + role: "user" | "assistant" | "system", + content: "Message text or structured object", + created_date: "2024-01-15T10:30:00Z", + updated_date: "2024-01-15T10:30:00Z", + + // Optional fields + reasoning: { + content: "Agent's reasoning process", + timing: 1500 + }, + tool_calls: [{ + name: "search", + arguments: { query: "weather" }, + result: { ... }, + status: "success" + }], + file_urls: ["https://..."], + usage: { + prompt_tokens: 150, + completion_tokens: 50 + }, + metadata: { ... }, + custom_context: { ... } +} +``` + +## Conversation Structure + +```javascript +{ + id: "conv-id-123", + app_id: "app-id", + agent_name: "support-agent", + created_by_id: "user-id", + created_date: "2024-01-15T10:00:00Z", + updated_date: "2024-01-15T10:30:00Z", + messages: [ ... ], + metadata: { ... } +} +``` + +## Common Patterns + +### Chat Interface + +```javascript +// Load conversation +const conv = await base44.agents.getConversation(conversationId); +setMessages(conv.messages); + +// Subscribe to updates +const unsubscribe = base44.agents.subscribeToConversation(conversationId, (updated) => { + setMessages(updated.messages); +}); + +// Send message +async function sendMessage(text) { + await base44.agents.addMessage(conv, { role: "user", content: text }); +} + +// Cleanup on unmount +return () => unsubscribe(); +``` + +## Type Definitions + +### AgentConversation + +```typescript +/** An agent conversation containing messages exchanged with an AI agent. */ +interface AgentConversation { + /** Unique identifier for the conversation. */ + id: string; + /** Application ID. */ + app_id: string; + /** Name of the agent in this conversation. */ + agent_name: string; + /** ID of the user who created the conversation. */ + created_by_id: string; + /** When the conversation was created. */ + created_date: string; + /** When the conversation was last updated. */ + updated_date: string; + /** Array of messages in the conversation. */ + messages: AgentMessage[]; + /** Optional metadata associated with the conversation. */ + metadata?: Record; +} +``` + +### AgentMessage + +```typescript +/** A message in an agent conversation. */ +interface AgentMessage { + /** Unique identifier for the message. */ + id: string; + /** Role of the message sender. */ + role: "user" | "assistant" | "system"; + /** When the message was created. */ + created_date: string; + /** When the message was last updated. */ + updated_date: string; + /** Message content. */ + content?: string | Record; + /** Optional reasoning information for the message. */ + reasoning?: AgentMessageReasoning | null; + /** URLs to files attached to the message. */ + file_urls?: string[]; + /** Tool calls made by the agent. */ + tool_calls?: AgentMessageToolCall[]; + /** Token usage statistics. */ + usage?: AgentMessageUsage; + /** Whether the message is hidden from the user. */ + hidden?: boolean; + /** Custom context provided with the message. */ + custom_context?: AgentMessageCustomContext[]; + /** Model used to generate the message. */ + model?: string; + /** Checkpoint ID for the message. */ + checkpoint_id?: string; + /** Metadata about when and by whom the message was created. */ + metadata?: AgentMessageMetadata; +} +``` + +### Supporting Types + +```typescript +/** Reasoning information for an agent message. */ +interface AgentMessageReasoning { + /** When reasoning started. */ + start_date: string; + /** When reasoning ended. */ + end_date?: string; + /** Reasoning content. */ + content: string; +} + +/** A tool call made by the agent. */ +interface AgentMessageToolCall { + /** Tool call ID. */ + id: string; + /** Name of the tool called. */ + name: string; + /** Arguments passed to the tool as JSON string. */ + arguments_string: string; + /** Status of the tool call. */ + status: "running" | "success" | "error" | "stopped"; + /** Results from the tool call. */ + results?: string; +} + +/** Token usage statistics for an agent message. */ +interface AgentMessageUsage { + /** Number of tokens in the prompt. */ + prompt_tokens?: number; + /** Number of tokens in the completion. */ + completion_tokens?: number; +} + +/** Custom context provided with an agent message. */ +interface AgentMessageCustomContext { + /** Context message. */ + message: string; + /** Associated data for the context. */ + data: Record; + /** Type of context. */ + type: string; +} + +/** Metadata about when and by whom a message was created. */ +interface AgentMessageMetadata { + /** When the message was created. */ + created_date: string; + /** Email of the user who created the message. */ + created_by_email: string; + /** Full name of the user who created the message. */ + created_by_full_name: string; +} +``` + +### CreateConversationParams + +```typescript +/** Parameters for creating a new conversation. */ +interface CreateConversationParams { + /** The name of the agent to create a conversation with. */ + agent_name: string; + /** Optional metadata to attach to the conversation. */ + metadata?: Record; +} +``` + +### ModelFilterParams + +```typescript +/** Parameters for filtering, sorting, and paginating conversations. */ +interface ModelFilterParams { + /** Query object with field-value pairs for filtering. */ + q?: Record; + /** Sort parameter (e.g., "-created_date" for descending). */ + sort?: string | null; + /** Maximum number of results to return. */ + limit?: number | null; + /** Number of results to skip for pagination. */ + skip?: number | null; + /** Array of field names to include in the response. */ + fields?: string[] | null; +} +``` + +### AgentsModule + +```typescript +/** Agents module for managing AI agent conversations. */ +interface AgentsModule { + /** Gets all conversations from all agents in the app. */ + getConversations(): Promise; + + /** Gets a specific conversation by ID. */ + getConversation(conversationId: string): Promise; + + /** Lists conversations with filtering, sorting, and pagination. */ + listConversations(filterParams: ModelFilterParams): Promise; + + /** Creates a new conversation with an agent. */ + createConversation(conversation: CreateConversationParams): Promise; + + /** Adds a message to a conversation. */ + addMessage(conversation: AgentConversation, message: Partial): Promise; + + /** Subscribes to real-time updates for a conversation. Returns unsubscribe function. */ + subscribeToConversation(conversationId: string, onUpdate?: (conversation: AgentConversation) => void): () => void; + + /** Gets WhatsApp connection URL for an agent. */ + getWhatsAppConnectURL(agentName: string): string; +} +``` diff --git a/evals/fixtures/agent-with-function/skills/base44-sdk/references/client.md b/evals/fixtures/agent-with-function/skills/base44-sdk/references/client.md new file mode 100644 index 0000000..554bf2b --- /dev/null +++ b/evals/fixtures/agent-with-function/skills/base44-sdk/references/client.md @@ -0,0 +1,257 @@ +# Client Setup + +How to create and configure the Base44 client. + +## Contents +- [In Base44-Generated Apps](#in-base44-generated-apps) +- [In External Apps](#in-external-apps) +- [In Backend Functions](#in-backend-functions) +- [Authentication Modes](#authentication-modes) (Anonymous, User, Service Role) +- [Available Modules](#available-modules) +- [Client Methods](#client-methods) +- [Client Configuration Options](#client-configuration-options) + +## In Base44-Generated Apps + +The client is pre-configured and available as `base44`. Just use it: + +```javascript +const tasks = await base44.entities.Task.list(); +``` + +## In External Apps + +Install the SDK and create a client: + +```bash +npm install @base44/sdk +``` + +```javascript +import { createClient } from "@base44/sdk"; + +// IMPORTANT: The parameter name is 'appId' (NOT 'clientId', NOT 'id') +// IMPORTANT: onError must be nested inside 'options' object +const base44 = createClient({ + appId: "your-app-id", // Required: Use 'appId' parameter + token: "optional-user-token", // Optional: for pre-authenticated requests + options: { // Optional: configuration options + onError: (error) => { // Optional: error handler (must be in options) + console.error("Base44 error:", error); + } + } +}); +``` + +**Common Mistakes:** +- ❌ `createClient({ clientId: "..." })` - WRONG parameter name +- ❌ `createClient({ id: "..." })` - WRONG parameter name +- ❌ `createClient({ appId: "...", onError: ... })` - WRONG: onError must be in options +- ✅ `createClient({ appId: "..." })` - CORRECT parameter name +- ✅ `createClient({ appId: "...", options: { onError: ... } })` - CORRECT: onError in options + +## In Backend Functions + +Use `createClientFromRequest` to get a client with the caller's auth context: + +```javascript +import { createClientFromRequest } from "@base44/sdk"; + +Deno.serve(async (req) => { + const base44 = createClientFromRequest(req); + + // Client inherits authentication from the request + const user = await base44.auth.me(); + + return Response.json({ user }); +}); +``` + +## Authentication Modes + +| Mode | How to Get | Permissions | +|------|-----------|-------------| +| **Anonymous** | `createClient({ appId })` without token | Public data only | +| **User** | After `loginViaEmailPassword()` or via `createClientFromRequest` | User's own data | +| **Service Role** | `base44.asServiceRole.*` in backend | Full admin access | + +## Anonymous Mode + +No authentication. Can only access public resources. + +```javascript +const base44 = createClient({ appId: "your-app-id" }); + +// Only works if Task entity allows anonymous read +const publicTasks = await base44.entities.Task.list(); +``` + +## User Mode + +After user logs in, the client automatically includes their token. + +```javascript +const base44 = createClient({ appId: "your-app-id" }); + +// Login sets the token +await base44.auth.loginViaEmailPassword("user@example.com", "password"); + +// Subsequent requests are authenticated +const user = await base44.auth.me(); +const myTasks = await base44.entities.Task.list(); // filtered by permissions +``` + +## Service Role Mode + +Admin-level access. **Backend only.** + +```javascript +// Inside a backend function +Deno.serve(async (req) => { + const base44 = createClientFromRequest(req); + + // User mode - respects permissions + const myTasks = await base44.entities.Task.list(); + + // Service role - bypasses permissions + const allTasks = await base44.asServiceRole.entities.Task.list(); + const allUsers = await base44.asServiceRole.entities.User.list(); + const oauthToken = await base44.asServiceRole.connectors.getAccessToken("slack"); + + return Response.json({ myTasks, allTasks }); +}); +``` + +## Available Modules + +The client exposes these modules: + +```javascript +base44.entities // CRUD operations +base44.auth // Authentication +base44.agents // AI conversations +base44.functions // Backend function invocation +base44.integrations // Third-party services +base44.analytics // Event tracking +base44.appLogs // App usage logging +base44.users // User invitations + +// Service role only (backend) +base44.asServiceRole.entities +base44.asServiceRole.agents +base44.asServiceRole.functions +base44.asServiceRole.integrations +base44.asServiceRole.appLogs +base44.asServiceRole.connectors +``` + +## Client Methods + +The client provides these methods: + +```javascript +// Set authentication token for all subsequent requests +base44.setToken(newToken); + +// Cleanup WebSocket connections (call when done with client) +base44.cleanup(); +``` + +### setToken + +Updates the authentication token for all subsequent API requests and WebSocket connections. + +```javascript +// After receiving a token (e.g., from external auth) +base44.setToken("new-jwt-token"); +``` + +### cleanup + +Disconnects WebSocket connections. Call when you're done with the client or when the component unmounts. + +```javascript +// Cleanup on component unmount (React example) +useEffect(() => { + return () => base44.cleanup(); +}, []); +``` + +## Client Configuration Options + +```javascript +createClient({ + appId: "your-app-id", // Required: MUST use 'appId' (not 'clientId' or 'id') + token: "jwt-token", // Optional: pre-set auth token + options: { // Optional: configuration options + onError: (error) => {} // Optional: global error handler (must be in options) + } +}); +``` + +**⚠️ Critical:** +- The parameter name is `appId`, not `clientId` or `id`. Using the wrong parameter name will cause errors. +- The `onError` handler must be nested inside the `options` object, not at the top level. + +## Type Definitions + +### CreateClientConfig + +```typescript +/** Configuration for creating a Base44 client. */ +interface CreateClientConfig { + /** The Base44 app ID (required). */ + appId: string; + /** User authentication token. Used to authenticate as a specific user. */ + token?: string; + /** Service role authentication token (backend only). */ + serviceToken?: string; + /** Additional client options. */ + options?: CreateClientOptions; +} + +/** Options for creating a Base44 client. */ +interface CreateClientOptions { + /** Optional error handler called whenever an API error occurs. */ + onError?: (error: Error) => void; +} +``` + +### Base44Client + +```typescript +/** The Base44 client instance. */ +interface Base44Client { + /** Entities module for CRUD operations on your data models. */ + entities: EntitiesModule; + /** Integrations module for calling pre-built integration endpoints. */ + integrations: IntegrationsModule; + /** Auth module for user authentication and management. */ + auth: AuthModule; + /** Functions module for invoking custom backend functions. */ + functions: FunctionsModule; + /** Agents module for managing AI agent conversations. */ + agents: AgentsModule; + /** App logs module for tracking app usage. */ + appLogs: AppLogsModule; + /** Analytics module for tracking custom events. */ + analytics: AnalyticsModule; + + /** Cleanup function to disconnect WebSocket connections. */ + cleanup(): void; + + /** Sets a new authentication token for all subsequent requests. */ + setToken(newToken: string): void; + + /** Provides access to modules with elevated service role permissions (backend only). */ + readonly asServiceRole: { + entities: EntitiesModule; + integrations: IntegrationsModule; + connectors: ConnectorsModule; + functions: FunctionsModule; + agents: AgentsModule; + appLogs: AppLogsModule; + cleanup(): void; + }; +} +``` diff --git a/evals/fixtures/agent-with-function/skills/base44-sdk/references/connectors.md b/evals/fixtures/agent-with-function/skills/base44-sdk/references/connectors.md new file mode 100644 index 0000000..f3fbcb4 --- /dev/null +++ b/evals/fixtures/agent-with-function/skills/base44-sdk/references/connectors.md @@ -0,0 +1,113 @@ +# Connectors Module + +OAuth token management for external services. **Service role only** (backend functions). + +## Method + +```javascript +base44.asServiceRole.connectors.getAccessToken(integrationType): Promise +``` + +Returns a raw OAuth access token for the specified service. + +## Supported Services + +| Integration Type | Service | +|-----------------|---------| +| `"googlecalendar"` | Google Calendar | +| `"googledrive"` | Google Drive | +| `"slack"` | Slack | +| `"notion"` | Notion | +| `"salesforce"` | Salesforce | +| `"hubspot"` | HubSpot | +| `"linkedin"` | LinkedIn | +| `"tiktok"` | TikTok | +| `"github"` | GitHub | + +## Example Usage + +```javascript +// Backend function only +Deno.serve(async (req) => { + const base44 = createClientFromRequest(req); + + // Get OAuth token for Slack + const slackToken = await base44.asServiceRole.connectors.getAccessToken("slack"); + + // Use token directly with Slack API + const response = await fetch("https://slack.com/api/chat.postMessage", { + method: "POST", + headers: { + "Authorization": `Bearer ${slackToken}`, + "Content-Type": "application/json" + }, + body: JSON.stringify({ + channel: "#general", + text: "Hello from Base44!" + }) + }); + + return Response.json(await response.json()); +}); +``` + +## Google Calendar Example + +```javascript +Deno.serve(async (req) => { + const base44 = createClientFromRequest(req); + + const token = await base44.asServiceRole.connectors.getAccessToken("googlecalendar"); + + // List upcoming events + const response = await fetch( + "https://www.googleapis.com/calendar/v3/calendars/primary/events?" + + new URLSearchParams({ + maxResults: "10", + orderBy: "startTime", + singleEvents: "true", + timeMin: new Date().toISOString() + }), + { + headers: { "Authorization": `Bearer ${token}` } + } + ); + + const events = await response.json(); + return Response.json(events); +}); +``` + +## Setup Requirements + +1. **Builder plan** or higher +2. **Backend functions** enabled +3. **Connector configured** in Base44 dashboard (OAuth flow completed) + +## Important Notes + +- **One account per connector per app**: All users share the same connected account +- **Backend only**: `connectors` module not available in frontend code +- **Service role required**: Must use `base44.asServiceRole.connectors` +- **You handle the API calls**: Base44 only provides the token; you make the actual API requests +- **Token refresh**: Base44 handles token refresh automatically + +## Type Definitions + +```typescript +/** + * The type of external integration/connector. + * Examples: 'googlecalendar', 'slack', 'github', 'notion', etc. + */ +type ConnectorIntegrationType = string; + +/** Connectors module for managing OAuth tokens for external services. */ +interface ConnectorsModule { + /** + * Retrieves an OAuth access token for a specific external integration type. + * @param integrationType - The type of integration (e.g., 'googlecalendar', 'slack'). + * @returns Promise resolving to the access token string. + */ + getAccessToken(integrationType: ConnectorIntegrationType): Promise; +} +``` diff --git a/evals/fixtures/agent-with-function/skills/base44-sdk/references/entities.md b/evals/fixtures/agent-with-function/skills/base44-sdk/references/entities.md new file mode 100644 index 0000000..a7bdf35 --- /dev/null +++ b/evals/fixtures/agent-with-function/skills/base44-sdk/references/entities.md @@ -0,0 +1,253 @@ +# Entities Module + +CRUD operations on data models. Access via `base44.entities.EntityName.method()`. + +## Contents +- [Methods](#methods) +- [Examples](#examples) (Create, Bulk Create, List, Filter, Get, Update, Delete, Subscribe) +- [User Entity](#user-entity) +- [Service Role Access](#service-role-access) +- [Permissions](#permissions) + +## Methods + +| Method | Signature | Description | +|--------|-----------|-------------| +| `create(data)` | `Promise` | Create one record | +| `bulkCreate(dataArray)` | `Promise` | Create multiple records | +| `list(sort?, limit?, skip?, fields?)` | `Promise` | Get all records (paginated) | +| `filter(query, sort?, limit?, skip?, fields?)` | `Promise` | Get records matching conditions | +| `get(id)` | `Promise` | Get single record by ID | +| `update(id, data)` | `Promise` | Update record (partial update) | +| `delete(id)` | `Promise` | Delete record by ID | +| `deleteMany(query)` | `Promise` | Delete all matching records | +| `importEntities(file)` | `Promise` | Import from CSV (frontend only) | +| `subscribe(callback)` | `() => void` | Subscribe to realtime updates (returns unsubscribe function) | + +## Examples + +### Create + +```javascript +const task = await base44.entities.Task.create({ + title: "Complete documentation", + status: "pending", + dueDate: "2024-12-31" +}); +``` + +### Bulk Create + +```javascript +const tasks = await base44.entities.Task.bulkCreate([ + { title: "Task 1", status: "pending" }, + { title: "Task 2", status: "pending" } +]); +``` + +### List with Pagination + +```javascript +// Get first 10 records, sorted by created_date descending +const tasks = await base44.entities.Task.list( + "-created_date", // sort (string: prefix with - for descending) + 10, // limit + 0 // skip +); + +// Get next page +const page2 = await base44.entities.Task.list("-created_date", 10, 10); +``` + +### Filter + +```javascript +// Simple filter +const pending = await base44.entities.Task.filter({ status: "pending" }); + +// Multiple conditions +const myPending = await base44.entities.Task.filter({ + status: "pending", + assignedTo: userId +}); + +// With sort, limit, skip +const recent = await base44.entities.Task.filter( + { status: "pending" }, + "-created_date", // sort (string: prefix with - for descending) + 5, + 0 +); + +// Select specific fields +const titles = await base44.entities.Task.filter( + { status: "pending" }, + null, + null, + null, + ["id", "title"] +); +``` + +### Get by ID + +```javascript +const task = await base44.entities.Task.get("task-id-123"); +``` + +### Update + +```javascript +// Partial update - only specified fields change +await base44.entities.Task.update("task-id-123", { + status: "completed", + completedAt: new Date().toISOString() +}); +``` + +### Delete + +```javascript +// Single record +await base44.entities.Task.delete("task-id-123"); + +// Multiple records matching query +await base44.entities.Task.deleteMany({ status: "archived" }); +``` + +### Subscribe to Realtime Updates + +```javascript +// Subscribe to all changes on Task entity +const unsubscribe = base44.entities.Task.subscribe((event) => { + console.log(`Task ${event.id} was ${event.type}:`, event.data); + // event.type is "create", "update", or "delete" +}); + +// Later: unsubscribe to stop receiving updates +unsubscribe(); +``` + +**Event structure:** +```javascript +{ + type: "create" | "update" | "delete", + data: { ... }, // the entity data + id: "entity-id", // the affected entity's ID + timestamp: "2024-01-15T10:30:00Z" +} +``` + +## User Entity + +Every app has a built-in `User` entity with special rules: + +- Regular users can only read/update **their own** record +- Cannot create users via `entities.create()` - use `auth.register()` instead +- Service role has full access to all user records + +```javascript +// Get current user's record +const me = await base44.entities.User.get(currentUserId); + +// Service role: get any user +const anyUser = await base44.asServiceRole.entities.User.get(userId); +``` + +## Service Role Access + +For admin-level operations (bypass user permissions): + +```javascript +// Backend only +const allTasks = await base44.asServiceRole.entities.Task.list(); +const allUsers = await base44.asServiceRole.entities.User.list(); +``` + +## Permissions (RLS & FLS) + +Data access is controlled by **Row Level Security (RLS)** and **Field Level Security (FLS)** rules defined in entity schemas. + +1. **Authentication level**: anonymous, authenticated, or service role +2. **RLS rules**: Control which records (rows) users can create/read/update/delete +3. **FLS rules**: Control which fields users can read/write within accessible records + +Operations succeed or fail based on these rules - no partial results. + +RLS and FLS are configured in entity schema files (`base44/entities/*.jsonc`). See [entities-create.md](../../base44-cli/references/entities-create.md#row-level-security-rls) for configuration details. + +**Note:** `asServiceRole` sets the user's role to `"admin"` but does NOT bypass RLS. Your RLS rules must include admin access (e.g., `{ "user_condition": { "role": "admin" } }`) for service role operations to succeed. + +## Type Definitions + +### RealtimeEvent + +```typescript +/** Event types for realtime entity updates. */ +type RealtimeEventType = "create" | "update" | "delete"; + +/** Payload received when a realtime event occurs. */ +interface RealtimeEvent { + /** The type of change that occurred. */ + type: RealtimeEventType; + /** The entity data. */ + data: any; + /** The unique identifier of the affected entity. */ + id: string; + /** ISO 8601 timestamp of when the event occurred. */ + timestamp: string; +} + +/** Callback function invoked when a realtime event occurs. */ +type RealtimeCallback = (event: RealtimeEvent) => void; + +/** Function returned from subscribe, call it to unsubscribe. */ +type Subscription = () => void; +``` + +### EntityHandler + +```typescript +/** Entity handler providing CRUD operations for a specific entity type. */ +interface EntityHandler { + /** Lists records with optional pagination and sorting. */ + list(sort?: string, limit?: number, skip?: number, fields?: string[]): Promise; + + /** Filters records based on a query. */ + filter(query: Record, sort?: string, limit?: number, skip?: number, fields?: string[]): Promise; + + /** Gets a single record by ID. */ + get(id: string): Promise; + + /** Creates a new record. */ + create(data: Record): Promise; + + /** Updates an existing record. */ + update(id: string, data: Record): Promise; + + /** Deletes a single record by ID. */ + delete(id: string): Promise; + + /** Deletes multiple records matching a query. */ + deleteMany(query: Record): Promise; + + /** Creates multiple records in a single request. */ + bulkCreate(data: Record[]): Promise; + + /** Imports records from a file (frontend only). */ + importEntities(file: File): Promise; + + /** Subscribes to realtime updates. Returns unsubscribe function. */ + subscribe(callback: RealtimeCallback): Subscription; +} +``` + +### EntitiesModule + +```typescript +/** Entities module for managing app data. */ +interface EntitiesModule { + /** Access any entity by name dynamically. */ + [entityName: string]: EntityHandler; +} +``` diff --git a/evals/fixtures/agent-with-function/skills/base44-sdk/references/functions.md b/evals/fixtures/agent-with-function/skills/base44-sdk/references/functions.md new file mode 100644 index 0000000..8072465 --- /dev/null +++ b/evals/fixtures/agent-with-function/skills/base44-sdk/references/functions.md @@ -0,0 +1,216 @@ +# Functions Module + +Invoke custom backend functions via `base44.functions`. + +## Contents +- [Method](#method) +- [Invoking Functions](#invoking-functions) (Frontend, File Upload, Service Role, REST API) +- [Writing Backend Functions](#writing-backend-functions) (Basic, Service Role, Secrets, Errors) +- [Setup Requirements](#setup-requirements) +- [Authentication Modes](#authentication-modes) + +## Method + +```javascript +base44.functions.invoke(functionName, data): Promise +``` + +- `functionName`: Name of the backend function +- `data`: Object of parameters (sent as JSON, or multipart if contains File objects) +- Returns: Whatever the function returns + +## Invoking Functions + +### From Frontend + +```javascript +const result = await base44.functions.invoke("processOrder", { + orderId: "order-123", + action: "ship" +}); + +console.log(result); +``` + +### With File Upload + +```javascript +const fileInput = document.querySelector('input[type="file"]'); +const file = fileInput.files[0]; + +// Automatically uses multipart/form-data when File objects present +const result = await base44.functions.invoke("uploadDocument", { + file: file, + category: "invoices" +}); +``` + +### With Service Role (Backend) + +```javascript +// Inside another backend function +const result = await base44.asServiceRole.functions.invoke("adminTask", { + userId: "user-123" +}); +``` + +### Via REST API (curl) + +Functions can be called via HTTP POST to your app domain: + +```bash +curl -X POST "https:///functions/" \ + -H "Content-Type: application/json" \ + -d '{"key": "value"}' +``` + +## Writing Backend Functions + +Backend functions run on Deno. Must export using `Deno.serve()`. + +### Required Directory Structure + +Each function must be in its own subdirectory under `base44/functions/` with a configuration file: + +``` +base44/ + functions/ + process-order/ # kebab-case directory name + function.jsonc # required configuration + index.ts # entry point +``` + +**function.jsonc:** +```jsonc +{ + "name": "process-order", + "entry": "index.ts" +} +``` + +For complete setup and deployment instructions, see [functions-create.md](../../base44-cli/references/functions-create.md) in base44-cli. + +### Basic Structure + +```javascript +// base44/functions/process-order/index.ts +import { createClientFromRequest } from "npm:@base44/sdk"; + +Deno.serve(async (req) => { + // Get authenticated client from request + const base44 = createClientFromRequest(req); + + // Parse input + const { orderId, action } = await req.json(); + + // Your logic here + const order = await base44.entities.Orders.get(orderId); + + // Return response + return Response.json({ + success: true, + order: order + }); +}); +``` + +### With Service Role Access + +```javascript +import { createClientFromRequest } from "npm:@base44/sdk"; + +Deno.serve(async (req) => { + const base44 = createClientFromRequest(req); + + // Check user is authenticated + const user = await base44.auth.me(); + if (!user) { + return Response.json({ error: "Unauthorized" }, { status: 401 }); + } + + // Use service role for admin operations + const allOrders = await base44.asServiceRole.entities.Orders.list(); + + return Response.json({ orders: allOrders }); +}); +``` + +### Using Secrets + +```javascript +Deno.serve(async (req) => { + // Access environment variables (configured in app settings) + const apiKey = Deno.env.get("STRIPE_API_KEY"); + + const response = await fetch("https://api.stripe.com/v1/charges", { + headers: { + "Authorization": `Bearer ${apiKey}` + } + }); + + return Response.json(await response.json()); +}); +``` + +### Error Handling + +```javascript +import { createClientFromRequest } from "npm:@base44/sdk"; + +Deno.serve(async (req) => { + try { + const base44 = createClientFromRequest(req); + const { orderId } = await req.json(); + + const order = await base44.entities.Orders.get(orderId); + if (!order) { + return Response.json( + { error: "Order not found" }, + { status: 404 } + ); + } + + return Response.json({ order }); + + } catch (error) { + return Response.json( + { error: error.message }, + { status: 500 } + ); + } +}); +``` + +## Setup Requirements + +1. Enable Backend Functions in app settings (requires appropriate plan) +2. Create function files in `/functions` folder +3. Configure secrets via app dashboard for API keys + +## Authentication Modes + +| Mode | Context | Permissions | +|------|---------|-------------| +| User | `base44.functions.invoke()` | Runs under calling user's permissions | +| Service Role | `base44.asServiceRole.functions.invoke()` | Admin-level access | + +Inside the function, use `createClientFromRequest(req)` to get a client that inherits the caller's auth context. + +## Type Definitions + +```typescript +/** Functions module for invoking custom backend functions. */ +interface FunctionsModule { + /** + * Invokes a custom backend function by name. + * + * If any parameter is a File object, the request will automatically be + * sent as multipart/form-data. Otherwise, it will be sent as JSON. + * + * @param functionName - The name of the function to invoke. + * @param data - An object containing named parameters for the function. + * @returns Promise resolving to the function's response. + */ + invoke(functionName: string, data: Record): Promise; +} +``` diff --git a/evals/fixtures/agent-with-function/skills/base44-sdk/references/integrations.md b/evals/fixtures/agent-with-function/skills/base44-sdk/references/integrations.md new file mode 100644 index 0000000..f356a18 --- /dev/null +++ b/evals/fixtures/agent-with-function/skills/base44-sdk/references/integrations.md @@ -0,0 +1,377 @@ +# Integrations Module + +Access third-party services via `base44.integrations`. + +## Types of Integrations + +1. **Core/Built-in**: AI, email, file uploads (available by default) +2. **Catalog integrations**: Pre-built connectors from Base44 catalog +3. **Custom integrations**: Your own OpenAPI-based integrations + +## Accessing Integrations + +```javascript +// Core integrations +base44.integrations.Core.FunctionName(params) + +// Custom integrations +base44.integrations.custom.call(slug, operationId, params) +``` + +## Core Integrations + +### InvokeLLM (AI Text Generation) + +Generate text or structured JSON data using AI models. + +```javascript +// Basic prompt - returns string +const response = await base44.integrations.Core.InvokeLLM({ + prompt: "Summarize this text: ..." +}); + +// With internet context (uses Google Search, Maps, News) +const response = await base44.integrations.Core.InvokeLLM({ + prompt: "What's the current weather in NYC?", + add_context_from_internet: true +}); + +// Structured JSON response - returns object +const response = await base44.integrations.Core.InvokeLLM({ + prompt: "Analyze the sentiment of: 'Great product but slow shipping'", + response_json_schema: { + type: "object", + properties: { + sentiment: { type: "string", enum: ["positive", "negative", "mixed"] }, + score: { type: "number", description: "Score from 1-10" }, + key_points: { type: "array", items: { type: "string" } } + } + } +}); +// Returns: { sentiment: "mixed", score: 7, key_points: ["great product", "slow shipping"] } + +// With file attachments (uploaded via UploadFile) +const response = await base44.integrations.Core.InvokeLLM({ + prompt: "Describe what's in this image", + file_urls: ["https://...uploaded_image.png"] +}); +``` + +**Parameters:** +- `prompt` (string, required): The prompt text to send to the model +- `add_context_from_internet` (boolean, optional): If true, uses Google Search/Maps/News for real-time context +- `response_json_schema` (object, optional): JSON schema for structured output +- `file_urls` (string[], optional): URLs of uploaded files for context + +### GenerateImage + +Create AI-generated images from text prompts. + +```javascript +const { url } = await base44.integrations.Core.GenerateImage({ + prompt: "A serene mountain landscape with a lake" +}); +console.log(url); // https://...generated_image.png +``` + +### SendEmail + +Send emails to registered users. Every app gets this integration (no plan upgrade required). + +```javascript +await base44.integrations.Core.SendEmail({ + to: "user@example.com", + subject: "Welcome!", + body: "

Hello

Welcome to our app.

", + from_name: "My App" // optional, defaults to app name +}); +``` + +**Parameters:** +- `to` (string, required): Recipient email address +- `subject` (string, required): Email subject line +- `body` (string, required): Plain text or HTML email body +- `from_name` (string, optional): Sender name displayed to recipient + +**Limitations:** +- 1 credit per email (2 credits with custom domain) + +### UploadFile (Public) + +Upload files to public storage. + +```javascript +const fileInput = document.querySelector('input[type="file"]'); +const { file_url } = await base44.integrations.Core.UploadFile({ + file: fileInput.files[0] +}); +console.log(file_url); // https://...uploaded_file.pdf +``` + +### UploadPrivateFile + +Upload files to private storage that requires a signed URL to access. + +```javascript +const { file_uri } = await base44.integrations.Core.UploadPrivateFile({ + file: fileInput.files[0] +}); +console.log(file_uri); // "private/user123/document.pdf" + +// Create a signed URL to access the file +const { signed_url } = await base44.integrations.Core.CreateFileSignedUrl({ + file_uri, + expires_in: 3600 // URL expires in 1 hour (default: 300 seconds) +}); +console.log(signed_url); // Temporary URL to access the private file +``` + +### CreateFileSignedUrl + +Generate temporary access links for private files. + +```javascript +const { signed_url } = await base44.integrations.Core.CreateFileSignedUrl({ + file_uri: "private/user123/document.pdf", + expires_in: 7200 // 2 hours +}); +``` + +**Parameters:** +- `file_uri` (string, required): URI from UploadPrivateFile +- `expires_in` (number, optional): Expiration time in seconds (default: 300) + +### ExtractDataFromUploadedFile + +Extract structured data from uploaded files using AI. + +```javascript +// First upload the file +const { file_url } = await base44.integrations.Core.UploadFile({ file }); + +// Then extract structured data +const result = await base44.integrations.Core.ExtractDataFromUploadedFile({ + file_url, + json_schema: { + type: "object", + properties: { + invoice_number: { type: "string" }, + total_amount: { type: "number" }, + date: { type: "string" }, + vendor_name: { type: "string" } + } + } +}); +console.log(result); // { invoice_number: "INV-12345", total_amount: 1250.00, ... } +``` + +## Custom Integrations + +Custom integrations allow workspace administrators to connect any external API by importing an OpenAPI specification. Use `base44.integrations.custom.call()` to invoke them. + +### Syntax + +```javascript +const response = await base44.integrations.custom.call( + slug, // Integration identifier (set by admin) + operationId, // Endpoint in "method:path" format + params // Optional: payload, pathParams, queryParams +); +``` + +### Examples + +```javascript +// GET request with query params +const response = await base44.integrations.custom.call( + "my-crm", + "get:/contacts", + { queryParams: { limit: 10, status: "active" } } +); + +// POST request with body +const response = await base44.integrations.custom.call( + "my-crm", + "post:/contacts", + { payload: { name: "John Doe", email: "john@example.com" } } +); + +// Request with path parameters +const response = await base44.integrations.custom.call( + "github", + "post:/repos/{owner}/{repo}/issues", + { + pathParams: { owner: "myorg", repo: "myrepo" }, + payload: { title: "Bug report", body: "Something is broken" } + } +); +``` + +### Response Structure + +```javascript +{ + success: true, // Whether external API returned 2xx + status_code: 200, // HTTP status code + data: { ... } // Response from external API +} +``` + +## Requirements + +- **Core integrations**: Available on all plans +- **Catalog/Custom integrations**: Require Builder plan or higher + +## Type Definitions + +### Core Integration Parameters + +```typescript +/** Parameters for the InvokeLLM function. */ +interface InvokeLLMParams { + /** The prompt text to send to the model. */ + prompt: string; + /** If true, uses Google Search/Maps/News for real-time context. */ + add_context_from_internet?: boolean; + /** JSON schema for structured output. If provided, returns object instead of string. */ + response_json_schema?: object; + /** File URLs (from UploadFile) to provide as context. */ + file_urls?: string[]; +} + +/** Parameters for the GenerateImage function. */ +interface GenerateImageParams { + /** Description of the image to generate. */ + prompt: string; +} + +/** Result from GenerateImage. */ +interface GenerateImageResult { + /** URL of the generated image. */ + url: string; +} + +/** Parameters for the UploadFile function. */ +interface UploadFileParams { + /** The file object to upload. */ + file: File; +} + +/** Result from UploadFile. */ +interface UploadFileResult { + /** URL of the uploaded file. */ + file_url: string; +} + +/** Parameters for the SendEmail function. */ +interface SendEmailParams { + /** Recipient email address. */ + to: string; + /** Email subject line. */ + subject: string; + /** Plain text or HTML email body. */ + body: string; + /** Sender name (defaults to app name). */ + from_name?: string; +} + +/** Parameters for ExtractDataFromUploadedFile. */ +interface ExtractDataFromUploadedFileParams { + /** URL of the uploaded file. */ + file_url: string; + /** JSON schema defining fields to extract. */ + json_schema: object; +} + +/** Parameters for UploadPrivateFile. */ +interface UploadPrivateFileParams { + /** The file object to upload. */ + file: File; +} + +/** Result from UploadPrivateFile. */ +interface UploadPrivateFileResult { + /** URI of the private file (used for signed URLs). */ + file_uri: string; +} + +/** Parameters for CreateFileSignedUrl. */ +interface CreateFileSignedUrlParams { + /** URI from UploadPrivateFile. */ + file_uri: string; + /** Expiration time in seconds (default: 300). */ + expires_in?: number; +} + +/** Result from CreateFileSignedUrl. */ +interface CreateFileSignedUrlResult { + /** Temporary signed URL to access the file. */ + signed_url: string; +} +``` + +### CoreIntegrations + +```typescript +/** Core package containing built-in Base44 integration functions. */ +interface CoreIntegrations { + InvokeLLM(params: InvokeLLMParams): Promise; + GenerateImage(params: GenerateImageParams): Promise; + UploadFile(params: UploadFileParams): Promise; + SendEmail(params: SendEmailParams): Promise; + ExtractDataFromUploadedFile(params: ExtractDataFromUploadedFileParams): Promise; + UploadPrivateFile(params: UploadPrivateFileParams): Promise; + CreateFileSignedUrl(params: CreateFileSignedUrlParams): Promise; +} +``` + +### Custom Integrations + +```typescript +/** Parameters for calling a custom integration endpoint. */ +interface CustomIntegrationCallParams { + /** Request body payload. */ + payload?: Record; + /** Path parameters to substitute in the URL. */ + pathParams?: Record; + /** Query string parameters. */ + queryParams?: Record; + /** Additional headers for this request. */ + headers?: Record; +} + +/** Response from a custom integration call. */ +interface CustomIntegrationCallResponse { + /** Whether the external API returned a 2xx status code. */ + success: boolean; + /** The HTTP status code from the external API. */ + status_code: number; + /** The response data from the external API. */ + data: any; +} + +/** Module for calling custom workspace-level API integrations. */ +interface CustomIntegrationsModule { + /** + * Call a custom integration endpoint. + * @param slug - The integration's unique identifier. + * @param operationId - The operation ID from the OpenAPI spec. + * @param params - Optional payload, pathParams, queryParams, headers. + */ + call(slug: string, operationId: string, params?: CustomIntegrationCallParams): Promise; +} +``` + +### IntegrationsModule + +```typescript +/** Integrations module for calling integration endpoints. */ +type IntegrationsModule = { + /** Core package with built-in integrations. */ + Core: CoreIntegrations; + /** Custom integrations module. */ + custom: CustomIntegrationsModule; + /** Additional integration packages (dynamic). */ + [packageName: string]: any; +}; +``` diff --git a/evals/fixtures/agent-with-function/skills/base44-sdk/references/users.md b/evals/fixtures/agent-with-function/skills/base44-sdk/references/users.md new file mode 100644 index 0000000..4205b06 --- /dev/null +++ b/evals/fixtures/agent-with-function/skills/base44-sdk/references/users.md @@ -0,0 +1,82 @@ +# Users Module + +Invite users to the app via `base44.users`. + +## Contents +- [Methods](#methods) +- [Examples](#examples) (Invite User) +- [Roles](#roles) +- [Notes](#notes) + +## Methods + +| Method | Signature | Description | +|--------|-----------|-------------| +| `inviteUser(user_email, role)` | `Promise` | Invite a user to the app | + +## Examples + +### Invite User + +```javascript +// Invite a user with "user" role +await base44.users.inviteUser("newuser@example.com", "user"); + +// Invite an admin +await base44.users.inviteUser("admin@example.com", "admin"); +``` + +### Invite Multiple Users + +```javascript +const usersToInvite = [ + { email: "user1@example.com", role: "user" }, + { email: "user2@example.com", role: "user" }, + { email: "manager@example.com", role: "admin" } +]; + +for (const user of usersToInvite) { + await base44.users.inviteUser(user.email, user.role); + console.log(`Invited ${user.email} as ${user.role}`); +} +``` + +## Roles + +The `role` parameter must be one of: + +| Role | Description | +|------|-------------| +| `"user"` | Standard user with default permissions | +| `"admin"` | Administrator with elevated permissions | + +**Note:** Only `"user"` and `"admin"` are valid role values. An error will be thrown if you pass any other value. + +## Notes + +- **Email invitation**: The invited user receives an email with a link to join the app +- **Duplicate handling**: Inviting an existing user will re-send the invitation +- **Also available in auth**: `base44.auth.inviteUser()` provides the same functionality +- **Role validation**: Only `"user"` or `"admin"` are accepted + +```javascript +// These are equivalent: +await base44.users.inviteUser("newuser@example.com", "user"); +await base44.auth.inviteUser("newuser@example.com", "user"); +``` + +## Type Definitions + +```typescript +/** Users module for inviting users to the app. */ +interface UsersModule { + /** + * Invite a user to the application. + * @param user_email - User's email address. + * @param role - User's role ('user' or 'admin'). + * @returns Promise resolving when the invitation is sent. + * @throws Error if role is not 'user' or 'admin'. + */ + inviteUser(user_email: string, role: "user" | "admin"): Promise; +} +``` diff --git a/evals/fixtures/anti-hallucination/eval.json b/evals/fixtures/anti-hallucination/eval.json new file mode 100644 index 0000000..2e31864 --- /dev/null +++ b/evals/fixtures/anti-hallucination/eval.json @@ -0,0 +1,253 @@ +{ + "$schema": "../../eval.schema.json", + "name": "anti-hallucination", + "description": "Test that correct Base44 APIs are used and hallucinated APIs are avoided", + "prompts": [ + { + "name": "anti-hallucination-crud", + "description": "Verify correct entity CRUD method names", + "prompt": "Create a ProductManager component in src/components/ProductManager.jsx that provides full CRUD for the Product entity. It must: list all products, create new products, get a single product by ID, update a product, delete a product, and filter products by category. Use the Base44 SDK exclusively.", + "expectedSkills": [ + "base44-sdk" + ], + "checks": [ + { + "type": "file-exists", + "filePath": "src/components/ProductManager.jsx", + "description": "src/components/ProductManager.jsx exists" + }, + { + "type": "file-content", + "filePath": "src/components/ProductManager.jsx", + "pattern": "entities\\.Product\\.list\\(", + "description": "Uses correct list() method" + }, + { + "type": "file-content", + "filePath": "src/components/ProductManager.jsx", + "pattern": "entities\\.Product\\.create\\(", + "description": "Uses correct create() method" + }, + { + "type": "file-content", + "filePath": "src/components/ProductManager.jsx", + "pattern": "entities\\.Product\\.get\\(", + "description": "Uses correct get() method" + }, + { + "type": "file-content", + "filePath": "src/components/ProductManager.jsx", + "pattern": "entities\\.Product\\.update\\(", + "description": "Uses correct update() method" + }, + { + "type": "file-content", + "filePath": "src/components/ProductManager.jsx", + "pattern": "entities\\.Product\\.delete\\(", + "description": "Uses correct delete() method" + }, + { + "type": "file-content", + "filePath": "src/components/ProductManager.jsx", + "pattern": "entities\\.Product\\.filter\\(", + "description": "Uses correct filter() method" + }, + { + "type": "file-content-excluded", + "filePath": "src/components/ProductManager.jsx", + "pattern": "Product\\.find\\(", + "description": "Does not hallucinate find() method" + }, + { + "type": "file-content-excluded", + "filePath": "src/components/ProductManager.jsx", + "pattern": "Product\\.findOne\\(", + "description": "Does not hallucinate findOne() method" + }, + { + "type": "file-content-excluded", + "filePath": "src/components/ProductManager.jsx", + "pattern": "Product\\.insert\\(", + "description": "Does not hallucinate insert() method" + }, + { + "type": "file-content-excluded", + "filePath": "src/components/ProductManager.jsx", + "pattern": "Product\\.remove\\(", + "description": "Does not hallucinate remove() method" + }, + { + "type": "file-content-excluded", + "filePath": "src/components/ProductManager.jsx", + "pattern": "Product\\.save\\(", + "description": "Does not hallucinate save() method" + } + ] + }, + { + "name": "anti-hallucination-auth", + "description": "Verify correct auth method names", + "prompt": "Create an AuthPage component in src/pages/AuthPage.jsx that provides login with email/password, login with Google, user registration, getting current user info, and logout. Use the Base44 SDK auth module exclusively.", + "expectedSkills": [ + "base44-sdk" + ], + "checks": [ + { + "type": "file-exists", + "filePath": "src/pages/AuthPage.jsx", + "description": "src/pages/AuthPage.jsx exists" + }, + { + "type": "file-content", + "filePath": "src/pages/AuthPage.jsx", + "pattern": "loginViaEmailPassword", + "description": "Uses correct loginViaEmailPassword method" + }, + { + "type": "file-content", + "filePath": "src/pages/AuthPage.jsx", + "pattern": "loginWithProvider\\s*\\(\\s*['\"]google['\"]", + "description": "Uses correct loginWithProvider method with 'google'" + }, + { + "type": "file-content", + "filePath": "src/pages/AuthPage.jsx", + "pattern": "register\\s*\\(", + "description": "Uses correct register() method" + }, + { + "type": "file-content", + "filePath": "src/pages/AuthPage.jsx", + "pattern": "auth\\.me\\(\\)", + "description": "Uses correct me() method for current user" + }, + { + "type": "file-content", + "filePath": "src/pages/AuthPage.jsx", + "pattern": "logout\\(\\)", + "description": "Uses correct logout() method" + }, + { + "type": "file-content-excluded", + "filePath": "src/pages/AuthPage.jsx", + "pattern": "signInWithEmailAndPassword", + "description": "Does not hallucinate Firebase signInWithEmailAndPassword" + }, + { + "type": "file-content-excluded", + "filePath": "src/pages/AuthPage.jsx", + "pattern": "signInWithGoogle|signInWithProvider", + "description": "Does not hallucinate Firebase signInWithGoogle or signInWithProvider" + }, + { + "type": "file-content-excluded", + "filePath": "src/pages/AuthPage.jsx", + "pattern": "createUser|signUp\\(", + "description": "Does not hallucinate createUser or signUp methods" + }, + { + "type": "file-content-excluded", + "filePath": "src/pages/AuthPage.jsx", + "pattern": "onAuthStateChanged|currentUser", + "description": "Does not hallucinate Firebase onAuthStateChanged or currentUser" + } + ] + }, + { + "name": "anti-hallucination-imports", + "description": "Verify correct import patterns", + "prompt": "Create a Dashboard component in src/pages/Dashboard.jsx that imports and uses the Base44 SDK client. It should fetch and display the current user info and a list of products. Make sure to import the base44 client correctly.", + "expectedSkills": [ + "base44-sdk" + ], + "checks": [ + { + "type": "file-exists", + "filePath": "src/pages/Dashboard.jsx", + "description": "src/pages/Dashboard.jsx exists" + }, + { + "type": "file-content", + "filePath": "src/pages/Dashboard.jsx", + "pattern": "import.*base44|from ['\"].*base44", + "description": "Imports from base44 SDK" + }, + { + "type": "file-content", + "filePath": "src/pages/Dashboard.jsx", + "pattern": "auth\\.me\\(\\)", + "description": "Uses correct me() method for current user" + }, + { + "type": "file-content", + "filePath": "src/pages/Dashboard.jsx", + "pattern": "entities\\.Product", + "description": "Accesses Product entity correctly" + }, + { + "type": "file-content-excluded", + "filePath": "src/pages/Dashboard.jsx", + "pattern": "from ['\"]firebase", + "description": "Does not import from Firebase SDK" + }, + { + "type": "file-content-excluded", + "filePath": "src/pages/Dashboard.jsx", + "pattern": "from ['\"]@supabase", + "description": "Does not import from Supabase SDK" + }, + { + "type": "file-content-excluded", + "filePath": "src/pages/Dashboard.jsx", + "pattern": "initializeApp|getFirestore", + "description": "Does not hallucinate Firebase initialization methods" + } + ] + }, + { + "name": "anti-hallucination-config", + "description": "Verify correct client configuration", + "prompt": "Create a new Base44 client configuration file at src/lib/base44Client.js for an external app. Initialize the client with createClient and configure it with the app ID. Export the configured client as default.", + "expectedSkills": [ + "base44-sdk" + ], + "checks": [ + { + "type": "file-exists", + "filePath": "src/lib/base44Client.js", + "description": "src/lib/base44Client.js exists" + }, + { + "type": "file-content", + "filePath": "src/lib/base44Client.js", + "pattern": "createClient", + "description": "Uses correct createClient function" + }, + { + "type": "file-content", + "filePath": "src/lib/base44Client.js", + "pattern": "from ['\"]@base44/sdk['\"]", + "description": "Imports from @base44/sdk" + }, + { + "type": "file-content", + "filePath": "src/lib/base44Client.js", + "pattern": "appId", + "description": "Uses correct appId configuration property" + }, + { + "type": "file-content-excluded", + "filePath": "src/lib/base44Client.js", + "pattern": "clientId", + "description": "Does not hallucinate clientId property (correct is appId)" + }, + { + "type": "file-content-excluded", + "filePath": "src/lib/base44Client.js", + "pattern": "initializeApp|getApp", + "description": "Does not hallucinate Firebase initialization methods" + } + ] + } + ] +} diff --git a/evals/fixtures/anti-hallucination/project/.claude/settings.json b/evals/fixtures/anti-hallucination/project/.claude/settings.json new file mode 100644 index 0000000..ab60505 --- /dev/null +++ b/evals/fixtures/anti-hallucination/project/.claude/settings.json @@ -0,0 +1,6 @@ +{ + "permissions": { + "allow": ["Read", "Write", "Edit", "Bash", "Glob", "Grep"] + }, + "skills": ["./skills"] +} diff --git a/evals/fixtures/anti-hallucination/project/AGENTS.md b/evals/fixtures/anti-hallucination/project/AGENTS.md new file mode 100644 index 0000000..6c0f2be --- /dev/null +++ b/evals/fixtures/anti-hallucination/project/AGENTS.md @@ -0,0 +1,25 @@ +IMPORTANT: Prefer retrieval-led reasoning over pre-training-led reasoning. Base44 is NOT Firebase, Supabase, or any other SDK you know. Every API name below is the ONLY correct form. Always verify against this index and the referenced docs. + +[Base44 SDK Docs Index]|root: ./skills/base44-sdk/references +|entities.md: CRUD - create(data), list(), filter(query), get(id), update(id,data), delete(id), subscribe(cb) +|auth.md: Auth - loginViaEmailPassword(), loginWithProvider('google'), me(), register(), logout() +|base44-agents.md: Agents - createConversation({agent_name}), addMessage(conv,{role,content}), subscribeToConversation(id,cb) +|functions.md: Functions - invoke(name, data) +|integrations.md: Core - InvokeLLM({prompt}), SendEmail({to,subject,body}), UploadFile({file}) +|client.md: Client - createClient({appId}), import from "@base44/sdk" +|QUICK_REFERENCE.md: All method signatures + +[Base44 CLI Docs Index]|root: ./skills/base44-cli/references +|entities-create.md: Entity schema - {name:"PascalCase", type:"object", properties:{}, required:[]} +|functions-create.md: Function - function.jsonc {name,entry} + index.ts with Deno.serve + createClientFromRequest +|create.md: Project creation - npx base44 create name -p . +|deploy.md: Deploy - npx base44 deploy -y +|agents-push.md: Agent config - {name:"snake_case", description, instructions, tool_configs:[{entity_name,allowed_operations}|{function_name,description}]} + +[Common Mistakes - NEVER use these hallucinated names] +|SDK: Do NOT use find() → use filter(), findOne() → get(), insert() → create(), remove() → delete(), onChange() → subscribe() +|SDK: Do NOT use signInWithGoogle() → use loginWithProvider('google'), functions.call() → functions.invoke() +|SDK: Do NOT use ai.generate() → use integrations.Core.InvokeLLM({prompt}) +|CLI: Entity names must be PascalCase alphanumeric. File names must be kebab-case.jsonc +|CLI: Agent names must be lowercase with underscores only. Function entry field is "entry" not "entrypoint" +|CLI: Backend functions import from "npm:@base44/sdk" (Deno requires npm: prefix) diff --git a/evals/fixtures/anti-hallucination/project/CLAUDE.md b/evals/fixtures/anti-hallucination/project/CLAUDE.md new file mode 100644 index 0000000..724ce4d --- /dev/null +++ b/evals/fixtures/anti-hallucination/project/CLAUDE.md @@ -0,0 +1,7 @@ +# Base44 Development Guidelines + +When working with Base44 projects, first explore the existing project structure to understand what already exists. Look at existing code, config files, and patterns in use. + +Then consult the AGENTS.md index in the project root for correct API patterns. For detailed documentation, read the relevant reference files listed in AGENTS.md. + +IMPORTANT: Prefer retrieval-led reasoning over pre-training-led reasoning. Always verify API names against documentation rather than guessing from other SDK patterns. diff --git a/evals/fixtures/anti-hallucination/project/base44/config.jsonc b/evals/fixtures/anti-hallucination/project/base44/config.jsonc new file mode 100644 index 0000000..66fd90c --- /dev/null +++ b/evals/fixtures/anti-hallucination/project/base44/config.jsonc @@ -0,0 +1,10 @@ +{ + "name": "Anti-Hallucination App", + "description": "A test application for verifying correct API usage", + "entitiesDir": "./entities", + "functionsDir": "./functions", + "site": { + "buildCommand": "npm run build", + "outputDirectory": "dist" + } +} diff --git a/evals/fixtures/anti-hallucination/project/base44/entities/product.jsonc b/evals/fixtures/anti-hallucination/project/base44/entities/product.jsonc new file mode 100644 index 0000000..da2dfd6 --- /dev/null +++ b/evals/fixtures/anti-hallucination/project/base44/entities/product.jsonc @@ -0,0 +1,23 @@ +{ + "name": "Product", + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "Product name" + }, + "price": { + "type": "number", + "description": "Product price" + }, + "category": { + "type": "string", + "description": "Product category" + }, + "in_stock": { + "type": "boolean", + "default": true + } + }, + "required": ["name", "price"] +} diff --git a/evals/fixtures/anti-hallucination/project/package.json b/evals/fixtures/anti-hallucination/project/package.json new file mode 100644 index 0000000..1e6f905 --- /dev/null +++ b/evals/fixtures/anti-hallucination/project/package.json @@ -0,0 +1,20 @@ +{ + "name": "anti-hallucination-app", + "version": "0.1.0", + "private": true, + "type": "module", + "scripts": { + "dev": "vite", + "build": "vite build" + }, + "dependencies": { + "@base44/sdk": "^1.0.0", + "react": "^18.2.0", + "react-dom": "^18.2.0" + }, + "devDependencies": { + "@vitejs/plugin-react": "^4.2.1", + "base44": "latest", + "vite": "^5.2.0" + } +} diff --git a/evals/fixtures/anti-hallucination/project/src/App.jsx b/evals/fixtures/anti-hallucination/project/src/App.jsx new file mode 100644 index 0000000..5553fc1 --- /dev/null +++ b/evals/fixtures/anti-hallucination/project/src/App.jsx @@ -0,0 +1,11 @@ +import React from 'react'; + +function App() { + return ( +
+

Auth Test App

+
+ ); +} + +export default App; diff --git a/evals/fixtures/anti-hallucination/project/src/api/base44Client.js b/evals/fixtures/anti-hallucination/project/src/api/base44Client.js new file mode 100644 index 0000000..7887147 --- /dev/null +++ b/evals/fixtures/anti-hallucination/project/src/api/base44Client.js @@ -0,0 +1,5 @@ +import { createClient } from "@base44/sdk"; + +const base44 = createClient({ appId: "test-app" }); + +export default base44; diff --git a/evals/fixtures/anti-hallucination/skills/base44-cli/SKILL.md b/evals/fixtures/anti-hallucination/skills/base44-cli/SKILL.md new file mode 100644 index 0000000..3c33f05 --- /dev/null +++ b/evals/fixtures/anti-hallucination/skills/base44-cli/SKILL.md @@ -0,0 +1,384 @@ +--- +name: base44-cli +description: "The base44 CLI is used for EVERYTHING related to base44 projects: resource configuration (entities, backend functions, ai agents), initialization and actions (resource creation, deployment). This skill is the place for learning about how to configure resources. When you plan or implement a feature, you must learn this skill" +--- + +# Base44 CLI + +Create and manage Base44 apps (projects) using the Base44 CLI tool. + +## ⚡ IMMEDIATE ACTION REQUIRED - Read This First + +This skill activates on ANY mention of "base44" or when a `base44/` folder exists. **DO NOT read documentation files or search the web before acting.** + +**Your first action MUST be:** +1. Check if `base44/config.jsonc` exists in the current directory +2. If **NO** (new project scenario): + - This skill (base44-cli) handles the request + - Guide user through project initialization + - Do NOT activate base44-sdk yet +3. If **YES** (existing project scenario): + - Transfer to base44-sdk skill for implementation + - This skill only handles CLI commands (login, deploy, entities push) + +## Critical: Local Installation Only + +NEVER call `base44` directly. The CLI is installed locally as a dev dependency and must be accessed via a package manager: + +- `npx base44 ` (npm - recommended) +- `yarn base44 ` (yarn) +- `pnpm base44 ` (pnpm) + +WRONG: `base44 login` +RIGHT: `npx base44 login` + +## MANDATORY: Authentication Check at Session Start + +**CRITICAL**: At the very start of every AI session when this skill is activated, you MUST: + +1. **Check authentication status** by running: + ```bash + npx base44 whoami + ``` + +2. **If the user is logged in** (command succeeds and shows an email): + - Continue with the requested task + +3. **If the user is NOT logged in** (command fails or shows an error): + - **STOP immediately** + - **DO NOT proceed** with any CLI operations + - **Ask the user to login manually** by running: + ```bash + npx base44 login + ``` + - Wait for the user to confirm they have logged in before continuing + +**This check is mandatory and must happen before executing any other Base44 CLI commands.** + +## Overview + +The Base44 CLI provides command-line tools for authentication, creating projects, managing entities, and deploying Base44 applications. It is framework-agnostic and works with popular frontend frameworks like Vite, Next.js, and Create React App, Svelte, Vue, and more. + +## When to Use This Skill vs base44-sdk + +**Use base44-cli when:** +- Creating a **NEW** Base44 project from scratch +- Initializing a project in an empty directory +- Directory is missing `base44/config.jsonc` +- User mentions: "create a new project", "initialize project", "setup a project", "start a new Base44 app" +- Deploying, pushing entities, or authenticating via CLI +- Working with CLI commands (`npx base44 ...`) + +**Use base44-sdk when:** +- Building features in an **EXISTING** Base44 project +- `base44/config.jsonc` already exists +- Writing JavaScript/TypeScript code using Base44 SDK +- Implementing functionality, components, or features +- User mentions: "implement", "build a feature", "add functionality", "write code" + +**Skill Dependencies:** +- `base44-cli` is a **prerequisite** for `base44-sdk` in new projects +- If user wants to "create an app" and no Base44 project exists, use `base44-cli` first +- `base44-sdk` assumes a Base44 project is already initialized + +**State Check Logic:** +Before selecting a skill, check: +- IF (user mentions "create/build app" OR "make a project"): + - IF (directory is empty OR no `base44/config.jsonc` exists): + → Use **base44-cli** (project initialization needed) + - ELSE: + → Use **base44-sdk** (project exists, build features) + +## Project Structure + +A Base44 project combines a standard frontend project with a `base44/` configuration folder: + +``` +my-app/ +├── base44/ # Base44 configuration (created by CLI) +│ ├── config.jsonc # Project settings, site config +│ ├── entities/ # Entity schema definitions +│ │ ├── task.jsonc +│ │ └── board.jsonc +│ ├── functions/ # Backend functions (optional) +│ │ └── my-function/ +│ │ ├── function.jsonc +│ │ └── index.ts +│ └── agents/ # Agent configurations (optional) +│ └── support_agent.jsonc +├── src/ # Frontend source code +│ ├── api/ +│ │ └── base44Client.js # Base44 SDK client +│ ├── pages/ +│ ├── components/ +│ └── main.jsx +├── index.html # SPA entry point +├── package.json +└── vite.config.js # Or your framework's config +``` + +**Key files:** +- `base44/config.jsonc` - Project name, description, site build settings +- `base44/entities/*.jsonc` - Data model schemas (see Entity Schema section) +- `base44/agents/*.jsonc` - Agent configurations (optional) +- `src/api/base44Client.js` - Pre-configured SDK client for frontend use + +**config.jsonc example:** +```jsonc +{ + "name": "My App", // Required: project name + "description": "App description", // Optional: project description + "entitiesDir": "./entities", // Optional: default "entities" + "functionsDir": "./functions", // Optional: default "functions" + "agentsDir": "./agents", // Optional: default "agents" + "site": { // Optional: site deployment config + "installCommand": "npm install", // Optional: install dependencies + "buildCommand": "npm run build", // Optional: build command + "serveCommand": "npm run dev", // Optional: local dev server + "outputDirectory": "./dist" // Optional: build output directory + } +} +``` + +**Config properties:** + +| Property | Description | Default | +|----------|-------------|---------| +| `name` | Project name (required) | - | +| `description` | Project description | - | +| `entitiesDir` | Directory for entity schemas | `"entities"` | +| `functionsDir` | Directory for backend functions | `"functions"` | +| `agentsDir` | Directory for agent configs | `"agents"` | +| `site.installCommand` | Command to install dependencies | - | +| `site.buildCommand` | Command to build the project | - | +| `site.serveCommand` | Command to run dev server | - | +| `site.outputDirectory` | Build output directory for deployment | - | + +## Installation + +Install the Base44 CLI as a dev dependency in your project: + +```bash +npm install --save-dev base44 +``` + +**Important:** Never assume or hardcode the `base44` package version. Always install without a version specifier to get the latest version. + +Then run commands using `npx`: + +```bash +npx base44 +``` + +**Note:** All commands in this documentation use `npx base44`. You can also use `yarn base44`, or `pnpm base44` if preferred. + +## Available Commands + +### Authentication + +| Command | Description | Reference | +| --------------- | ----------------------------------------------- | ------------------------------------------- | +| `base44 login` | Authenticate with Base44 using device code flow | [auth-login.md](references/auth-login.md) | +| `base44 logout` | Logout from current device | [auth-logout.md](references/auth-logout.md) | +| `base44 whoami` | Display current authenticated user | [auth-whoami.md](references/auth-whoami.md) | + +### Project Management + +| Command | Description | Reference | +|---------|-------------|-----------| +| `base44 create` | Create a new Base44 project from a template | [create.md](references/create.md) ⚠️ **MUST READ** | +| `base44 link` | Link an existing local project to Base44 | [link.md](references/link.md) | +| `base44 dashboard` | Open the app dashboard in your browser | [dashboard.md](references/dashboard.md) | + +### Deployment + +| Command | Description | Reference | +|---------|-------------|-----------| +| `base44 deploy` | Deploy all resources (entities, functions, site) | [deploy.md](references/deploy.md) | + +### Entity Management + +| Action / Command | Description | Reference | +| ---------------------- | ------------------------------------------- | --------------------------------------------------- | +| Create Entities | Define entities in `base44/entities` folder | [entities-create.md](references/entities-create.md) | +| `base44 entities push` | Push local entities to Base44 | [entities-push.md](references/entities-push.md) | + +#### Entity Schema (Quick Reference) + +ALWAYS follow this exact structure when creating entity files: + +**File naming:** `base44/entities/{kebab-case-name}.jsonc` (e.g., `team-member.jsonc` for `TeamMember`) + +**Schema template:** +```jsonc +{ + "name": "EntityName", + "type": "object", + "properties": { + "field_name": { + "type": "string", + "description": "Field description" + } + }, + "required": ["field_name"] +} +``` + +**Field types:** `string`, `number`, `integer`, `boolean`, `array`, `object`, `binary` +**String formats:** `date`, `date-time`, `time`, `email`, `uri`, `hostname`, `ipv4`, `ipv6`, `uuid`, `file`, `regex`, `richtext` +**For enums:** Add `"enum": ["value1", "value2"]` and optionally `"default": "value1"` +**Entity names:** Must be alphanumeric only (pattern: `/^[a-zA-Z0-9]+$/`) + +For complete documentation, see [entities-create.md](references/entities-create.md). + +### Function Management + +| Action / Command | Description | Reference | +| ------------------------- | --------------------------------------------- | ------------------------------------------------------- | +| Create Functions | Define functions in `base44/functions` folder | [functions-create.md](references/functions-create.md) | +| `base44 functions deploy` | Deploy local functions to Base44 | [functions-deploy.md](references/functions-deploy.md) | + +### Agent Management + +Agents are conversational AI assistants that can interact with users, access your app's entities, and call backend functions. Use these commands to manage agent configurations. + +| Action / Command | Description | Reference | +| ----------------------- | --------------------------------------- | ----------------------------------------------- | +| Create Agents | Define agents in `base44/agents` folder | See Agent Schema below | +| `base44 agents pull` | Pull remote agents to local files | [agents-pull.md](references/agents-pull.md) | +| `base44 agents push` | Push local agents to Base44 | [agents-push.md](references/agents-push.md) | + +**Note:** Agent commands perform full synchronization - pushing replaces all remote agents with local ones, and pulling replaces all local agents with remote ones. + +#### Agent Schema (Quick Reference) + +**File naming:** `base44/agents/{agent_name}.jsonc` (e.g., `support_agent.jsonc`) + +**Schema template:** +```jsonc +{ + "name": "agent_name", + "description": "Brief description of what this agent does", + "instructions": "Detailed instructions for the agent's behavior", + "tool_configs": [ + // Entity tool - gives agent access to entity operations + { "entity_name": "tasks", "allowed_operations": ["read", "create", "update", "delete"] }, + // Backend function tool - gives agent access to a function + { "function_name": "send_email", "description": "Send an email notification" } + ], + "whatsapp_greeting": "Hello! How can I help you today?" +} +``` + +**Naming rules:** +- Agent names must match pattern: `/^[a-z0-9_]+$/` (lowercase alphanumeric with underscores, 1-100 chars) +- Valid: `support_agent`, `order_bot` +- Invalid: `Support-Agent`, `OrderBot` + +**Required fields:** `name`, `description`, `instructions` +**Optional fields:** `tool_configs` (defaults to `[]`), `whatsapp_greeting` + +**Tool config types:** +- **Entity tools**: `entity_name` + `allowed_operations` (array of: `read`, `create`, `update`, `delete`) +- **Backend function tools**: `function_name` + `description` + +### Site Deployment + +| Command | Description | Reference | +| -------------------- | ----------------------------------------- | ------------------------------------------- | +| `base44 site deploy` | Deploy built site files to Base44 hosting | [site-deploy.md](references/site-deploy.md) | + +**SPA only**: Base44 hosting supports Single Page Applications with a single `index.html` entry point. All routes are served from `index.html` (client-side routing). + +## Quick Start + +1. Install the CLI in your project: + ```bash + npm install --save-dev base44 + ``` + +2. Authenticate with Base44: + ```bash + npx base44 login + ``` + +3. Create a new project (ALWAYS provide name and `--path` flag): + ```bash + npx base44 create my-app -p . + ``` + +4. Build and deploy everything: + ```bash + npm run build + npx base44 deploy -y + ``` + +Or deploy individual resources: +- `npx base44 entities push` - Push entities only +- `npx base44 functions deploy` - Deploy functions only +- `npx base44 agents push` - Push agents only +- `npx base44 site deploy -y` - Deploy site only + +## Common Workflows + +### Creating a New Project + +**⚠️ MANDATORY: Before running `base44 create`, you MUST read [create.md](references/create.md) for:** +- **Template selection** - Choose the correct template (`backend-and-client` vs `backend-only`) +- **Correct workflow** - Different templates require different setup steps +- **Common pitfalls** - Avoid folder creation errors that cause failures + +Failure to follow the create.md instructions will result in broken project scaffolding. + +### Linking an Existing Project +```bash +# If you have base44/config.jsonc but no .app.jsonc +npx base44 link --create --name my-app +``` + +### Deploying All Changes +```bash +# Build your project first +npm run build + +# Deploy everything (entities, functions, and site) +npx base44 deploy -y +``` + +### Deploying Individual Resources +```bash +# Push only entities +npx base44 entities push + +# Deploy only functions +npx base44 functions deploy + +# Push only agents +npx base44 agents push + +# Deploy only site +npx base44 site deploy -y +``` + +### Opening the Dashboard +```bash +# Open app dashboard in browser +npx base44 dashboard +``` + +## Authentication + +Most commands require authentication. If you're not logged in, the CLI will automatically prompt you to login. Your session is stored locally and persists across CLI sessions. + +## Troubleshooting + +| Error | Solution | +| --------------------------- | ----------------------------------------------------------------------------------- | +| Not authenticated | Run `npx base44 login` first | +| No entities found | Ensure entities exist in `base44/entities/` directory | +| Entity not recognized | Ensure file uses kebab-case naming (e.g., `team-member.jsonc` not `TeamMember.jsonc`) | +| No functions found | Ensure functions exist in `base44/functions/` with valid `function.jsonc` configs | +| No agents found | Ensure agents exist in `base44/agents/` directory with valid `.jsonc` configs | +| Invalid agent name | Agent names must be lowercase alphanumeric with underscores only | +| No site configuration found | Check that `site.outputDirectory` is configured in project config | +| Site deployment fails | Ensure you ran `npm run build` first and the build succeeded | diff --git a/evals/fixtures/anti-hallucination/skills/base44-cli/references/agents-pull.md b/evals/fixtures/anti-hallucination/skills/base44-cli/references/agents-pull.md new file mode 100644 index 0000000..6a700f0 --- /dev/null +++ b/evals/fixtures/anti-hallucination/skills/base44-cli/references/agents-pull.md @@ -0,0 +1,73 @@ +# base44 agents pull + +Pull AI agent configurations from Base44 to local files. Agents are conversational AI assistants that can interact with users, access your app's entities, and call backend functions. + +## Syntax + +```bash +npx base44 agents pull +``` + +## Authentication + +**Required**: Yes. If not authenticated, you'll be prompted to login first. + +## What It Does + +1. Fetches all agents from Base44 +2. Writes agent files to the `base44/agents/` directory +3. Deletes local agent files that don't exist remotely +4. Reports written and deleted agents + +## Prerequisites + +- Must be run from a Base44 project directory +- Project must be linked to a Base44 app + +## Output + +```bash +$ npx base44 agents pull + +Fetching agents from Base44... +✓ Agents fetched successfully + +Writing agent files... +✓ Agent files written successfully + +Written: support_agent, order_bot +Deleted: old_agent + +Pulled 2 agents to base44/agents +``` + +## Agent Synchronization + +The pull operation synchronizes remote agents to your local files: + +- **Written**: Agent files created or updated from remote +- **Deleted**: Local agent files removed (didn't exist remotely) + +**Warning**: This operation replaces all local agent configurations with remote versions. Any local changes not pushed to Base44 will be overwritten. + +## Error Handling + +If no agents exist on Base44: +```bash +$ npx base44 agents pull +No agents found on Base44 +``` + +## Use Cases + +- Sync agent configurations to a new development machine +- Get the latest agent configurations from your team +- Restore local agent files after accidental deletion +- Start working on an existing project with agents + +## Notes + +- This command syncs agent configurations, not conversation data +- Agent files are stored as `.jsonc` in the `base44/agents/` directory +- The directory location is configurable via `agentsDir` in `config.jsonc` +- Use `base44 agents push` to upload local changes to Base44 diff --git a/evals/fixtures/anti-hallucination/skills/base44-cli/references/agents-push.md b/evals/fixtures/anti-hallucination/skills/base44-cli/references/agents-push.md new file mode 100644 index 0000000..fa2795b --- /dev/null +++ b/evals/fixtures/anti-hallucination/skills/base44-cli/references/agents-push.md @@ -0,0 +1,156 @@ +# base44 agents push + +Push local AI agent configurations to Base44. Agents are conversational AI assistants that can interact with users, access your app's entities, and call backend functions. + +## Syntax + +```bash +npx base44 agents push +``` + +## Authentication + +**Required**: Yes. If not authenticated, you'll be prompted to login first. + +## What It Does + +1. Reads all agent files from the `base44/agents/` directory +2. Validates agent configurations +3. Displays the count of agents to be pushed +4. Uploads agents to the Base44 backend +5. Reports the results: created, updated, and deleted agents + +## Prerequisites + +- Must be run from a Base44 project directory +- Project must have agent definitions in the `base44/agents/` folder + +## Output + +```bash +$ npx base44 agents push + +Found 2 agents to push +Pushing agents to Base44... + +Created: support_agent +Updated: order_bot +Deleted: old_agent + +✓ Agents pushed to Base44 +``` + +## Agent Synchronization + +The push operation synchronizes your local agents with Base44: + +- **Created**: New agents that didn't exist in Base44 +- **Updated**: Existing agents with modified configuration +- **Deleted**: Agents that were removed from your local configuration + +**Warning**: This is a full sync operation. Agents removed locally will be deleted from Base44. + +## Error Handling + +If no agents are found in your project: +```bash +$ npx base44 agents push +No local agents found - this will delete all remote agents +``` + +If an agent has an invalid name: +```bash +$ npx base44 agents push +Error: Agent name must be lowercase alphanumeric with underscores +``` + +## Agent Configuration Schema + +Each agent file should be a `.jsonc` file in `base44/agents/` with this structure: + +```jsonc +{ + "name": "agent_name", // Required: lowercase alphanumeric with underscores, 1-100 chars + "description": "Brief description of what this agent does", // Required: min 1 char + "instructions": "Detailed instructions for the agent's behavior", // Required: min 1 char + "tool_configs": [ // Optional: defaults to [] + // Entity tool - gives agent access to entity operations + { "entity_name": "Task", "allowed_operations": ["read", "create", "update", "delete"] }, + // Backend function tool - gives agent access to a function + { "function_name": "send_email", "description": "Send an email notification" } + ], + "whatsapp_greeting": "Hello! How can I help you today?" // Optional +} +``` + +**Naming rules:** +- **Agent names** must match pattern: `/^[a-z0-9_]+$/` (lowercase alphanumeric with underscores only, 1-100 characters) + - Valid: `support_agent`, `order_bot`, `task_helper` + - Invalid: `Support-Agent`, `OrderBot`, `task helper` +- **Agent file names** must use underscores (matching the agent name) + - Valid: `support_agent.jsonc`, `order_bot.jsonc` + - Invalid: `support-agent.jsonc` (hyphens not allowed) +- **Entity names in `tool_configs`** must use PascalCase (matching the entity's `name` field) + - Valid: `"entity_name": "Task"`, `"entity_name": "TeamMember"` + - Invalid: `"entity_name": "task"`, `"entity_name": "team_member"` + +**Required fields:** +- `name`: Required, must follow naming rules above +- `description`: Required, minimum 1 character +- `instructions`: Required, minimum 1 character +- `tool_configs`: Optional, defaults to empty array +- `whatsapp_greeting`: Optional + +### Common Mistake: Wrong tool_configs Format + +**WRONG** - Do NOT use `tools` with `type` and `entity`: +```jsonc +{ + "name": "my_agent", + "tools": [ // ❌ WRONG + { "type": "entity_query", "entity": "Task" } + ] +} +``` + +**CORRECT** - Use `tool_configs` with `entity_name` and `allowed_operations`: +```jsonc +{ + "name": "my_agent", + "tool_configs": [ // ✅ CORRECT + { "entity_name": "Task", "allowed_operations": ["read"] } + ] +} +``` + +### Best Practices for Agent Instructions + +When giving agents access to entities, be explicit in the instructions about using the tools: + +```jsonc +{ + "name": "support_agent", + "instructions": "You are a helpful support agent.\n\nIMPORTANT: You have access to customer data through entity tools. When users ask about their orders or account:\n1. ALWAYS use the Order entity tool to query their order history\n2. Use the Customer entity tool to look up account details\n3. Analyze the data and provide personalized responses\n\nAlways query the relevant entities first before answering questions about user data.", + "tool_configs": [ + { "entity_name": "Order", "allowed_operations": ["read"] }, + { "entity_name": "Customer", "allowed_operations": ["read"] } + ] +} +``` + +Without explicit instructions to use the entity tools, the agent may not proactively query user data when asked. + +## Use Cases + +- After defining new agents in your project +- When modifying existing agent configurations +- To sync agent changes before testing +- As part of your development workflow when agent behavior changes + +## Notes + +- This command syncs the agent configuration, not conversation data +- Changes are applied to your Base44 project immediately +- Make sure to test agent changes in a development environment first +- Agent definitions are located in the `base44/agents/` directory +- Use `base44 agents pull` to download agents from Base44 diff --git a/evals/fixtures/anti-hallucination/skills/base44-cli/references/auth-login.md b/evals/fixtures/anti-hallucination/skills/base44-cli/references/auth-login.md new file mode 100644 index 0000000..adebd27 --- /dev/null +++ b/evals/fixtures/anti-hallucination/skills/base44-cli/references/auth-login.md @@ -0,0 +1,54 @@ +# base44 login + +Authenticate with Base44 using device code flow. + +## Syntax + +```bash +npx base44 login +``` + +## Authentication + +**Required**: No (this is the login command itself) + +## How It Works + +The login command uses OAuth 2.0 device code flow for authentication: + +1. Generates a device code for authentication +2. Displays a verification code and verification URI +3. Directs you to visit the URI and enter the code +4. Polls for authentication completion (up to device code expiration) +5. Retrieves access and refresh tokens upon successful authentication +6. Fetches and displays your user information +7. Saves authentication data locally with expiration timestamp + +## Interactive Flow + +```bash +$ npx base44 login + +Please visit: https://auth.base44.com/device +Enter code: ABCD-EFGH + +Waiting for authentication... +✓ Successfully authenticated! + +Logged in as: user@example.com +``` + +## Session Management + +- Authentication tokens are stored locally on your device +- Tokens include expiration timestamps +- The session persists across CLI sessions +- Other commands will automatically use your stored credentials +- Use `npx base44 logout` to clear your session +- Use `npx base44 whoami` to check your current authentication status + +## Notes + +- You only need to login once per device +- If your session expires, you'll be prompted to login again when running authenticated commands +- The CLI automatically prompts for login when you run commands that require authentication diff --git a/evals/fixtures/anti-hallucination/skills/base44-cli/references/auth-logout.md b/evals/fixtures/anti-hallucination/skills/base44-cli/references/auth-logout.md new file mode 100644 index 0000000..33c2ddf --- /dev/null +++ b/evals/fixtures/anti-hallucination/skills/base44-cli/references/auth-logout.md @@ -0,0 +1,32 @@ +# base44 logout + +Logout from current device and clear stored authentication data. + +## Syntax + +```bash +npx base44 logout +``` + +## Authentication + +**Required**: No + +## What It Does + +- Deletes stored authentication data from your device +- Clears your local session +- Removes access and refresh tokens + +## Output + +```bash +$ npx base44 logout +Logged out successfully +``` + +## Notes + +- You can logout even if you're not currently logged in (no error) +- After logout, you'll need to run `npx base44 login` again to use authenticated commands +- This only affects the current device; your Base44 account remains active diff --git a/evals/fixtures/anti-hallucination/skills/base44-cli/references/auth-whoami.md b/evals/fixtures/anti-hallucination/skills/base44-cli/references/auth-whoami.md new file mode 100644 index 0000000..abe450c --- /dev/null +++ b/evals/fixtures/anti-hallucination/skills/base44-cli/references/auth-whoami.md @@ -0,0 +1,37 @@ +# base44 whoami + +Display the currently authenticated user. + +## Syntax + +```bash +npx base44 whoami +``` + +## Authentication + +**Required**: Yes. If not authenticated, you'll be prompted to login first. + +## What It Does + +- Reads stored authentication data +- Displays the email of the currently logged-in user + +## Output + +```bash +$ npx base44 whoami +Logged in as: user@example.com +``` + +## Use Cases + +- Verify you're logged in before running other commands +- Check which account you're currently using +- Confirm authentication is working properly +- Useful in scripts or automation to verify credentials + +## Notes + +- If you're not logged in, the command will prompt you to authenticate first +- The email displayed matches your Base44 account email diff --git a/evals/fixtures/anti-hallucination/skills/base44-cli/references/create.md b/evals/fixtures/anti-hallucination/skills/base44-cli/references/create.md new file mode 100644 index 0000000..7580645 --- /dev/null +++ b/evals/fixtures/anti-hallucination/skills/base44-cli/references/create.md @@ -0,0 +1,111 @@ +# base44 create + +Creates a new Base44 project from a template. This command is framework-agnostic and can either scaffold a complete project or add Base44 configuration to an existing project. + +## Critical: Non-Interactive Mode Required + +ALWAYS provide both the project name AND `--path` flag. Without both, the command opens an interactive TUI which agents cannot use properly. + +WRONG: `npx base44 create` +WRONG: `npx base44 create my-app` +RIGHT: `npx base44 create my-app -p ./my-app` + +## Syntax + +```bash +npx base44 create [name] --path [options] +``` + +## Arguments & Options + +| Argument/Option | Description | Required | +|--------|-------------|----------| +| `name` | Project name (positional argument) | Yes* | +| `-p, --path ` | Path where to create the project | Yes* | +| `-t, --template ` | Template ID (see templates below) | No | +| `--deploy` | Build and deploy the site (includes pushing entities) | No | +| `--no-skills` | Skip AI agent skills installation (skills are added by default) | No | + +*Required for non-interactive mode. Both `name` and `--path` must be provided together. + +## Template Selection (CRITICAL - Choose Appropriately) + +**You MUST select the most appropriate template based on user requirements:** + +| Template ID | When to Use | Example Scenarios | +|-------------|-------------|-------------------| +| `backend-and-client` | Creating a NEW full-stack web app from scratch | "Create a task app", "Build me a dashboard", "Make a SaaS app" | +| `backend-only` | Adding Base44 to an EXISTING project OR using a different framework (Next.js, Vue, Svelte, etc.) | "Add Base44 to my project", "I want to use Next.js", "I already have a frontend" | + +**Default Choice:** When the user asks to "create an app" or "build a project" without specifying a particular framework, use `backend-and-client` to provide a complete, production-ready application with Vite + React + Tailwind. + +## The `--path` Flag + +- **For `backend-and-client` template (new projects):** Use a new subfolder path + ```bash + npx base44 create my-app -p ./my-app -t backend-and-client + ``` +- **For `backend-only` template (existing projects):** Use `-p .` in the current directory + ```bash + npx base44 create my-app -p . + ``` + +## Workflow: Using `backend-only` with External Frameworks + +**CRITICAL: The project folder MUST exist BEFORE running `base44 create` with `backend-only`** + +The `backend-only` template only adds Base44 configuration files - it does NOT create a frontend. If you need a frontend with a specific framework: + +```bash +# Step 1: Initialize the frontend project FIRST +npm create vite@latest my-app -- --template react # or vue, svelte, etc. +# OR: npx create-next-app@latest my-app +# OR: any other framework's init command + +# Step 2: Navigate into the created folder +cd my-app + +# Step 3: Install Base44 CLI +npm install --save-dev base44 + +# Step 4: Add Base44 configuration +npx base44 create my-app -p . +``` + +**WARNING:** Do NOT: +- Create an empty folder manually, then try to run `npx create vite` inside it (will fail - folder exists) +- Run `base44 create` with `backend-only` expecting it to create a frontend (it won't) + +**DO:** +- Run the external framework's init command FIRST (it creates its own folder) +- Then run `base44 create` inside that folder with `-p .` + +## Examples + +```bash +# RECOMMENDED: Create full-stack project (for new apps) +npx base44 create my-app -p ./my-app -t backend-and-client + +# Create full-stack and deploy in one step +npx base44 create my-app -p ./my-app -t backend-and-client --deploy + +# Add Base44 to EXISTING project (must be inside the project folder) +npx base44 create my-app -p . + +# Add Base44 to existing project and deploy +npx base44 create my-app -p . --deploy + +# Create without adding AI agent skills +npx base44 create my-app -p . --no-skills +``` + +## What It Does + +1. Applies the selected template to the target path +2. Creates a `base44/` folder with configuration files +3. Registers the project with Base44 backend +4. Creates `base44/.app.jsonc` with the app ID +5. If `--deploy` is used: + - Pushes any entities defined in `base44/entities/` + - Runs install and build commands (for templates with frontend) + - Deploys the site to Base44 hosting diff --git a/evals/fixtures/anti-hallucination/skills/base44-cli/references/dashboard.md b/evals/fixtures/anti-hallucination/skills/base44-cli/references/dashboard.md new file mode 100644 index 0000000..95a534a --- /dev/null +++ b/evals/fixtures/anti-hallucination/skills/base44-cli/references/dashboard.md @@ -0,0 +1,35 @@ +# base44 dashboard + +Opens the Base44 app dashboard in your default web browser. + +## Syntax + +```bash +npx base44 dashboard +``` + +## Options + +This command has no options. + +## What It Does + +1. Reads the project's app ID from `base44/.app.jsonc` +2. Opens the dashboard URL in your default browser + +## Example + +```bash +# Open dashboard for current project +npx base44 dashboard +``` + +## Requirements + +- Must be run from a linked Base44 project directory (contains `base44/.app.jsonc`) +- Must be authenticated (run `npx base44 login` first) + +## Notes + +- The dashboard provides a web interface to manage your app's entities, functions, users, and settings +- If you're not in a project directory, the command will fail with an error diff --git a/evals/fixtures/anti-hallucination/skills/base44-cli/references/deploy.md b/evals/fixtures/anti-hallucination/skills/base44-cli/references/deploy.md new file mode 100644 index 0000000..500526f --- /dev/null +++ b/evals/fixtures/anti-hallucination/skills/base44-cli/references/deploy.md @@ -0,0 +1,86 @@ +# base44 deploy + +Deploys all project resources (entities, functions, and site) to Base44 in a single command. + +## Syntax + +```bash +npx base44 deploy [options] +``` + +## Options + +| Option | Description | +|--------|-------------| +| `-y, --yes` | Skip confirmation prompt | + +## What It Deploys + +The command automatically detects and deploys: + +1. **Entities** - All `.jsonc` files in `base44/entities/` +2. **Functions** - All functions in `base44/functions/` +3. **Agents** - All agent configurations in `base44/agents/` +4. **Site** - Built files from `site.outputDirectory` (if configured) + +## Examples + +```bash +# Interactive mode - shows what will be deployed and asks for confirmation +npx base44 deploy + +# Non-interactive - skip confirmation (for CI/CD or agent use) +npx base44 deploy -y +``` + +## Typical Workflow + +```bash +# 1. Make your changes (entities, functions, frontend code) + +# 2. Build the frontend (if you have one) +npm run build + +# 3. Deploy everything +npx base44 deploy -y +``` + +## What It Does + +1. Reads project configuration from `base44/config.jsonc` +2. Detects available resources (entities, functions, site) +3. Shows a summary of what will be deployed +4. Asks for confirmation (unless `-y` flag is used) +5. Deploys all resources in sequence: + - Pushes entity schemas + - Deploys functions + - Pushes agent configurations + - Uploads site files +6. Displays the dashboard URL and app URL (if site was deployed) + +## Requirements + +- Must be run from a linked Base44 project directory +- Must be authenticated (run `npx base44 login` first) +- For site deployment, must run `npm run build` first + +## Output + +After successful deployment: +- **Dashboard**: Link to your app's management dashboard +- **App URL**: Your deployed site's public URL (if site was included) + +## Notes + +- If no resources are found, the command exits with a message +- Use individual commands (`entities push`, `functions deploy`, `site deploy`) if you only want to deploy specific resources +- The site must be built before deployment - this command does not run `npm run build` for you + +## Related Commands + +| Command | Description | +|---------|-------------| +| `base44 entities push` | Push only entities | +| `base44 functions deploy` | Deploy only functions | +| `base44 agents push` | Push only agents | +| `base44 site deploy` | Deploy only the site | diff --git a/evals/fixtures/anti-hallucination/skills/base44-cli/references/entities-create.md b/evals/fixtures/anti-hallucination/skills/base44-cli/references/entities-create.md new file mode 100644 index 0000000..29b40aa --- /dev/null +++ b/evals/fixtures/anti-hallucination/skills/base44-cli/references/entities-create.md @@ -0,0 +1,552 @@ +# Creating Entities + +Base44 entities are defined locally in your project and then pushed to the Base44 backend. + +## Critical: File Naming + +Entity files MUST use kebab-case naming: `{kebab-case-name}.jsonc` + +| Entity Name | File Name | +|-------------|-----------| +| `Task` | `task.jsonc` | +| `TeamMember` | `team-member.jsonc` | +| `ActivityLog` | `activity-log.jsonc` | + +WRONG: `TeamMember.jsonc`, `teamMember.jsonc` +RIGHT: `team-member.jsonc` + +## Table of Contents + +- [Creating Entities](#creating-entities) + - [Entity Directory](#entity-directory) + - [How to Create an Entity](#how-to-create-an-entity) + - [Entity Schema Structure](#entity-schema-structure) + - [Supported Field Types](#supported-field-types) + - [Field Properties](#field-properties) + - [Complete Example](#complete-example) + - [Naming Conventions](#naming-conventions) + - [Relationships Between Entities](#relationships-between-entities) + - [Row Level Security (RLS)](#row-level-security-rls) + - [Field Level Security (FLS)](#field-level-security-fls) + - [Pushing Entities](#pushing-entities) + +## Entity Directory + +All entity definitions must be placed in the `base44/entities/` folder in your project root. Each entity is defined in its own `.jsonc` file. + +Example structure: +``` +my-app/ + base44/ + entities/ + user.jsonc + product.jsonc + order.jsonc +``` + +## How to Create an Entity + +1. Create a new `.jsonc` file in the `base44/entities/` directory +2. Define your entity schema following the structure below +3. Push the changes to Base44 using the CLI + +## Entity Schema Structure + +Each entity file follows a JSON Schema-like structure: + +```jsonc +{ + "name": "EntityName", // PascalCase entity name + "type": "object", // Always "object" + "properties": { + // Define your fields here + }, + "required": ["field1"] // Array of required field names +} +``` + +### Common Mistake: Nested Schema Property + +**WRONG** - Do NOT wrap properties in a `schema` object: +```jsonc +{ + "name": "Task", + "description": "A task entity", + "schema": { // ❌ WRONG - don't use nested "schema" + "type": "object", + "properties": { ... } + } +} +``` + +**CORRECT** - Put `type` and `properties` at the top level: +```jsonc +{ + "name": "Task", + "description": "A task entity", + "type": "object", // ✅ CORRECT - top level + "properties": { ... } // ✅ CORRECT - top level +} +``` + +This is a common mistake that will cause "Invalid schema: Schema must have a 'type' field" errors when pushing entities. + +## Supported Field Types + +### String + +Basic text field: +```jsonc +{ + "title": { + "type": "string", + "description": "Task title" + } +} +``` + +With format: +```jsonc +{ + "due_date": { + "type": "string", + "format": "date", + "description": "Due date" + } +} +``` + +Available formats: `date`, `date-time`, `time`, `email`, `uri`, `hostname`, `ipv4`, `ipv6`, `uuid`, `file`, `regex`, `richtext` + +### String with Enum + +Constrained to specific values: +```jsonc +{ + "status": { + "type": "string", + "enum": ["todo", "in_progress", "done"], + "default": "todo", + "description": "Current status" + } +} +``` + +### Number + +```jsonc +{ + "position": { + "type": "number", + "description": "Position for ordering" + } +} +``` + +### Integer + +For whole numbers only: +```jsonc +{ + "quantity": { + "type": "integer", + "description": "Item quantity", + "minimum": 0, + "maximum": 1000 + } +} +``` + +### Binary + +For file/blob data: +```jsonc +{ + "attachment": { + "type": "binary", + "description": "File attachment" + } +} +``` + +### Boolean + +```jsonc +{ + "notify_on_change": { + "type": "boolean", + "default": true, + "description": "Enable notifications" + } +} +``` + +### Array of Strings + +```jsonc +{ + "labels": { + "type": "array", + "items": { "type": "string" }, + "description": "Task labels/tags" + } +} +``` + +### Array of Objects + +```jsonc +{ + "attachments": { + "type": "array", + "description": "File attachments", + "items": { + "type": "object", + "properties": { + "name": { "type": "string" }, + "url": { "type": "string" }, + "type": { "type": "string" } + } + } + } +} +``` + +## Field Properties + +| Property | Description | +| ------------- | ---------------------------------------------------------------------------------------- | +| `type` | Data type: `string`, `number`, `integer`, `boolean`, `array`, `object`, `binary` | +| `description` | Human-readable description of the field | +| `enum` | Array of allowed values (for strings) | +| `enumNames` | Human-readable labels for enum values (same order as `enum`) | +| `default` | Default value when not provided | +| `format` | Format hint: `date`, `date-time`, `time`, `email`, `uri`, `hostname`, `ipv4`, `ipv6`, `uuid`, `file`, `regex`, `richtext` | +| `items` | Schema for array items | +| `properties` | Nested properties for object types | +| `$ref` | Reference to another schema definition | +| `minLength` | Minimum string length | +| `maxLength` | Maximum string length | +| `pattern` | Regex pattern for string validation | +| `minimum` | Minimum value for numbers | +| `maximum` | Maximum value for numbers | +| `rls` | Field-level security rules (see Field Level Security section) | + +## Complete Example + +Here's a complete entity definition for a Task: + +```jsonc +{ + "name": "Task", + "type": "object", + "properties": { + "title": { + "type": "string", + "description": "Task title" + }, + "description": { + "type": "string", + "description": "Task description" + }, + "status": { + "type": "string", + "enum": ["todo", "in_progress", "done"], + "default": "todo", + "description": "Current status of the task" + }, + "board_id": { + "type": "string", + "description": "Board this task belongs to" + }, + "assignee_email": { + "type": "string", + "description": "Email of assigned user" + }, + "priority": { + "type": "string", + "enum": ["low", "medium", "high"], + "default": "medium", + "description": "Task priority" + }, + "due_date": { + "type": "string", + "format": "date", + "description": "Due date" + }, + "labels": { + "type": "array", + "items": { "type": "string" }, + "description": "Task labels/tags" + } + }, + "required": ["title"] +} +``` + +## Naming Conventions + +- **Entity name**: Use PascalCase with alphanumeric characters only (e.g., `Task`, `TeamMember`, `ActivityLog`) + - Must match pattern: `/^[a-zA-Z0-9]+$/` + - Valid: `Task`, `TeamMember`, `Order123` + - Invalid: `Team_Member`, `Team-Member`, `Team Member` +- **File name**: Use kebab-case matching the entity (e.g., `task.jsonc`, `team-member.jsonc`, `activity-log.jsonc`) +- **Field names**: Use snake_case (e.g., `board_id`, `user_email`, `due_date`) + +## Relationships Between Entities + +To create relationships between entities, use ID reference fields: + +```jsonc +{ + "board_id": { + "type": "string", + "description": "Board this task belongs to" + }, + "team_id": { + "type": "string", + "description": "Associated team ID" + } +} +``` + +## Row Level Security (RLS) + +Row Level Security (RLS) controls which records users can access based on their identity and attributes. RLS rules are defined per entity inside the `rls` field of the schema. + +**Important:** If no RLS is defined, all records are accessible to all users. + +### RLS Operations + +RLS supports five operations: + +| Operation | Description | +|-----------|-------------| +| `create` | Control who can add new records | +| `read` | Control who can view records | +| `update` | Control who can modify records | +| `delete` | Control who can remove records | +| `write` | Shorthand for `create`, `update`, and `delete` combined | + +### Permission Values + +Each operation accepts one of the following values: + +1. **`true`** - Allow all users (including anonymous/unauthenticated) +2. **`false`** - Block all users +3. **Condition object** - Allow users matching the condition + +### Template Variables + +Use template variables to reference the current user's attributes: + +| Template | Description | +|----------|-------------| +| `{{user.id}}` | The user's ID | +| `{{user.email}}` | The user's email | +| `{{user.role}}` | The user's role | +| `{{user.data.field_name}}` | Custom field from the user's `data` object | + +### Built-in Entity Attributes + +Every entity record has these built-in attributes available for RLS rules: + +| Attribute | Description | +|-----------|-------------| +| `id` | Unique record identifier | +| `created_date` | Timestamp when record was created | +| `updated_date` | Timestamp when record was last updated | +| `created_by` | Email of the user who created the record | + +### Rule Types + +There are two condition types you can use: + +**1. Entity-to-user comparison** - Compare record fields to the current user's values: +```jsonc +{ + "created_by": "{{user.email}}" +} +``` + +**2. User condition check** - Check user properties directly using `user_condition`: +```jsonc +{ + "user_condition": { "role": "admin" } +} +``` + +**Important limitations:** +- `user_condition` only supports **simple equality** (e.g., `{ "role": "admin" }`) +- For `data.*` field comparisons, you can use operators: `$in`, `$nin`, `$ne`, `$all` +- Logical operators `$or`, `$and`, `$nor` are available for combining conditions +- You cannot filter by entity field values directly (e.g., `{"status": "published"}`) +- Only user-related conditions are allowed + +### RLS Examples + +**Owner-only access:** +```jsonc +{ + "created_by": "{{user.email}}" +} +``` + +**Department-based access:** +```jsonc +{ + "data.department": "{{user.data.department}}" +} +``` + +**Admin-only access:** +```jsonc +{ + "user_condition": { "role": "admin" } +} +``` + +**Complete RLS configuration:** +```jsonc +{ + "name": "Task", + "type": "object", + "properties": { + "title": { + "type": "string", + "description": "Task title" + }, + "status": { + "type": "string", + "enum": ["todo", "in_progress", "done"], + "default": "todo" + } + }, + "required": ["title"], + "rls": { + "create": true, + "read": { "created_by": "{{user.email}}" }, + "update": { "created_by": "{{user.email}}" }, + "delete": { "created_by": "{{user.email}}" } + } +} +``` + +### Common RLS Patterns + +**Public create, admin-only management (e.g., contact forms, waitlists):** +```jsonc +{ + "rls": { + "create": true, + "read": { "user_condition": { "role": "admin" } }, + "update": { "user_condition": { "role": "admin" } }, + "delete": { "user_condition": { "role": "admin" } } + } +} +``` + +**Owner-only access:** +```jsonc +{ + "rls": { + "create": true, + "read": { "created_by": "{{user.email}}" }, + "update": { "created_by": "{{user.email}}" }, + "delete": { "created_by": "{{user.email}}" } + } +} +``` + +**Logged-in users only:** +```jsonc +{ + "rls": { + "create": { "user_condition": { "id": "{{user.id}}" } }, + "read": true, + "update": { "created_by": "{{user.email}}" }, + "delete": { "created_by": "{{user.email}}" } + } +} +``` + +### Limitations + +- **user_condition is equality only:** `user_condition` only supports exact match (e.g., `{ "role": "admin" }`) - no operators +- **No comparison operators on user_condition:** `$gt`, `$lt`, `$regex`, `$expr`, `$where` are NOT supported for user conditions +- **No entity field filtering:** Cannot filter by entity field values (e.g., `{"status": "published"}`) +- **No deeply nested templates:** Templates like `{{user.data.profile.department}}` may not work + +**Supported operators:** +- **Logical operators:** `$or`, `$and`, `$nor` for combining multiple conditions +- **Field operators (for `data.*` fields only):** `$in`, `$nin`, `$ne`, `$all` + +### Complex Access Patterns + +For complex access patterns that require multiple conditions (e.g., "owner OR admin"), you have two options: + +1. **Use the Base44 Dashboard UI** - The dashboard allows adding multiple rules per operation with OR logic +2. **Use separate entities** - Split data into multiple entities with different access rules +3. **Use backend functions** - Implement custom access logic in backend functions + +## Field Level Security (FLS) + +Field Level Security allows you to control access to individual fields within an entity. FLS rules are defined within each field's schema using the `rls` property. + +### FLS Operations + +FLS supports the same operations as entity-level RLS: + +| Operation | Description | +|-----------|-------------| +| `create` | Control who can set this field when creating records | +| `read` | Control who can view this field | +| `update` | Control who can modify this field | +| `delete` | Control who can clear this field | +| `write` | Shorthand for `create`, `update`, and `delete` combined | + +### FLS Example + +```jsonc +{ + "name": "Employee", + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "Employee name" + }, + "salary": { + "type": "number", + "description": "Employee salary", + "rls": { + "read": { "user_condition": { "role": "hr" } }, + "update": { "user_condition": { "role": "hr" } } + } + }, + "department": { + "type": "string", + "description": "Department name" + } + }, + "required": ["name"] +} +``` + +In this example, only users with the `hr` role can read or update the `salary` field. All users with access to the entity can read/update other fields. + +### FLS Notes + +- If no field-level RLS is defined, the field inherits the entity-level RLS rules +- FLS rules follow the same condition format as entity-level RLS +- Use FLS for sensitive fields like salary, SSN, or internal notes + +## Pushing Entities + +The `entities push` command will push all entities that exist in the `base44/entities` folder. + +```bash +npx base44 entities push +``` + +For more details on the push command, see [entities-push.md](entities-push.md). diff --git a/evals/fixtures/anti-hallucination/skills/base44-cli/references/entities-push.md b/evals/fixtures/anti-hallucination/skills/base44-cli/references/entities-push.md new file mode 100644 index 0000000..63b92d7 --- /dev/null +++ b/evals/fixtures/anti-hallucination/skills/base44-cli/references/entities-push.md @@ -0,0 +1,71 @@ +# base44 entities push + +Push local entity definitions to Base44. + +## Syntax + +```bash +npx base44 entities push +``` + +## Authentication + +**Required**: Yes. If not authenticated, you'll be prompted to login first. + +## What It Does + +1. Pushes all entities that exist in the `base44/entities` folder +2. Validates that entities exist in the folder +3. Displays the count of entities to be pushed +4. Uploads entities to the Base44 backend +5. Reports the results: created, updated, and deleted entities + +## Prerequisites + +- Must be run from a Base44 project directory +- Project must have entity definitions in the `base44/entities` folder + +## Output + +```bash +$ npx base44 entities push + +Found 3 entities to push +Pushing entities to Base44... + +Created: User, Post +Updated: Comment +Deleted: OldEntity + +✓ Entities pushed successfully +``` + +## Entity Synchronization + +The push operation synchronizes your local entity schema with Base44: + +- **Created**: New entities that didn't exist in Base44 +- **Updated**: Existing entities with modified schema or configuration +- **Deleted**: Entities that were removed from your local configuration + +## Error Handling + +If no entities are found in your project: +```bash +$ npx base44 entities push +No entities found in project +``` + +## Use Cases + +- After defining new entities in your project +- When modifying existing entity schemas +- To sync entity changes before deploying +- As part of your development workflow when data models change + +## Notes + +- This command syncs the entity schema/structure, not the actual data +- Changes are applied to your Base44 project immediately +- Make sure to test entity changes in a development environment first +- Entity definitions are located in the `base44/entities/` directory diff --git a/evals/fixtures/anti-hallucination/skills/base44-cli/references/functions-create.md b/evals/fixtures/anti-hallucination/skills/base44-cli/references/functions-create.md new file mode 100644 index 0000000..c523cdc --- /dev/null +++ b/evals/fixtures/anti-hallucination/skills/base44-cli/references/functions-create.md @@ -0,0 +1,229 @@ +# Creating Functions + +Base44 functions are serverless backend functions that run on Deno. They are defined locally in your project and deployed to the Base44 backend. + +## Function Directory + +All function definitions must be placed in the `base44/functions/` folder in your project. Each function lives in its own subdirectory with a configuration file and entry point. + +Example structure: +``` +my-app/ + base44/ + functions/ + process-order/ + function.jsonc + index.ts + send-notification/ + function.jsonc + index.ts +``` + +## How to Create a Function + +1. Create a new directory in `base44/functions/` with your function name (use kebab-case) +2. Create a `function.jsonc` configuration file in the directory +3. Create the entry point file (e.g., `index.ts`) +4. Deploy the function using the CLI + +## Function Configuration + +Each function requires a `function.jsonc` configuration file: + +```jsonc +{ + "name": "my-function", + "entry": "index.ts" +} +``` + +### Configuration Properties + +| Property | Description | Required | +|----------|-------------|----------| +| `name` | Function name (must match `/^[^.]+$/` - no dots allowed) | Yes | +| `entry` | Entry point file path relative to the function directory (min 1 char) | Yes | + +## Entry Point File + +Functions run on Deno and must export using `Deno.serve()`. Use `npm:` prefix for npm packages. + +```typescript +import { createClientFromRequest } from "npm:@base44/sdk"; + +Deno.serve(async (req) => { + // Get authenticated client from request + const base44 = createClientFromRequest(req); + + // Parse input + const { orderId, action } = await req.json(); + + // Your logic here + const order = await base44.entities.Orders.get(orderId); + + // Return response + return Response.json({ + success: true, + order: order + }); +}); +``` + +### Request Object + +The function receives a standard Deno `Request` object: +- `req.json()` - Parse JSON body +- `req.text()` - Get raw text body +- `req.headers` - Access request headers +- `req.method` - HTTP method + +### Response Object + +Return using `Response.json()` for JSON responses: + +```typescript +// Success response +return Response.json({ data: result }); + +// Error response with status code +return Response.json({ error: "Something went wrong" }, { status: 400 }); + +// Not found +return Response.json({ error: "Order not found" }, { status: 404 }); +``` + +## Complete Example + +### Directory Structure +``` +base44/ + functions/ + process-order/ + function.jsonc + index.ts +``` + +### function.jsonc +```jsonc +{ + "name": "process-order", + "entry": "index.ts" +} +``` + +### index.ts +```typescript +import { createClientFromRequest } from "npm:@base44/sdk"; + +Deno.serve(async (req) => { + try { + const base44 = createClientFromRequest(req); + const { orderId } = await req.json(); + + // Validate input + if (!orderId) { + return Response.json( + { error: "Order ID is required" }, + { status: 400 } + ); + } + + // Fetch and process the order + const order = await base44.entities.Orders.get(orderId); + if (!order) { + return Response.json( + { error: "Order not found" }, + { status: 404 } + ); + } + + return Response.json({ + success: true, + orderId: order.id, + processedAt: new Date().toISOString() + }); + + } catch (error) { + return Response.json( + { error: error.message }, + { status: 500 } + ); + } +}); +``` + +## Using Service Role Access + +For admin-level operations, use `asServiceRole`: + +```typescript +import { createClientFromRequest } from "npm:@base44/sdk"; + +Deno.serve(async (req) => { + const base44 = createClientFromRequest(req); + + // Check user is authenticated + const user = await base44.auth.me(); + if (!user) { + return Response.json({ error: "Unauthorized" }, { status: 401 }); + } + + // Use service role for admin operations + const allOrders = await base44.asServiceRole.entities.Orders.list(); + + return Response.json({ orders: allOrders }); +}); +``` + +## Using Secrets + +Access environment variables configured in the app dashboard: + +```typescript +Deno.serve(async (req) => { + // Access environment variables (configured in app settings) + const apiKey = Deno.env.get("STRIPE_API_KEY"); + + const response = await fetch("https://api.stripe.com/v1/charges", { + headers: { + "Authorization": `Bearer ${apiKey}` + } + }); + + return Response.json(await response.json()); +}); +``` + +## Naming Conventions + +- **Directory name**: Use kebab-case (e.g., `process-order`, `send-notification`) +- **Function name**: Match the directory name, must match pattern `/^[^.]+$/` (no dots allowed) + - Valid: `process-order`, `send_notification`, `myFunction` + - Invalid: `process.order`, `send.notification.v2` +- **Entry file**: Typically `index.ts` or `index.js` + +## Deploying Functions + +After creating your function, deploy it to Base44: + +```bash +npx base44 functions deploy +``` + +For more details on deploying, see [functions-deploy.md](functions-deploy.md). + +## Notes + +- Functions run on Deno runtime, not Node.js +- Use `npm:` prefix for npm packages (e.g., `npm:@base44/sdk`) +- Use `createClientFromRequest(req)` to get a client that inherits the caller's auth context +- Configure secrets via app dashboard for API keys +- Make sure to handle errors gracefully and return appropriate HTTP status codes + +## Common Mistakes + +| Wrong | Correct | Why | +|-------|---------|-----| +| `functions/myFunction.js` (single file) | `functions/my-function/index.ts` + `function.jsonc` | Functions require subdirectory with config | +| `import { ... } from "@base44/sdk"` | `import { ... } from "npm:@base44/sdk"` | Deno requires `npm:` prefix for npm packages | +| `MyFunction` or `myFunction` directory | `my-function` directory | Use kebab-case for directory names | diff --git a/evals/fixtures/anti-hallucination/skills/base44-cli/references/functions-deploy.md b/evals/fixtures/anti-hallucination/skills/base44-cli/references/functions-deploy.md new file mode 100644 index 0000000..f89d796 --- /dev/null +++ b/evals/fixtures/anti-hallucination/skills/base44-cli/references/functions-deploy.md @@ -0,0 +1,78 @@ +# base44 functions deploy + +Deploy local function definitions to Base44. + +## Syntax + +```bash +npx base44 functions deploy +``` + +## Authentication + +**Required**: Yes. If not authenticated, you'll be prompted to login first. + +## What It Does + +1. Scans the `base44/functions/` directory for function definitions +2. Validates that functions exist and have valid configurations +3. Displays the count of functions to be deployed +4. Uploads function code and configuration to Base44 +5. Reports the results: deployed and deleted functions + +## Prerequisites + +- Must be run from a Base44 project directory +- Project must have function definitions in the `base44/functions/` folder +- Each function must have a valid `function.jsonc` config file + +## Output + +```bash +$ npx base44 functions deploy + +Found 2 functions to deploy +Deploying functions to Base44... + +Deployed: process-order, send-notification +Deleted: old-function + +✓ Functions deployed successfully +``` + +## Function Synchronization + +The deploy operation synchronizes your local functions with Base44: + +- **Deployed**: Functions that were created or updated +- **Deleted**: Functions that were removed from your local configuration + +## Error Handling + +If no functions are found in your project: +```bash +$ npx base44 functions deploy +No functions found. Create functions in the 'functions' directory. +``` + +If a function has configuration errors: +```bash +$ npx base44 functions deploy +Function deployment errors: +'my-function' function: Entry point cannot be empty +``` + +## Use Cases + +- After creating new functions in your project +- When modifying existing function code or configuration +- To sync function changes before testing +- As part of your development workflow when backend logic changes + +## Notes + +- This command deploys the function code and configuration +- Changes are applied to your Base44 project immediately +- Make sure to test functions in a development environment first +- Function definitions are located in the `base44/functions/` directory +- For how to create functions, see [functions-create.md](functions-create.md) diff --git a/evals/fixtures/anti-hallucination/skills/base44-cli/references/link.md b/evals/fixtures/anti-hallucination/skills/base44-cli/references/link.md new file mode 100644 index 0000000..0b009ce --- /dev/null +++ b/evals/fixtures/anti-hallucination/skills/base44-cli/references/link.md @@ -0,0 +1,81 @@ +# base44 link + +Links an existing local Base44 project to a Base44 app in the cloud. Use this when you have a `base44/config.jsonc` but haven't connected it to a Base44 app yet. + +## Critical: When to Use Link vs Create + +| Scenario | Command | +|----------|---------| +| Starting fresh, no `base44/` folder | `npx base44 create` | +| Have `base44/config.jsonc` but no `.app.jsonc` | `npx base44 link` | +| Project already linked (has `.app.jsonc`) | Already done, use `deploy` | + +## Syntax + +```bash +npx base44 link [options] +``` + +## Options + +| Option | Description | Required | +|--------|-------------|----------| +| `-c, --create` | Create a new project (skip selection prompt) | No | +| `-n, --name ` | Project name (required when `--create` is used) | With `--create` | +| `-d, --description ` | Project description | No | +| `-p, --projectId ` | Project ID to link to an existing project (skip selection prompt) | No | + +## Non-Interactive Mode + +For CI/CD or agent use: + +**Create a new project:** +```bash +npx base44 link --create --name my-app +``` + +**Link to an existing project:** +```bash +npx base44 link --projectId +``` + +WRONG: `npx base44 link --create` (missing --name) +WRONG: `npx base44 link --create --projectId ` (cannot use both) +RIGHT: `npx base44 link --create --name my-app` +RIGHT: `npx base44 link --projectId ` + +## Examples + +```bash +# Interactive mode - prompts for project details +npx base44 link + +# Non-interactive - create and link in one step +npx base44 link --create --name my-app + +# With description +npx base44 link --create --name my-app --description "My awesome app" + +# Link to a specific existing project by ID +npx base44 link --projectId abc123 +``` + +## What It Does + +1. Finds the `base44/config.jsonc` in the current directory (or parent directories) +2. Verifies no `.app.jsonc` exists (project not already linked) +3. Either: + - Creates a new Base44 app in the cloud (with `--create`), OR + - Links to an existing project (with `--projectId` or interactive selection) +4. Writes the app ID to `base44/.app.jsonc` + +## Requirements + +- Must have `base44/config.jsonc` in the project +- Must NOT have `base44/.app.jsonc` (use `deploy` if already linked) +- Must be authenticated (run `npx base44 login` first) + +## Notes + +- After linking, you can deploy resources with `npx base44 deploy` +- The `.app.jsonc` file should be git-ignored (contains your app ID) diff --git a/evals/fixtures/anti-hallucination/skills/base44-cli/references/rls-examples.md b/evals/fixtures/anti-hallucination/skills/base44-cli/references/rls-examples.md new file mode 100644 index 0000000..7d3ef42 --- /dev/null +++ b/evals/fixtures/anti-hallucination/skills/base44-cli/references/rls-examples.md @@ -0,0 +1,463 @@ +# RLS Examples + +Practical Row-Level Security patterns for common application types. + +**Important:** Base44 RLS supports: +- **Logical operators:** `$or`, `$and`, `$nor` for combining conditions +- **Field operators (for `data.*` fields):** `$in`, `$nin`, `$ne`, `$all` +- **user_condition:** Equality only (no operators) + +## Contents +- [Simple Patterns (JSON Schema)](#simple-patterns-json-schema) +- [Using Operators](#using-operators) +- [Field-Level Security Examples](#field-level-security-examples) +- [Complex Patterns (Dashboard UI or Backend)](#complex-patterns-dashboard-ui-or-backend) +- [Best Practices](#best-practices) + +--- + +## Simple Patterns (JSON Schema) + +These patterns work with the JSON schema RLS format. + +### Todo App - Owner-only access + +Users see and manage only their own tasks. + +```jsonc +{ + "name": "Task", + "type": "object", + "properties": { + "title": { "type": "string" }, + "description": { "type": "string" }, + "completed": { "type": "boolean" }, + "priority": { "type": "string", "enum": ["low", "medium", "high"] }, + "due_date": { "type": "string", "format": "date" } + }, + "rls": { + "create": true, + "read": { "created_by": "{{user.email}}" }, + "update": { "created_by": "{{user.email}}" }, + "delete": { "created_by": "{{user.email}}" } + } +} +``` + +### Contact Form - Public create, admin-only read + +Anyone can submit, only admins can view submissions. + +```jsonc +{ + "name": "ContactSubmission", + "type": "object", + "properties": { + "name": { "type": "string" }, + "email": { "type": "string", "format": "email" }, + "message": { "type": "string" } + }, + "rls": { + "create": true, + "read": { "user_condition": { "role": "admin" } }, + "update": { "user_condition": { "role": "admin" } }, + "delete": { "user_condition": { "role": "admin" } } + } +} +``` + +### User Profile - Self-management + +Users can only access their own profile. + +```jsonc +{ + "name": "UserProfile", + "type": "object", + "properties": { + "name": { "type": "string" }, + "avatar_url": { "type": "string" }, + "bio": { "type": "string" }, + "preferences": { "type": "object" } + }, + "rls": { + "create": true, + "read": { "created_by": "{{user.email}}" }, + "update": { "created_by": "{{user.email}}" }, + "delete": { "created_by": "{{user.email}}" } + } +} +``` + +### Department Data - Same department access + +Users can only see records from their department. + +```jsonc +{ + "name": "DepartmentAnnouncement", + "type": "object", + "properties": { + "title": { "type": "string" }, + "content": { "type": "string" }, + "department": { "type": "string" } + }, + "rls": { + "create": { "user_condition": { "role": "manager" } }, + "read": { "data.department": "{{user.data.department}}" }, + "update": { "user_condition": { "role": "manager" } }, + "delete": { "user_condition": { "role": "admin" } } + } +} +``` + +### Subscription - Admin-managed, user-readable via email field + +```jsonc +{ + "name": "Subscription", + "type": "object", + "properties": { + "user_email": { "type": "string" }, + "tier": { "type": "string", "enum": ["free", "basic", "pro", "enterprise"] }, + "credits": { "type": "number" }, + "renewal_date": { "type": "string", "format": "date" } + }, + "rls": { + "create": { "user_condition": { "role": "admin" } }, + "read": { "data.user_email": "{{user.email}}" }, + "update": { "user_condition": { "role": "admin" } }, + "delete": { "user_condition": { "role": "admin" } } + } +} +``` + +**Note:** This pattern only allows users to read their own subscription. Admins need to use the Dashboard UI to configure additional read access for themselves. + +### Private Data - Owner-only + +```jsonc +{ + "name": "PrivateNotes", + "type": "object", + "properties": { + "title": { "type": "string" }, + "content": { "type": "string" }, + "tags": { "type": "array", "items": { "type": "string" } } + }, + "rls": { + "create": true, + "read": { "created_by": "{{user.email}}" }, + "update": { "created_by": "{{user.email}}" }, + "delete": { "created_by": "{{user.email}}" } + } +} +``` + +### Public Read, Authenticated Write + +Anyone can read, only logged-in users can create/edit their own records. + +```jsonc +{ + "name": "BlogPost", + "type": "object", + "properties": { + "title": { "type": "string" }, + "content": { "type": "string" }, + "author_email": { "type": "string" } + }, + "rls": { + "create": true, + "read": true, + "update": { "created_by": "{{user.email}}" }, + "delete": { "created_by": "{{user.email}}" } + } +} +``` + +--- + +## Using Operators + +### Logical Operators + +Combine multiple conditions using `$or`, `$and`, or `$nor`: + +**Owner OR Admin access:** +```jsonc +{ + "name": "Document", + "type": "object", + "properties": { + "title": { "type": "string" }, + "content": { "type": "string" } + }, + "rls": { + "create": true, + "read": { + "$or": [ + { "created_by": "{{user.email}}" }, + { "user_condition": { "role": "admin" } } + ] + }, + "update": { + "$or": [ + { "created_by": "{{user.email}}" }, + { "user_condition": { "role": "admin" } } + ] + }, + "delete": { "user_condition": { "role": "admin" } } + } +} +``` + +**Multiple roles with $or:** +```jsonc +{ + "rls": { + "read": { + "$or": [ + { "user_condition": { "role": "admin" } }, + { "user_condition": { "role": "manager" } }, + { "user_condition": { "role": "hr" } } + ] + } + } +} +``` + +### Field Operators for data.* Fields + +Use `$in`, `$nin`, `$ne`, `$all` for comparing entity data fields: + +**Access based on tags ($in):** +```jsonc +{ + "rls": { + "read": { + "data.category": { "$in": ["public", "shared"] } + } + } +} +``` + +**Exclude specific statuses ($nin):** +```jsonc +{ + "rls": { + "read": { + "data.status": { "$nin": ["archived", "deleted"] } + } + } +} +``` + +**Not equal ($ne):** +```jsonc +{ + "rls": { + "read": { + "data.visibility": { "$ne": "private" } + } + } +} +``` + +**All tags must match ($all):** +```jsonc +{ + "rls": { + "read": { + "data.required_tags": { "$all": ["approved", "reviewed"] } + } + } +} +``` + +### Combining Logical and Field Operators + +```jsonc +{ + "rls": { + "read": { + "$and": [ + { "data.status": { "$ne": "draft" } }, + { + "$or": [ + { "created_by": "{{user.email}}" }, + { "data.visibility": "public" } + ] + } + ] + } + } +} +``` + +--- + +## Field-Level Security Examples + +Control access to specific fields within an entity. + +### Sensitive Salary Field + +```jsonc +{ + "name": "Employee", + "type": "object", + "properties": { + "name": { "type": "string" }, + "email": { "type": "string", "format": "email" }, + "salary": { + "type": "number", + "description": "Annual salary", + "rls": { + "read": { "user_condition": { "role": "hr" } }, + "write": { "user_condition": { "role": "hr" } } + } + }, + "performance_notes": { + "type": "string", + "description": "Manager notes", + "rls": { + "read": { + "$or": [ + { "user_condition": { "role": "manager" } }, + { "user_condition": { "role": "hr" } } + ] + }, + "write": { "user_condition": { "role": "manager" } } + } + } + } +} +``` + +### Admin-Only Internal Fields + +```jsonc +{ + "name": "Order", + "type": "object", + "properties": { + "order_number": { "type": "string" }, + "total": { "type": "number" }, + "internal_notes": { + "type": "string", + "description": "Internal processing notes", + "rls": { + "read": { "user_condition": { "role": "admin" } }, + "write": { "user_condition": { "role": "admin" } } + } + }, + "profit_margin": { + "type": "number", + "description": "Profit margin percentage", + "rls": { + "read": { "user_condition": { "role": "admin" } }, + "write": false + } + } + } +} +``` + +--- + +## Complex Patterns (Dashboard UI or Backend) + +Some patterns may still require the Dashboard UI or backend functions. + +### Bidirectional Relationships (e.g., Friendships, Matches) + +**Requirement:** Either party in a relationship should have access. + +**Now possible with $or:** +```jsonc +{ + "rls": { + "read": { + "$or": [ + { "data.user_a_email": "{{user.email}}" }, + { "data.user_b_email": "{{user.email}}" } + ] + } + } +} +``` + +**Alternative solutions:** +1. **Entity redesign:** Store two records per relationship (one for each party) +2. **Backend function:** Query with custom logic + +### Complex Business Logic + +**Requirement:** Access depends on multiple entity fields with complex conditions. + +**JSON Schema limitation:** While operators help, very complex business logic may still be hard to express. + +**Solution options:** +1. **Backend function:** Implement custom access logic +2. **Combine simpler rules:** Break complex rules into simpler entity-level and field-level rules + +--- + +## Best Practices + +### Security Strategy + +Use a combination of entity-level RLS and field-level security: + +| Data Type | Approach | Example | +|-----------|----------|---------| +| User-editable | Entity RLS: Owner-only | UserProfile with `created_by` check | +| Sensitive fields | Field-level RLS | Salary field with HR role check | +| Multi-role access | `$or` with user_condition | Admin OR Manager access | +| Conditional access | Field operators | `$in`, `$ne` on data fields | +| Public content | Entity RLS: `read: true` | PublicPost | +| Private content | Entity RLS: Owner-only | PrivateNote | + +### When to Use Each Approach + +| Requirement | Approach | +|-------------|----------| +| Single condition (owner, admin, department) | JSON Schema RLS | +| Multiple OR/AND conditions | JSON Schema RLS with `$or`/`$and` | +| Field value checks with `$in`/`$ne`/etc. | JSON Schema RLS for `data.*` fields | +| Field-level access control | JSON Schema FLS (field-level `rls`) | +| Complex comparison operators (`$gt`, `$lt`) | Backend functions | +| Very complex business logic | Backend functions | + +### Common Role Patterns + +| Role | Typical Access | +|------|----------------| +| `admin` | Full access to all records | +| `moderator` | Read/update access, limited delete | +| `manager` | Department-scoped access | +| `user` | Own records only | + +### Supported Operators Summary + +| Operator | Supported | Notes | +|----------|-----------|-------| +| `$or` | Yes | Combine multiple conditions | +| `$and` | Yes | All conditions must match | +| `$nor` | Yes | None of the conditions match | +| `$in` | Yes | For `data.*` fields only | +| `$nin` | Yes | For `data.*` fields only | +| `$ne` | Yes | For `data.*` fields only | +| `$all` | Yes | For `data.*` fields only | +| `$gt`, `$lt`, `$gte`, `$lte` | No | Use backend functions | +| `$regex` | No | Use backend functions | + +### Limitations Summary + +| Not Supported | Alternative | +|---------------|-------------| +| Operators on `user_condition` | Use equality only for user checks | +| Comparison operators (`$gt`, `$lt`) | Backend functions | +| Regex matching (`$regex`) | Backend functions | +| Cross-entity relationships | Backend functions | diff --git a/evals/fixtures/anti-hallucination/skills/base44-cli/references/site-deploy.md b/evals/fixtures/anti-hallucination/skills/base44-cli/references/site-deploy.md new file mode 100644 index 0000000..929d302 --- /dev/null +++ b/evals/fixtures/anti-hallucination/skills/base44-cli/references/site-deploy.md @@ -0,0 +1,118 @@ +# base44 site deploy + +Deploy built site files to Base44 hosting. + +## Table of Contents + +- [Syntax](#syntax) +- [Authentication](#authentication) +- [Prerequisites](#prerequisites) +- [How It Works](#how-it-works) +- [Interactive Flow](#interactive-flow) +- [Typical Workflow](#typical-workflow) +- [Configuration](#configuration) +- [Error Handling](#error-handling) +- [Use Cases](#use-cases) +- [Notes](#notes) + +## Syntax + +```bash +npx base44 site deploy [options] +``` + +## Options + +| Option | Description | +| ------------ | ------------------------- | +| `-y, --yes` | Skip confirmation prompt | + +Use `-y` flag for non-interactive/automated deployments: + +```bash +npx base44 site deploy -y +``` + +## Authentication + +**Required**: Yes. If not authenticated, you'll be prompted to login first. + +## Prerequisites + +- Must be run from a Base44 project directory +- Project must have `site.outputDirectory` configured in project config +- Site must be built before deploying (run your build command first) +- **SPA only**: Base44 hosting supports Single Page Applications with a single `index.html` entry point. All routes are served from `index.html` (client-side routing). + +## How It Works + +1. Reads project configuration +2. Validates that site configuration exists +3. Prompts for deployment confirmation showing the output directory +4. Creates an archive of site files from the output directory +5. Deploys to Base44 hosting +6. Returns the app URL + +## Interactive Flow + +```bash +$ npx base44 site deploy + +Deploy site from ./dist? (yes/no) yes + +Creating archive... +Uploading to Base44... +Deploying... + +✓ Deployment successful! + +Visit your site at: https://my-app.base44.app +``` + +## Typical Workflow + +```bash +# 1. Build your site using your framework's build command +npm run build + +# 2. Deploy to Base44 +npx base44 site deploy +``` + +## Configuration + +The `site.outputDirectory` in your project configuration should point to where your framework outputs built files: + +- Vite: typically `./dist` +- Next.js: typically `./.next` or `./out` +- Create React App: typically `./build` +- Custom: whatever your build tool outputs to + +## Error Handling + +If site configuration is missing: +```bash +$ npx base44 site deploy +Error: No site configuration found in project +``` + +If you cancel the deployment: +```bash +Deploy site from ./dist? (yes/no) no +Deployment cancelled +``` + +## Use Cases + +- Deploy your site after making changes +- Push new versions of your application +- Deploy after updating content or functionality +- Part of your CI/CD pipeline + +## Notes + +- Always build your site before deploying +- The command deploys whatever is in your output directory +- Make sure your build completed successfully before deploying +- Previous deployments are preserved (versioned) in Base44 +- Deployment is immediate and updates your live site diff --git a/evals/fixtures/anti-hallucination/skills/base44-sdk/SKILL.md b/evals/fixtures/anti-hallucination/skills/base44-sdk/SKILL.md new file mode 100644 index 0000000..01ddd7f --- /dev/null +++ b/evals/fixtures/anti-hallucination/skills/base44-sdk/SKILL.md @@ -0,0 +1,296 @@ +--- +name: base44-sdk +description: "The base44 SDK is the library to communicate with base44 services. In projects, you use it to communicate with remote resources (entities, backend functions, ai agents) and to write backend functions. This skill is the place for learning about available modules and types. When you plan or implement a feature, you must learn this skill" +--- + +# Base44 Coder + +Build apps on the Base44 platform using the Base44 JavaScript SDK. + +## ⚡ IMMEDIATE ACTION REQUIRED - Read This First + +This skill activates on ANY mention of "base44" or when a `base44/` folder exists. **DO NOT read documentation files or search the web before acting.** + +**Your first action MUST be:** +1. Check if `base44/config.jsonc` exists in the current directory +2. If **YES** (existing project scenario): + - This skill (base44-sdk) handles the request + - Implement features using Base44 SDK + - Do NOT use base44-cli unless user explicitly requests CLI commands +3. If **NO** (new project scenario): + - Transfer to base44-cli skill for project initialization + - This skill cannot help until project is initialized + +## When to Use This Skill vs base44-cli + +**Use base44-sdk when:** +- Building features in an **EXISTING** Base44 project +- `base44/config.jsonc` already exists in the project +- Base44 SDK imports are present (`@base44/sdk`) +- Writing JavaScript/TypeScript code using Base44 SDK modules +- Implementing functionality, components, or features +- User mentions: "implement", "build a feature", "add functionality", "write code for" +- User says "create a [type] app" **and** a Base44 project already exists + +**DO NOT USE base44-sdk for:** +- ❌ Initializing new Base44 projects (use `base44-cli` instead) +- ❌ Empty directories without Base44 configuration +- ❌ When user says "create a new Base44 project/app/site" and no project exists +- ❌ CLI commands like `npx base44 create`, `npx base44 deploy`, `npx base44 login` (use `base44-cli`) + +**Skill Dependencies:** +- `base44-sdk` assumes a Base44 project is **already initialized** +- `base44-cli` is a **prerequisite** for `base44-sdk` in new projects +- If user wants to "create an app" and no Base44 project exists, use `base44-cli` first + +**State Check Logic:** +Before selecting this skill, verify: +- IF (user mentions "create/build app" OR "make a project"): + - IF (directory is empty OR no `base44/config.jsonc` exists): + → Use **base44-cli** (project initialization needed) + - ELSE: + → Use **base44-sdk** (project exists, build features) + +## Quick Start + +```javascript +// In Base44-generated apps, base44 client is pre-configured and available + +// CRUD operations +const task = await base44.entities.Task.create({ title: "New task", status: "pending" }); +const tasks = await base44.entities.Task.list(); +await base44.entities.Task.update(task.id, { status: "done" }); + +// Get current user +const user = await base44.auth.me(); +``` + +```javascript +// External apps +import { createClient } from "@base44/sdk"; + +// IMPORTANT: Use 'appId' (NOT 'clientId' or 'id') +const base44 = createClient({ appId: "your-app-id" }); +await base44.auth.loginViaEmailPassword("user@example.com", "password"); +``` + +## ⚠️ CRITICAL: Do Not Hallucinate APIs + +**Before writing ANY Base44 code, verify method names against this table or [QUICK_REFERENCE.md](references/QUICK_REFERENCE.md).** + +Base44 SDK has unique method names. Do NOT assume patterns from Firebase, Supabase, or other SDKs. + +### Authentication - WRONG vs CORRECT + +| ❌ WRONG (hallucinated) | ✅ CORRECT | +|------------------------|-----------| +| `signInWithGoogle()` | `loginWithProvider('google')` | +| `signInWithProvider('google')` | `loginWithProvider('google')` | +| `auth.google()` | `loginWithProvider('google')` | +| `signInWithEmailAndPassword(email, pw)` | `loginViaEmailPassword(email, pw)` | +| `signIn(email, pw)` | `loginViaEmailPassword(email, pw)` | +| `createUser()` / `signUp()` | `register({email, password})` | +| `onAuthStateChanged()` | `me()` (no listener, call when needed) | +| `currentUser` | `await auth.me()` | + +### Functions - WRONG vs CORRECT + +| ❌ WRONG (hallucinated) | ✅ CORRECT | +|------------------------|-----------| +| `functions.call('name', data)` | `functions.invoke('name', data)` | +| `functions.run('name', data)` | `functions.invoke('name', data)` | +| `callFunction('name', data)` | `functions.invoke('name', data)` | +| `httpsCallable('name')(data)` | `functions.invoke('name', data)` | + +### Integrations - WRONG vs CORRECT + +| ❌ WRONG (hallucinated) | ✅ CORRECT | +|------------------------|-----------| +| `ai.generate(prompt)` | `integrations.Core.InvokeLLM({prompt})` | +| `openai.chat(prompt)` | `integrations.Core.InvokeLLM({prompt})` | +| `llm(prompt)` | `integrations.Core.InvokeLLM({prompt})` | +| `sendEmail(to, subject, body)` | `integrations.Core.SendEmail({to, subject, body})` | +| `email.send()` | `integrations.Core.SendEmail({to, subject, body})` | +| `uploadFile(file)` | `integrations.Core.UploadFile({file})` | +| `storage.upload(file)` | `integrations.Core.UploadFile({file})` | + +### Entities - WRONG vs CORRECT + +| ❌ WRONG (hallucinated) | ✅ CORRECT | +|------------------------|-----------| +| `entities.Task.find({...})` | `entities.Task.filter({...})` | +| `entities.Task.findOne(id)` | `entities.Task.get(id)` | +| `entities.Task.insert(data)` | `entities.Task.create(data)` | +| `entities.Task.remove(id)` | `entities.Task.delete(id)` | +| `entities.Task.onChange(cb)` | `entities.Task.subscribe(cb)` | + +## SDK Modules + +| Module | Purpose | Reference | +|--------|---------|-----------| +| `entities` | CRUD operations on data models | [entities.md](references/entities.md) | +| `auth` | Login, register, user management | [auth.md](references/auth.md) | +| `agents` | AI conversations and messages | [base44-agents.md](references/base44-agents.md) | +| `functions` | Backend function invocation | [functions.md](references/functions.md) | +| `integrations` | AI, email, file uploads, custom APIs | [integrations.md](references/integrations.md) | +| `connectors` | OAuth tokens (service role only) | [connectors.md](references/connectors.md) | +| `analytics` | Track custom events and user activity | [analytics.md](references/analytics.md) | +| `appLogs` | Log user activity in app | [app-logs.md](references/app-logs.md) | +| `users` | Invite users to the app | [users.md](references/users.md) | + +For client setup and authentication modes, see [client.md](references/client.md). + +**TypeScript Support:** Each reference file includes a "Type Definitions" section with TypeScript interfaces and types for the module's methods, parameters, and return values. + +## Installation + +Install the Base44 SDK: + +```bash +npm install @base44/sdk +``` + +**Important:** Never assume or hardcode the `@base44/sdk` package version. Always install without a version specifier to get the latest version. + +## Creating a Client (External Apps) + +When creating a client in external apps, **ALWAYS use `appId` as the parameter name**: + +```javascript +import { createClient } from "@base44/sdk"; + +// ✅ CORRECT +const base44 = createClient({ appId: "your-app-id" }); + +// ❌ WRONG - Do NOT use these: +// const base44 = createClient({ clientId: "your-app-id" }); // WRONG +// const base44 = createClient({ id: "your-app-id" }); // WRONG +``` + +**Required parameter:** `appId` (string) - Your Base44 application ID + +**Optional parameters:** +- `token` (string) - Pre-authenticated user token +- `options` (object) - Configuration options + - `options.onError` (function) - Global error handler + +**Example with error handler:** +```javascript +const base44 = createClient({ + appId: "your-app-id", + options: { + onError: (error) => { + console.error("Base44 error:", error); + } + } +}); +``` + +## Module Selection + +**Working with app data?** +- Create/read/update/delete records → `entities` +- Import data from file → `entities.importEntities()` +- Realtime updates → `entities.EntityName.subscribe()` + +**User management?** +- Login/register/logout → `auth` +- Get current user → `auth.me()` +- Update user profile → `auth.updateMe()` +- Invite users → `users.inviteUser()` + +**AI features?** +- Chat with AI agents → `agents` (requires logged-in user) +- Create new conversation → `agents.createConversation()` +- Manage conversations → `agents.getConversations()` +- Generate text/JSON with AI → `integrations.Core.InvokeLLM()` +- Generate images → `integrations.Core.GenerateImage()` + +**Custom backend logic?** +- Run server-side code → `functions.invoke()` +- Need admin access → `base44.asServiceRole.functions.invoke()` + +**External services?** +- Send emails → `integrations.Core.SendEmail()` +- Upload files → `integrations.Core.UploadFile()` +- Custom APIs → `integrations.custom.call()` +- OAuth tokens (Google, Slack) → `connectors` (backend only) + +**Tracking and analytics?** +- Track custom events → `analytics.track()` +- Log page views/activity → `appLogs.logUserInApp()` + +## Common Patterns + +### Filter and Sort Data + +```javascript +const pendingTasks = await base44.entities.Task.filter( + { status: "pending", assignedTo: userId }, // query + "-created_date", // sort (descending) + 10, // limit + 0 // skip +); +``` + +### Protected Routes (check auth) + +```javascript +const user = await base44.auth.me(); +if (!user) { + // Navigate to your custom login page + navigate('/login', { state: { returnTo: window.location.pathname } }); + return; +} +``` + +### Backend Function Call + +```javascript +// Frontend +const result = await base44.functions.invoke("processOrder", { + orderId: "123", + action: "ship" +}); + +// Backend function (Deno) +import { createClientFromRequest } from "npm:@base44/sdk"; + +Deno.serve(async (req) => { + const base44 = createClientFromRequest(req); + const { orderId, action } = await req.json(); + // Process with service role for admin access + const order = await base44.asServiceRole.entities.Orders.get(orderId); + return Response.json({ success: true }); +}); +``` + +### Service Role Access + +Use `asServiceRole` in backend functions for admin-level operations: + +```javascript +// User mode - respects permissions +const myTasks = await base44.entities.Task.list(); + +// Service role - full access (backend only) +const allTasks = await base44.asServiceRole.entities.Task.list(); +const token = await base44.asServiceRole.connectors.getAccessToken("slack"); +``` + +## Frontend vs Backend + +| Capability | Frontend | Backend | +|------------|----------|---------| +| `entities` (user's data) | Yes | Yes | +| `auth` | Yes | Yes | +| `agents` | Yes | Yes | +| `functions.invoke()` | Yes | Yes | +| `integrations` | Yes | Yes | +| `analytics` | Yes | Yes | +| `appLogs` | Yes | Yes | +| `users` | Yes | Yes | +| `asServiceRole.*` | No | Yes | +| `connectors` | No | Yes | + +Backend functions use `Deno.serve()` and `createClientFromRequest(req)` to get a properly authenticated client. diff --git a/evals/fixtures/anti-hallucination/skills/base44-sdk/references/QUICK_REFERENCE.md b/evals/fixtures/anti-hallucination/skills/base44-sdk/references/QUICK_REFERENCE.md new file mode 100644 index 0000000..d357384 --- /dev/null +++ b/evals/fixtures/anti-hallucination/skills/base44-sdk/references/QUICK_REFERENCE.md @@ -0,0 +1,155 @@ +# Base44 SDK Quick Reference + +Compact method signatures for all SDK modules. **Verify against this before writing code.** + +--- + +## Auth (`base44.auth.*`) + +``` +loginViaEmailPassword(email, password, turnstileToken?) → Promise<{access_token, user}> +loginWithProvider('google' | 'microsoft' | 'facebook', fromUrl?) → void +me() → Promise +updateMe(data) → Promise +isAuthenticated() → Promise +logout(redirectUrl?) → void +redirectToLogin(nextUrl) → void # ⚠️ Avoid - prefer custom login UI +register({email, password, turnstile_token?, referral_code?}) → Promise +verifyOtp({email, otpCode}) → Promise +resendOtp(email) → Promise +inviteUser(userEmail, role) → Promise +resetPasswordRequest(email) → Promise +resetPassword({resetToken, newPassword}) → Promise +changePassword({userId, currentPassword, newPassword}) → Promise +setToken(token, saveToStorage?) → void +``` + +--- + +## Entities (`base44.entities.EntityName.*`) + +``` +create(data) → Promise +bulkCreate(dataArray) → Promise +list(sort?, limit?, skip?, fields?) → Promise +filter(query, sort?, limit?, skip?, fields?) → Promise +get(id) → Promise +update(id, data) → Promise +delete(id) → Promise +deleteMany(query) → Promise +importEntities(file) → Promise // frontend only +subscribe(callback) → () => void // returns unsubscribe fn +``` + +**Sort:** Use `-fieldName` for descending (e.g., `-created_date`) + +--- + +## Functions (`base44.functions.*`) + +``` +invoke(functionName, data) → Promise +``` + +**Backend:** Use `base44.asServiceRole.functions.invoke()` for admin access. + +--- + +## Integrations (`base44.integrations.Core.*`) + +``` +InvokeLLM({prompt, add_context_from_internet?, response_json_schema?, file_urls?}) → Promise +GenerateImage({prompt}) → Promise<{url}> +SendEmail({to, subject, body, from_name?}) → Promise +UploadFile({file}) → Promise<{file_url}> +UploadPrivateFile({file}) → Promise<{file_uri}> +CreateFileSignedUrl({file_uri, expires_in?}) → Promise<{signed_url}> +ExtractDataFromUploadedFile({file_url, json_schema}) → Promise +``` + +### Custom Integrations (`base44.integrations.custom.*`) + +``` +call(slug, operationId, {payload?, pathParams?, queryParams?, headers?}?) → Promise<{success, status_code, data}> +``` + +**operationId format:** `"method:/path"` (e.g., `"get:/contacts"`, `"post:/users/{id}"`) + +--- + +## Analytics (`base44.analytics.*`) + +``` +track({eventName, properties?}) → void +``` + +--- + +## App Logs (`base44.appLogs.*`) + +``` +logUserInApp(pageName) → Promise +``` + +--- + +## Users (`base44.users.*`) + +``` +inviteUser(userEmail, role) → Promise // role: 'user' | 'admin' +``` + +--- + +## Connectors (`base44.asServiceRole.connectors.*`) + +**Backend only, service role required.** + +``` +getAccessToken(integrationType) → Promise +``` + +**Types:** `'googlecalendar'`, `'googledrive'`, `'slack'`, `'notion'`, `'salesforce'`, `'hubspot'`, `'linkedin'`, `'tiktok'`, `'github'` + +--- + +## Service Role Access + +**Backend functions only.** Prefix any module with `asServiceRole` for admin access: + +```javascript +base44.asServiceRole.entities.Task.list() +base44.asServiceRole.functions.invoke('name', data) +base44.asServiceRole.connectors.getAccessToken('slack') +``` + +--- + +## Backend Function Template + +```javascript +import { createClientFromRequest } from "@base44/sdk"; + +Deno.serve(async (req) => { + const base44 = createClientFromRequest(req); + const data = await req.json(); + + // User context + const user = await base44.auth.me(); + + // Service role for admin operations + const allRecords = await base44.asServiceRole.entities.Task.list(); + + return Response.json({ success: true }); +}); +``` + +--- + +## Client Initialization (External Apps) + +```javascript +import { createClient } from "@base44/sdk"; + +const base44 = createClient({ appId: "your-app-id" }); // MUST use 'appId' +``` diff --git a/evals/fixtures/anti-hallucination/skills/base44-sdk/references/analytics.md b/evals/fixtures/anti-hallucination/skills/base44-sdk/references/analytics.md new file mode 100644 index 0000000..d1b2c69 --- /dev/null +++ b/evals/fixtures/anti-hallucination/skills/base44-sdk/references/analytics.md @@ -0,0 +1,113 @@ +# Analytics Module + +Track custom events and user activity via `base44.analytics`. + +## Contents +- [Methods](#methods) +- [Examples](#examples) +- [Automatic Tracking](#automatic-tracking) +- [Best Practices](#best-practices) + +## Methods + +| Method | Signature | Description | +|--------|-----------|-------------| +| `track(params)` | `void` | Track a custom event | + +## Examples + +### Track Custom Event + +```javascript +// Track a simple event +base44.analytics.track({ + eventName: "button_clicked" +}); + +// Track event with properties +base44.analytics.track({ + eventName: "purchase_completed", + properties: { + product_id: "prod-123", + amount: 99.99, + currency: "USD" + } +}); +``` + +### Track User Actions + +```javascript +// Track page view +base44.analytics.track({ + eventName: "page_view", + properties: { + page: "/dashboard", + referrer: document.referrer + } +}); + +// Track feature usage +base44.analytics.track({ + eventName: "feature_used", + properties: { + feature: "export_data", + format: "csv" + } +}); +``` + +## Automatic Tracking + +The analytics module automatically tracks: +- **Initialization events**: When the app loads +- **Heartbeat events**: Periodic activity signals +- **Session duration**: Time spent in the app + +These internal events help measure user engagement without manual instrumentation. + +## Best Practices + +1. **Use descriptive event names**: `order_completed` instead of `click` +2. **Include relevant properties**: Add context that helps analyze the event +3. **Be consistent**: Use the same event names and property keys across your app +4. **Don't track sensitive data**: Avoid PII in event properties + +```javascript +// Good: Descriptive with relevant properties +base44.analytics.track({ + eventName: "subscription_started", + properties: { + plan: "pro", + billing_cycle: "annual" + } +}); + +// Avoid: Vague event name, no context +base44.analytics.track({ + eventName: "click" +}); +``` + +## Type Definitions + +```typescript +/** Properties that can be attached to a tracked event. */ +type TrackEventProperties = { + [key: string]: string | number | boolean | null | undefined; +}; + +/** Parameters for the track() method. */ +interface TrackEventParams { + /** The name of the event to track. */ + eventName: string; + /** Optional properties to attach to the event. */ + properties?: TrackEventProperties; +} + +/** The analytics module interface. */ +interface AnalyticsModule { + /** Track a custom event with optional properties. */ + track(params: TrackEventParams): void; +} +``` diff --git a/evals/fixtures/anti-hallucination/skills/base44-sdk/references/app-logs.md b/evals/fixtures/anti-hallucination/skills/base44-sdk/references/app-logs.md new file mode 100644 index 0000000..0439805 --- /dev/null +++ b/evals/fixtures/anti-hallucination/skills/base44-sdk/references/app-logs.md @@ -0,0 +1,78 @@ +# App Logs Module + +Log user activity in your app via `base44.appLogs`. + +## Contents +- [Methods](#methods) +- [Examples](#examples) +- [Use Cases](#use-cases) + +## Methods + +| Method | Signature | Description | +|--------|-----------|-------------| +| `logUserInApp(pageName)` | `Promise` | Log user activity on a page | + +## Examples + +### Log User Activity + +```javascript +// Log when user visits a page +await base44.appLogs.logUserInApp("dashboard"); + +// Log specific page visits +await base44.appLogs.logUserInApp("settings"); +await base44.appLogs.logUserInApp("profile"); + +// Log feature usage +await base44.appLogs.logUserInApp("export-button-click"); +``` + +The page name doesn't have to be an actual page - it can be any string you want to track. + +## Use Cases + +### Track Page Views in React + +```javascript +// Log page views on route change +useEffect(() => { + base44.appLogs.logUserInApp(window.location.pathname); +}, [location.pathname]); +``` + +### Track Feature Usage + +```javascript +// Log when user uses specific features +function handleExport() { + base44.appLogs.logUserInApp("export-data"); + // ... export logic +} + +function handleSettingsChange() { + base44.appLogs.logUserInApp("settings-updated"); + // ... save settings +} +``` + +## Notes + +- Logs appear in the Analytics page of your app dashboard +- App logs track page-level and feature-level activity +- Use `analytics.track()` for custom events with properties, `appLogs.logUserInApp()` for simple page/feature tracking + +## Type Definitions + +```typescript +/** App Logs module for tracking and analyzing app usage. */ +interface AppLogsModule { + /** + * Log user activity in the app. + * @param pageName - Name of the page or section being visited. + * @returns Promise that resolves when the log is recorded. + */ + logUserInApp(pageName: string): Promise; +} +``` diff --git a/evals/fixtures/anti-hallucination/skills/base44-sdk/references/auth.md b/evals/fixtures/anti-hallucination/skills/base44-sdk/references/auth.md new file mode 100644 index 0000000..5195b3a --- /dev/null +++ b/evals/fixtures/anti-hallucination/skills/base44-sdk/references/auth.md @@ -0,0 +1,761 @@ +# Auth Module + +User authentication, registration, and session management via `base44.auth`. + +## Contents +- [TypeScript Types](#typescript-types) +- [Methods](#methods) +- [Examples](#examples) +- [Error Handling](#error-handling) +- [Auth Providers](#auth-providers) +- [Environment Availability](#environment-availability) +- [App Visibility](#app-visibility) +- [Limitations](#limitations) + +--- + +## TypeScript Types + +### User Interface +```typescript +interface User { + id: string; + created_date: string; + updated_date: string; + email: string; + full_name: string | null; + disabled: boolean | null; + is_verified: boolean; + app_id: string; + is_service: boolean; + role: string; + [key: string]: any; // Custom schema fields +} +``` + +### LoginResponse Interface +```typescript +interface LoginResponse { + access_token: string; // JWT token + user: User; // Complete user object +} +``` + +### Parameter Interfaces + +#### RegisterParams +```typescript +interface RegisterParams { + email: string; // Required + password: string; // Required + turnstile_token?: string | null; // Optional: Cloudflare Turnstile for bot protection + referral_code?: string | null; // Optional: Referral code +} +``` + +#### VerifyOtpParams +```typescript +interface VerifyOtpParams { + email: string; // User's email + otpCode: string; // OTP code from email +} +``` + +#### ResetPasswordParams +```typescript +interface ResetPasswordParams { + resetToken: string; // Token from password reset email + newPassword: string; // New password to set +} +``` + +#### ChangePasswordParams +```typescript +interface ChangePasswordParams { + userId: string; // User ID + currentPassword: string; // Current password for verification + newPassword: string; // New password to set +} +``` + +### Provider Type +```typescript +type Provider = 'google' | 'microsoft' | 'facebook'; +``` + +--- + +## Methods + +### Module Interface +```typescript +interface AuthModule { + // User Info + me(): Promise; + updateMe(data: Partial>): Promise; + isAuthenticated(): Promise; + + // Login/Logout + loginViaEmailPassword(email: string, password: string, turnstileToken?: string): Promise; + loginWithProvider(provider: Provider, fromUrl?: string): void; + logout(redirectUrl?: string): void; + redirectToLogin(nextUrl: string): void; + + // Token Management + setToken(token: string, saveToStorage?: boolean): void; + + // Registration + register(params: RegisterParams): Promise; + verifyOtp(params: VerifyOtpParams): Promise; + resendOtp(email: string): Promise; + + // User Management + inviteUser(userEmail: string, role: string): Promise; + + // Password Management + resetPasswordRequest(email: string): Promise; + resetPassword(params: ResetPasswordParams): Promise; + changePassword(params: ChangePasswordParams): Promise; +} +``` + +### Method Reference Table + +| Method | Parameters | Return Type | Description | +|--------|-----------|-------------|-------------| +| `register()` | `params: RegisterParams` | `Promise` | Create new user account | +| `loginViaEmailPassword()` | `email: string, password: string, turnstileToken?: string` | `Promise` | Authenticate with email/password | +| `loginWithProvider()` | `provider: Provider, fromUrl?: string` | `void` | Initiate OAuth login flow | +| `me()` | None | `Promise` | Get current authenticated user | +| `updateMe()` | `data: Partial` | `Promise` | Update current user's profile | +| `logout()` | `redirectUrl?: string` | `void` | Clear session, optionally redirect | +| `redirectToLogin()` | `nextUrl: string` | `void` | ⚠️ **Avoid** - Prefer custom login UI with `loginViaEmailPassword()` or `loginWithProvider()` | +| `isAuthenticated()` | None | `Promise` | Check if user is logged in | +| `setToken()` | `token: string, saveToStorage?: boolean` | `void` | Manually set auth token | +| `inviteUser()` | `userEmail: string, role: string` | `Promise` | Send invitation email | +| `verifyOtp()` | `params: VerifyOtpParams` | `Promise` | Verify OTP code | +| `resendOtp()` | `email: string` | `Promise` | Resend OTP code | +| `resetPasswordRequest()` | `email: string` | `Promise` | Request password reset | +| `resetPassword()` | `params: ResetPasswordParams` | `Promise` | Reset password with token | +| `changePassword()` | `params: ChangePasswordParams` | `Promise` | Change user password | + +--- + +## Examples + +### Register New User (Complete Flow) + +Registration requires email verification before login. Complete flow: + +1. **Register** - Create the user account +2. **Verification email sent** - User receives an OTP code +3. **Verify OTP** - User enters code to verify email +4. **Login** - User can now log in + +```javascript +try { + // Step 1: Register the user + await base44.auth.register({ + email: "user@example.com", + password: "securePassword123", + referral_code: "OPTIONAL_CODE", // optional + turnstile_token: "CAPTCHA_TOKEN" // optional, for bot protection + }); + console.log('Registration successful. Check email for OTP code.'); + + // Step 2: User receives email with OTP code (e.g., "123456") + + // Step 3: Verify the OTP code + await base44.auth.verifyOtp({ + email: "user@example.com", + otpCode: "123456" // code from verification email + }); + console.log('Email verified successfully.'); + + // Step 4: Now the user can log in + const loginResponse = await base44.auth.loginViaEmailPassword( + "user@example.com", + "securePassword123" + ); + console.log('Login successful:', loginResponse.user); + +} catch (error) { + console.error('Registration flow failed:', error.message); + // Handle specific errors (see Error Handling section) +} +``` + +> **Important**: Users cannot log in until they complete OTP verification. Attempting to call `loginViaEmailPassword` before verification will fail. + +### Login with Email/Password + +```javascript +try { + const response = await base44.auth.loginViaEmailPassword( + "user@example.com", + "password123", + turnstileToken // optional: for bot protection + ); + + console.log('Login successful'); + console.log('User:', response.user); + console.log('Token:', response.access_token); + + // JWT is automatically stored for subsequent requests + +} catch (error) { + console.error('Login failed:', error.message); + if (error.status === 401) { + console.error('Invalid credentials'); + } else if (error.status === 403) { + console.error('Email not verified. Please check your email for OTP.'); + } +} +``` + +### Login with OAuth Provider + +```javascript +// Redirect to Google OAuth +base44.auth.loginWithProvider('google'); + +// Redirect to Google OAuth and return to current page after +base44.auth.loginWithProvider('google', window.location.href); + +// Supported providers: 'google', 'microsoft', 'facebook' +base44.auth.loginWithProvider('microsoft'); +``` + +### Get Current User + +```javascript +try { + const user = await base44.auth.me(); + + if (user) { + console.log('User ID:', user.id); + console.log('Email:', user.email); + console.log('Name:', user.full_name); + console.log('Role:', user.role); + console.log('Verified:', user.is_verified); + } else { + console.log('User not authenticated'); + } + +} catch (error) { + console.error('Failed to fetch user:', error.message); + if (error.status === 401) { + // Token expired or invalid - navigate to your custom login page + navigate('/login'); + } +} +``` + +### Update User Profile + +```javascript +try { + const updatedUser = await base44.auth.updateMe({ + full_name: "John Doe", + // Custom schema fields can be updated here + phone: "+1234567890", + preferences: { theme: "dark" } + }); + + console.log('Profile updated:', updatedUser); + +} catch (error) { + console.error('Profile update failed:', error.message); + if (error.status === 400) { + console.error('Invalid data provided'); + } else if (error.status === 401) { + console.error('Authentication required'); + navigate('/login'); + } +} +``` + +### Check Authentication Status + +```javascript +try { + const isLoggedIn = await base44.auth.isAuthenticated(); + + if (isLoggedIn) { + // Show authenticated UI + console.log('User is authenticated'); + } else { + // Show login button + console.log('User is not authenticated'); + } + +} catch (error) { + console.error('Failed to check authentication:', error.message); + // On error, treat as not authenticated +} +``` + +### Logout + +```javascript +// Simple logout +base44.auth.logout(); + +// Logout and redirect to goodbye page +base44.auth.logout("/goodbye"); + +// Logout and redirect to homepage +base44.auth.logout("/"); +``` + +### Protected Route Pattern + +```javascript +// Using a navigation function (e.g., React Router's useNavigate, Next.js router) +async function requireAuth(navigate) { + try { + const user = await base44.auth.me(); + + if (!user) { + // Navigate to your custom login page + navigate('/login', { state: { returnTo: window.location.pathname } }); + return null; + } + + return user; + + } catch (error) { + console.error('Authentication check failed:', error.message); + navigate('/login', { state: { returnTo: window.location.pathname } }); + return null; + } +} + +// Usage in your app +async function loadProtectedPage(navigate) { + const user = await requireAuth(navigate); + if (!user) { + // Will navigate to login + return; + } + + // Continue with authenticated logic + console.log('Welcome,', user.full_name); +} +``` + +### Set Authentication Token + +```javascript +// SECURITY WARNING: Never hardcode tokens or expose them in client code +// Tokens should only be received from secure authentication flows + +// Set token and save to localStorage (default) +base44.auth.setToken(receivedToken); + +// Set token without saving to localStorage (temporary session) +base44.auth.setToken(receivedToken, false); + +// Verify token was set +try { + const isAuthenticated = await base44.auth.isAuthenticated(); + if (!isAuthenticated) { + console.error('Token validation failed'); + } +} catch (error) { + console.error('Failed to set token:', error.message); +} +``` + +### Invite User to Application + +```javascript +try { + // Note: Typically requires admin privileges + const response = await base44.auth.inviteUser( + "newuser@example.com", + "user" // or "admin" + ); + + console.log('Invitation sent successfully'); + +} catch (error) { + console.error('Failed to invite user:', error.message); + if (error.status === 403) { + console.error('Insufficient permissions to invite users'); + } else if (error.status === 400) { + console.error('Invalid email or role'); + } +} +``` + +### OTP Verification + +```javascript +try { + // Verify OTP code sent to user's email + await base44.auth.verifyOtp({ + email: "user@example.com", + otpCode: "123456" + }); + + console.log('OTP verified successfully'); + +} catch (error) { + console.error('OTP verification failed:', error.message); + if (error.status === 400) { + console.error('Invalid or expired OTP code'); + } else if (error.status === 429) { + console.error('Too many attempts. Please try again later.'); + } +} + +// Resend OTP if needed +try { + await base44.auth.resendOtp("user@example.com"); + console.log('OTP resent successfully'); + +} catch (error) { + console.error('Failed to resend OTP:', error.message); + if (error.status === 429) { + console.error('Too many requests. Please wait before trying again.'); + } +} +``` + +### Password Reset Flow + +```javascript +// Step 1: Request password reset +try { + await base44.auth.resetPasswordRequest("user@example.com"); + console.log('Password reset email sent. Check your inbox.'); + +} catch (error) { + console.error('Password reset request failed:', error.message); + if (error.status === 429) { + console.error('Too many requests. Please try again later.'); + } + // Note: For security, don't reveal if email exists +} + +// Step 2: Reset password with token from email +try { + await base44.auth.resetPassword({ + resetToken: "token-from-email", + newPassword: "newSecurePassword123" + }); + + console.log('Password reset successfully. You can now log in.'); + +} catch (error) { + console.error('Password reset failed:', error.message); + if (error.status === 400) { + console.error('Invalid or expired reset token'); + } else if (error.status === 422) { + console.error('Password does not meet requirements'); + } +} +``` + +### Change Password + +```javascript +try { + const currentUser = await base44.auth.me(); + + if (!currentUser) { + throw new Error('User must be authenticated to change password'); + } + + await base44.auth.changePassword({ + userId: currentUser.id, + currentPassword: "oldPassword123", + newPassword: "newSecurePassword456" + }); + + console.log('Password changed successfully'); + +} catch (error) { + console.error('Password change failed:', error.message); + if (error.status === 401) { + console.error('Current password is incorrect'); + } else if (error.status === 422) { + console.error('New password does not meet requirements'); + } else if (error.status === 403) { + console.error('Not authorized to change this password'); + } +} +``` + +--- + +## Error Handling + +### Common Error Scenarios + +The auth module can throw various errors. Here are common scenarios and how to handle them: + +#### Authentication Errors (401/403) +```javascript +try { + const user = await base44.auth.me(); +} catch (error) { + if (error.status === 401) { + // Token expired or invalid - navigate to your custom login page + navigate('/login'); + } else if (error.status === 403) { + // Email not verified or insufficient permissions + console.error('Access denied:', error.message); + } +} +``` + +#### Validation Errors (400/422) +```javascript +try { + await base44.auth.register({ + email: "invalid-email", + password: "weak" + }); +} catch (error) { + if (error.status === 400) { + console.error('Invalid input:', error.message); + // Handle validation errors + } else if (error.status === 422) { + console.error('Data validation failed:', error.details); + } +} +``` + +#### Rate Limiting (429) +```javascript +try { + await base44.auth.resendOtp("user@example.com"); +} catch (error) { + if (error.status === 429) { + console.error('Too many requests. Please wait before trying again.'); + // Show countdown or disable button temporarily + } +} +``` + +#### Generic Error Handler +```javascript +function handleAuthError(error) { + switch (error.status) { + case 400: + return 'Invalid input. Please check your data.'; + case 401: + return 'Authentication required. Redirecting to login...'; + case 403: + return 'Access denied. You may need to verify your email.'; + case 404: + return 'Resource not found.'; + case 422: + return 'Validation failed. Please check your input.'; + case 429: + return 'Too many requests. Please try again later.'; + case 500: + return 'Server error. Please try again.'; + default: + return 'An unexpected error occurred.'; + } +} + +// Usage +try { + await base44.auth.loginViaEmailPassword(email, password); +} catch (error) { + console.error(handleAuthError(error)); +} +``` + +--- + +## Auth Providers + +Configure authentication providers in your app dashboard: + +### Available Providers + +**Built-in (All Plans):** +- **Email/Password** - Default, always enabled +- **Google** - OAuth authentication +- **Microsoft** - OAuth authentication +- **Facebook** - OAuth authentication + +**SSO Providers (Elite Plan):** +- **Okta** +- **Azure AD** +- **GitHub** + +### Using OAuth Providers + +```javascript +// Initiate OAuth login flow +base44.auth.loginWithProvider('google'); + +// Return to specific page after authentication +base44.auth.loginWithProvider('microsoft', '/dashboard'); + +// Supported values: 'google', 'microsoft', 'facebook' +``` + +--- + +## Environment Availability + +| Environment | Availability | Notes | +|------------|--------------|-------| +| **Frontend** | ✅ Yes | All methods available | +| **Backend Functions** | ✅ Yes | Use `createClientFromRequest(req)` for authenticated client | +| **Service Role** | ❌ No | Auth methods not available in service role context | + +### Frontend Usage +```javascript +// Standard browser usage +const user = await base44.auth.me(); +``` + +### Backend Functions Usage +```javascript +import { createClientFromRequest } from "@base44/sdk"; + +Deno.serve(async (req) => { + const base44 = createClientFromRequest(req); + + // Get user from request context + const user = await base44.auth.me(); + + // User operations here... + return Response.json({ user }); +}); +``` + +--- + +## App Visibility + +Control who can access your app in the app settings: + +### Public Apps +- No login required for basic access +- Users can view public content without authentication +- Authenticated users get additional features/data + +### Private Apps +- Login required to access any content +- Unauthenticated users are automatically redirected to login +- All content is protected by default + +--- + +## Limitations + +### Authentication UI Options +- **Recommended:** Build custom login/signup UI using `loginViaEmailPassword()` and `loginWithProvider()` for full control over user experience and branding +- **Alternative:** `redirectToLogin()` uses Base44's hosted authentication pages with limited customization + +### Hosted Login (via redirectToLogin) +- `redirectToLogin()` shows both login and signup options on the same page +- No separate `redirectToSignup()` method +- Users can switch between login/signup on the hosted page +- ⚠️ **Note:** Prefer building custom login UI for better user experience + +### Password Requirements +- Minimum length and complexity requirements enforced +- Requirements not exposed via API +- Validation errors returned when requirements not met + +### Rate Limiting +- OTP requests are rate-limited to prevent abuse +- Password reset requests are rate-limited +- Login attempts may be rate-limited with Turnstile protection + +### Token Management +- JWTs are automatically stored in localStorage by default +- Token expiration and refresh not exposed in API +- Call `me()` or `isAuthenticated()` to verify token validity + +--- + +## Best Practices + +### 1. Always Handle Errors +```javascript +try { + await base44.auth.loginViaEmailPassword(email, password); +} catch (error) { + // Always handle authentication errors + console.error('Login failed:', error.message); +} +``` + +### 2. Verify Authentication Before Protected Actions +```javascript +const user = await requireAuth(); +if (!user) return; // Will redirect to login +// Proceed with authenticated action +``` + +### 3. Use Type Safety with TypeScript +```typescript +import type { User, LoginResponse } from '@base44/sdk'; + +const user: User = await base44.auth.me(); +const response: LoginResponse = await base44.auth.loginViaEmailPassword(email, password); +``` + +### 4. Don't Hardcode Credentials +```javascript +// ❌ BAD +base44.auth.setToken("hardcoded-token-here"); + +// ✅ GOOD +const token = await secureAuthFlow(); +base44.auth.setToken(token); +``` + +### 5. Provide User Feedback +```javascript +try { + await base44.auth.register(params); + showSuccessMessage('Registration successful! Check your email for verification code.'); +} catch (error) { + showErrorMessage('Registration failed: ' + error.message); +} +``` + +### 6. Handle Token Expiration Gracefully +```javascript +try { + const user = await base44.auth.me(); +} catch (error) { + if (error.status === 401) { + // Clear local state and navigate to login + localStorage.clear(); + navigate('/login'); + } +} +``` + +### 7. Build Custom Login UI (Recommended) +```javascript +// ✅ RECOMMENDED - Custom login form with direct methods +const handleLogin = async (email, password) => { + try { + const { user } = await base44.auth.loginViaEmailPassword(email, password); + navigate('/dashboard'); + } catch (error) { + setError(error.message); + } +}; + +const handleGoogleLogin = () => { + base44.auth.loginWithProvider('google', '/dashboard'); +}; + +// ❌ AVOID - Redirecting to hosted login page +// base44.auth.redirectToLogin(window.location.href); +``` diff --git a/evals/fixtures/anti-hallucination/skills/base44-sdk/references/base44-agents.md b/evals/fixtures/anti-hallucination/skills/base44-sdk/references/base44-agents.md new file mode 100644 index 0000000..ee55dee --- /dev/null +++ b/evals/fixtures/anti-hallucination/skills/base44-sdk/references/base44-agents.md @@ -0,0 +1,364 @@ +# Agents Module + +AI agent conversations and messages via `base44.agents`. + +> **Note:** This module requires a logged-in user. All agent methods work in the context of the authenticated user. + +## Contents +- [Concepts](#concepts) +- [Methods](#methods) +- [Examples](#examples) (Create, Get Conversations, List, Subscribe, Send Message, WhatsApp) +- [Message Structure](#message-structure) +- [Conversation Structure](#conversation-structure) +- [Common Patterns](#common-patterns) + +## Concepts + +- **Conversation**: A dialogue between user and an AI agent. Has unique ID, agent name, user reference, and metadata. +- **Message**: Single message in a conversation. Has role (`user`, `assistant`, `system`), content, timestamps, and optional metadata. + +## Methods + +| Method | Signature | Description | +|--------|-----------|-------------| +| `createConversation(params)` | `Promise` | Create a new conversation with an agent | +| `getConversations()` | `Promise` | Get all user's conversations | +| `getConversation(id)` | `Promise` | Get conversation with messages | +| `listConversations(filterParams)` | `Promise` | Filter/sort/paginate conversations | +| `subscribeToConversation(id, onUpdate?)` | `() => void` | Real-time updates via WebSocket (returns unsubscribe function) | +| `addMessage(conversation, message)` | `Promise` | Send a message | +| `getWhatsAppConnectURL(agentName)` | `string` | Get WhatsApp connection URL for agent | + +## Examples + +### Create Conversation + +```javascript +const conversation = await base44.agents.createConversation({ + agent_name: "support-agent", + metadata: { + order_id: "ORD-123", + category: "billing" + } +}); + +console.log(conversation.id); +``` + +### Get All Conversations + +```javascript +const conversations = await base44.agents.getConversations(); + +conversations.forEach(conv => { + console.log(conv.id, conv.agent_name, conv.created_date); +}); +``` + +### Get Single Conversation (with messages) + +```javascript +const conversation = await base44.agents.getConversation("conv-id-123"); + +console.log(conversation.messages); +``` + +### List with Filters + +```javascript +// Using filterParams object with q, sort, limit, skip, fields +const recent = await base44.agents.listConversations({ + q: { agent_name: "support-agent" }, + sort: "-created_date", + limit: 10, + skip: 0 +}); + +// Filter by metadata +const highPriority = await base44.agents.listConversations({ + q: { + agent_name: "support-agent", + "metadata.priority": "high" + }, + sort: "-updated_date", + limit: 20 +}); +``` + +### Subscribe to Updates (Real-time) + +```javascript +const unsubscribe = base44.agents.subscribeToConversation( + "conv-id-123", + (updatedConversation) => { + // Called when new messages arrive + console.log("New messages:", updatedConversation.messages); + } +); + +// Later: unsubscribe +unsubscribe(); +``` + +### Send a Message + +```javascript +const conversation = await base44.agents.getConversation("conv-id-123"); + +await base44.agents.addMessage(conversation, { + role: "user", + content: "What's the weather like today?" +}); +``` + +### Get WhatsApp Connection URL + +```javascript +const whatsappUrl = base44.agents.getWhatsAppConnectURL("support-agent"); +// Returns URL for users to connect with agent via WhatsApp +console.log(whatsappUrl); +``` + +## Message Structure + +```javascript +{ + role: "user" | "assistant" | "system", + content: "Message text or structured object", + created_date: "2024-01-15T10:30:00Z", + updated_date: "2024-01-15T10:30:00Z", + + // Optional fields + reasoning: { + content: "Agent's reasoning process", + timing: 1500 + }, + tool_calls: [{ + name: "search", + arguments: { query: "weather" }, + result: { ... }, + status: "success" + }], + file_urls: ["https://..."], + usage: { + prompt_tokens: 150, + completion_tokens: 50 + }, + metadata: { ... }, + custom_context: { ... } +} +``` + +## Conversation Structure + +```javascript +{ + id: "conv-id-123", + app_id: "app-id", + agent_name: "support-agent", + created_by_id: "user-id", + created_date: "2024-01-15T10:00:00Z", + updated_date: "2024-01-15T10:30:00Z", + messages: [ ... ], + metadata: { ... } +} +``` + +## Common Patterns + +### Chat Interface + +```javascript +// Load conversation +const conv = await base44.agents.getConversation(conversationId); +setMessages(conv.messages); + +// Subscribe to updates +const unsubscribe = base44.agents.subscribeToConversation(conversationId, (updated) => { + setMessages(updated.messages); +}); + +// Send message +async function sendMessage(text) { + await base44.agents.addMessage(conv, { role: "user", content: text }); +} + +// Cleanup on unmount +return () => unsubscribe(); +``` + +## Type Definitions + +### AgentConversation + +```typescript +/** An agent conversation containing messages exchanged with an AI agent. */ +interface AgentConversation { + /** Unique identifier for the conversation. */ + id: string; + /** Application ID. */ + app_id: string; + /** Name of the agent in this conversation. */ + agent_name: string; + /** ID of the user who created the conversation. */ + created_by_id: string; + /** When the conversation was created. */ + created_date: string; + /** When the conversation was last updated. */ + updated_date: string; + /** Array of messages in the conversation. */ + messages: AgentMessage[]; + /** Optional metadata associated with the conversation. */ + metadata?: Record; +} +``` + +### AgentMessage + +```typescript +/** A message in an agent conversation. */ +interface AgentMessage { + /** Unique identifier for the message. */ + id: string; + /** Role of the message sender. */ + role: "user" | "assistant" | "system"; + /** When the message was created. */ + created_date: string; + /** When the message was last updated. */ + updated_date: string; + /** Message content. */ + content?: string | Record; + /** Optional reasoning information for the message. */ + reasoning?: AgentMessageReasoning | null; + /** URLs to files attached to the message. */ + file_urls?: string[]; + /** Tool calls made by the agent. */ + tool_calls?: AgentMessageToolCall[]; + /** Token usage statistics. */ + usage?: AgentMessageUsage; + /** Whether the message is hidden from the user. */ + hidden?: boolean; + /** Custom context provided with the message. */ + custom_context?: AgentMessageCustomContext[]; + /** Model used to generate the message. */ + model?: string; + /** Checkpoint ID for the message. */ + checkpoint_id?: string; + /** Metadata about when and by whom the message was created. */ + metadata?: AgentMessageMetadata; +} +``` + +### Supporting Types + +```typescript +/** Reasoning information for an agent message. */ +interface AgentMessageReasoning { + /** When reasoning started. */ + start_date: string; + /** When reasoning ended. */ + end_date?: string; + /** Reasoning content. */ + content: string; +} + +/** A tool call made by the agent. */ +interface AgentMessageToolCall { + /** Tool call ID. */ + id: string; + /** Name of the tool called. */ + name: string; + /** Arguments passed to the tool as JSON string. */ + arguments_string: string; + /** Status of the tool call. */ + status: "running" | "success" | "error" | "stopped"; + /** Results from the tool call. */ + results?: string; +} + +/** Token usage statistics for an agent message. */ +interface AgentMessageUsage { + /** Number of tokens in the prompt. */ + prompt_tokens?: number; + /** Number of tokens in the completion. */ + completion_tokens?: number; +} + +/** Custom context provided with an agent message. */ +interface AgentMessageCustomContext { + /** Context message. */ + message: string; + /** Associated data for the context. */ + data: Record; + /** Type of context. */ + type: string; +} + +/** Metadata about when and by whom a message was created. */ +interface AgentMessageMetadata { + /** When the message was created. */ + created_date: string; + /** Email of the user who created the message. */ + created_by_email: string; + /** Full name of the user who created the message. */ + created_by_full_name: string; +} +``` + +### CreateConversationParams + +```typescript +/** Parameters for creating a new conversation. */ +interface CreateConversationParams { + /** The name of the agent to create a conversation with. */ + agent_name: string; + /** Optional metadata to attach to the conversation. */ + metadata?: Record; +} +``` + +### ModelFilterParams + +```typescript +/** Parameters for filtering, sorting, and paginating conversations. */ +interface ModelFilterParams { + /** Query object with field-value pairs for filtering. */ + q?: Record; + /** Sort parameter (e.g., "-created_date" for descending). */ + sort?: string | null; + /** Maximum number of results to return. */ + limit?: number | null; + /** Number of results to skip for pagination. */ + skip?: number | null; + /** Array of field names to include in the response. */ + fields?: string[] | null; +} +``` + +### AgentsModule + +```typescript +/** Agents module for managing AI agent conversations. */ +interface AgentsModule { + /** Gets all conversations from all agents in the app. */ + getConversations(): Promise; + + /** Gets a specific conversation by ID. */ + getConversation(conversationId: string): Promise; + + /** Lists conversations with filtering, sorting, and pagination. */ + listConversations(filterParams: ModelFilterParams): Promise; + + /** Creates a new conversation with an agent. */ + createConversation(conversation: CreateConversationParams): Promise; + + /** Adds a message to a conversation. */ + addMessage(conversation: AgentConversation, message: Partial): Promise; + + /** Subscribes to real-time updates for a conversation. Returns unsubscribe function. */ + subscribeToConversation(conversationId: string, onUpdate?: (conversation: AgentConversation) => void): () => void; + + /** Gets WhatsApp connection URL for an agent. */ + getWhatsAppConnectURL(agentName: string): string; +} +``` diff --git a/evals/fixtures/anti-hallucination/skills/base44-sdk/references/client.md b/evals/fixtures/anti-hallucination/skills/base44-sdk/references/client.md new file mode 100644 index 0000000..554bf2b --- /dev/null +++ b/evals/fixtures/anti-hallucination/skills/base44-sdk/references/client.md @@ -0,0 +1,257 @@ +# Client Setup + +How to create and configure the Base44 client. + +## Contents +- [In Base44-Generated Apps](#in-base44-generated-apps) +- [In External Apps](#in-external-apps) +- [In Backend Functions](#in-backend-functions) +- [Authentication Modes](#authentication-modes) (Anonymous, User, Service Role) +- [Available Modules](#available-modules) +- [Client Methods](#client-methods) +- [Client Configuration Options](#client-configuration-options) + +## In Base44-Generated Apps + +The client is pre-configured and available as `base44`. Just use it: + +```javascript +const tasks = await base44.entities.Task.list(); +``` + +## In External Apps + +Install the SDK and create a client: + +```bash +npm install @base44/sdk +``` + +```javascript +import { createClient } from "@base44/sdk"; + +// IMPORTANT: The parameter name is 'appId' (NOT 'clientId', NOT 'id') +// IMPORTANT: onError must be nested inside 'options' object +const base44 = createClient({ + appId: "your-app-id", // Required: Use 'appId' parameter + token: "optional-user-token", // Optional: for pre-authenticated requests + options: { // Optional: configuration options + onError: (error) => { // Optional: error handler (must be in options) + console.error("Base44 error:", error); + } + } +}); +``` + +**Common Mistakes:** +- ❌ `createClient({ clientId: "..." })` - WRONG parameter name +- ❌ `createClient({ id: "..." })` - WRONG parameter name +- ❌ `createClient({ appId: "...", onError: ... })` - WRONG: onError must be in options +- ✅ `createClient({ appId: "..." })` - CORRECT parameter name +- ✅ `createClient({ appId: "...", options: { onError: ... } })` - CORRECT: onError in options + +## In Backend Functions + +Use `createClientFromRequest` to get a client with the caller's auth context: + +```javascript +import { createClientFromRequest } from "@base44/sdk"; + +Deno.serve(async (req) => { + const base44 = createClientFromRequest(req); + + // Client inherits authentication from the request + const user = await base44.auth.me(); + + return Response.json({ user }); +}); +``` + +## Authentication Modes + +| Mode | How to Get | Permissions | +|------|-----------|-------------| +| **Anonymous** | `createClient({ appId })` without token | Public data only | +| **User** | After `loginViaEmailPassword()` or via `createClientFromRequest` | User's own data | +| **Service Role** | `base44.asServiceRole.*` in backend | Full admin access | + +## Anonymous Mode + +No authentication. Can only access public resources. + +```javascript +const base44 = createClient({ appId: "your-app-id" }); + +// Only works if Task entity allows anonymous read +const publicTasks = await base44.entities.Task.list(); +``` + +## User Mode + +After user logs in, the client automatically includes their token. + +```javascript +const base44 = createClient({ appId: "your-app-id" }); + +// Login sets the token +await base44.auth.loginViaEmailPassword("user@example.com", "password"); + +// Subsequent requests are authenticated +const user = await base44.auth.me(); +const myTasks = await base44.entities.Task.list(); // filtered by permissions +``` + +## Service Role Mode + +Admin-level access. **Backend only.** + +```javascript +// Inside a backend function +Deno.serve(async (req) => { + const base44 = createClientFromRequest(req); + + // User mode - respects permissions + const myTasks = await base44.entities.Task.list(); + + // Service role - bypasses permissions + const allTasks = await base44.asServiceRole.entities.Task.list(); + const allUsers = await base44.asServiceRole.entities.User.list(); + const oauthToken = await base44.asServiceRole.connectors.getAccessToken("slack"); + + return Response.json({ myTasks, allTasks }); +}); +``` + +## Available Modules + +The client exposes these modules: + +```javascript +base44.entities // CRUD operations +base44.auth // Authentication +base44.agents // AI conversations +base44.functions // Backend function invocation +base44.integrations // Third-party services +base44.analytics // Event tracking +base44.appLogs // App usage logging +base44.users // User invitations + +// Service role only (backend) +base44.asServiceRole.entities +base44.asServiceRole.agents +base44.asServiceRole.functions +base44.asServiceRole.integrations +base44.asServiceRole.appLogs +base44.asServiceRole.connectors +``` + +## Client Methods + +The client provides these methods: + +```javascript +// Set authentication token for all subsequent requests +base44.setToken(newToken); + +// Cleanup WebSocket connections (call when done with client) +base44.cleanup(); +``` + +### setToken + +Updates the authentication token for all subsequent API requests and WebSocket connections. + +```javascript +// After receiving a token (e.g., from external auth) +base44.setToken("new-jwt-token"); +``` + +### cleanup + +Disconnects WebSocket connections. Call when you're done with the client or when the component unmounts. + +```javascript +// Cleanup on component unmount (React example) +useEffect(() => { + return () => base44.cleanup(); +}, []); +``` + +## Client Configuration Options + +```javascript +createClient({ + appId: "your-app-id", // Required: MUST use 'appId' (not 'clientId' or 'id') + token: "jwt-token", // Optional: pre-set auth token + options: { // Optional: configuration options + onError: (error) => {} // Optional: global error handler (must be in options) + } +}); +``` + +**⚠️ Critical:** +- The parameter name is `appId`, not `clientId` or `id`. Using the wrong parameter name will cause errors. +- The `onError` handler must be nested inside the `options` object, not at the top level. + +## Type Definitions + +### CreateClientConfig + +```typescript +/** Configuration for creating a Base44 client. */ +interface CreateClientConfig { + /** The Base44 app ID (required). */ + appId: string; + /** User authentication token. Used to authenticate as a specific user. */ + token?: string; + /** Service role authentication token (backend only). */ + serviceToken?: string; + /** Additional client options. */ + options?: CreateClientOptions; +} + +/** Options for creating a Base44 client. */ +interface CreateClientOptions { + /** Optional error handler called whenever an API error occurs. */ + onError?: (error: Error) => void; +} +``` + +### Base44Client + +```typescript +/** The Base44 client instance. */ +interface Base44Client { + /** Entities module for CRUD operations on your data models. */ + entities: EntitiesModule; + /** Integrations module for calling pre-built integration endpoints. */ + integrations: IntegrationsModule; + /** Auth module for user authentication and management. */ + auth: AuthModule; + /** Functions module for invoking custom backend functions. */ + functions: FunctionsModule; + /** Agents module for managing AI agent conversations. */ + agents: AgentsModule; + /** App logs module for tracking app usage. */ + appLogs: AppLogsModule; + /** Analytics module for tracking custom events. */ + analytics: AnalyticsModule; + + /** Cleanup function to disconnect WebSocket connections. */ + cleanup(): void; + + /** Sets a new authentication token for all subsequent requests. */ + setToken(newToken: string): void; + + /** Provides access to modules with elevated service role permissions (backend only). */ + readonly asServiceRole: { + entities: EntitiesModule; + integrations: IntegrationsModule; + connectors: ConnectorsModule; + functions: FunctionsModule; + agents: AgentsModule; + appLogs: AppLogsModule; + cleanup(): void; + }; +} +``` diff --git a/evals/fixtures/anti-hallucination/skills/base44-sdk/references/connectors.md b/evals/fixtures/anti-hallucination/skills/base44-sdk/references/connectors.md new file mode 100644 index 0000000..f3fbcb4 --- /dev/null +++ b/evals/fixtures/anti-hallucination/skills/base44-sdk/references/connectors.md @@ -0,0 +1,113 @@ +# Connectors Module + +OAuth token management for external services. **Service role only** (backend functions). + +## Method + +```javascript +base44.asServiceRole.connectors.getAccessToken(integrationType): Promise +``` + +Returns a raw OAuth access token for the specified service. + +## Supported Services + +| Integration Type | Service | +|-----------------|---------| +| `"googlecalendar"` | Google Calendar | +| `"googledrive"` | Google Drive | +| `"slack"` | Slack | +| `"notion"` | Notion | +| `"salesforce"` | Salesforce | +| `"hubspot"` | HubSpot | +| `"linkedin"` | LinkedIn | +| `"tiktok"` | TikTok | +| `"github"` | GitHub | + +## Example Usage + +```javascript +// Backend function only +Deno.serve(async (req) => { + const base44 = createClientFromRequest(req); + + // Get OAuth token for Slack + const slackToken = await base44.asServiceRole.connectors.getAccessToken("slack"); + + // Use token directly with Slack API + const response = await fetch("https://slack.com/api/chat.postMessage", { + method: "POST", + headers: { + "Authorization": `Bearer ${slackToken}`, + "Content-Type": "application/json" + }, + body: JSON.stringify({ + channel: "#general", + text: "Hello from Base44!" + }) + }); + + return Response.json(await response.json()); +}); +``` + +## Google Calendar Example + +```javascript +Deno.serve(async (req) => { + const base44 = createClientFromRequest(req); + + const token = await base44.asServiceRole.connectors.getAccessToken("googlecalendar"); + + // List upcoming events + const response = await fetch( + "https://www.googleapis.com/calendar/v3/calendars/primary/events?" + + new URLSearchParams({ + maxResults: "10", + orderBy: "startTime", + singleEvents: "true", + timeMin: new Date().toISOString() + }), + { + headers: { "Authorization": `Bearer ${token}` } + } + ); + + const events = await response.json(); + return Response.json(events); +}); +``` + +## Setup Requirements + +1. **Builder plan** or higher +2. **Backend functions** enabled +3. **Connector configured** in Base44 dashboard (OAuth flow completed) + +## Important Notes + +- **One account per connector per app**: All users share the same connected account +- **Backend only**: `connectors` module not available in frontend code +- **Service role required**: Must use `base44.asServiceRole.connectors` +- **You handle the API calls**: Base44 only provides the token; you make the actual API requests +- **Token refresh**: Base44 handles token refresh automatically + +## Type Definitions + +```typescript +/** + * The type of external integration/connector. + * Examples: 'googlecalendar', 'slack', 'github', 'notion', etc. + */ +type ConnectorIntegrationType = string; + +/** Connectors module for managing OAuth tokens for external services. */ +interface ConnectorsModule { + /** + * Retrieves an OAuth access token for a specific external integration type. + * @param integrationType - The type of integration (e.g., 'googlecalendar', 'slack'). + * @returns Promise resolving to the access token string. + */ + getAccessToken(integrationType: ConnectorIntegrationType): Promise; +} +``` diff --git a/evals/fixtures/anti-hallucination/skills/base44-sdk/references/entities.md b/evals/fixtures/anti-hallucination/skills/base44-sdk/references/entities.md new file mode 100644 index 0000000..a7bdf35 --- /dev/null +++ b/evals/fixtures/anti-hallucination/skills/base44-sdk/references/entities.md @@ -0,0 +1,253 @@ +# Entities Module + +CRUD operations on data models. Access via `base44.entities.EntityName.method()`. + +## Contents +- [Methods](#methods) +- [Examples](#examples) (Create, Bulk Create, List, Filter, Get, Update, Delete, Subscribe) +- [User Entity](#user-entity) +- [Service Role Access](#service-role-access) +- [Permissions](#permissions) + +## Methods + +| Method | Signature | Description | +|--------|-----------|-------------| +| `create(data)` | `Promise` | Create one record | +| `bulkCreate(dataArray)` | `Promise` | Create multiple records | +| `list(sort?, limit?, skip?, fields?)` | `Promise` | Get all records (paginated) | +| `filter(query, sort?, limit?, skip?, fields?)` | `Promise` | Get records matching conditions | +| `get(id)` | `Promise` | Get single record by ID | +| `update(id, data)` | `Promise` | Update record (partial update) | +| `delete(id)` | `Promise` | Delete record by ID | +| `deleteMany(query)` | `Promise` | Delete all matching records | +| `importEntities(file)` | `Promise` | Import from CSV (frontend only) | +| `subscribe(callback)` | `() => void` | Subscribe to realtime updates (returns unsubscribe function) | + +## Examples + +### Create + +```javascript +const task = await base44.entities.Task.create({ + title: "Complete documentation", + status: "pending", + dueDate: "2024-12-31" +}); +``` + +### Bulk Create + +```javascript +const tasks = await base44.entities.Task.bulkCreate([ + { title: "Task 1", status: "pending" }, + { title: "Task 2", status: "pending" } +]); +``` + +### List with Pagination + +```javascript +// Get first 10 records, sorted by created_date descending +const tasks = await base44.entities.Task.list( + "-created_date", // sort (string: prefix with - for descending) + 10, // limit + 0 // skip +); + +// Get next page +const page2 = await base44.entities.Task.list("-created_date", 10, 10); +``` + +### Filter + +```javascript +// Simple filter +const pending = await base44.entities.Task.filter({ status: "pending" }); + +// Multiple conditions +const myPending = await base44.entities.Task.filter({ + status: "pending", + assignedTo: userId +}); + +// With sort, limit, skip +const recent = await base44.entities.Task.filter( + { status: "pending" }, + "-created_date", // sort (string: prefix with - for descending) + 5, + 0 +); + +// Select specific fields +const titles = await base44.entities.Task.filter( + { status: "pending" }, + null, + null, + null, + ["id", "title"] +); +``` + +### Get by ID + +```javascript +const task = await base44.entities.Task.get("task-id-123"); +``` + +### Update + +```javascript +// Partial update - only specified fields change +await base44.entities.Task.update("task-id-123", { + status: "completed", + completedAt: new Date().toISOString() +}); +``` + +### Delete + +```javascript +// Single record +await base44.entities.Task.delete("task-id-123"); + +// Multiple records matching query +await base44.entities.Task.deleteMany({ status: "archived" }); +``` + +### Subscribe to Realtime Updates + +```javascript +// Subscribe to all changes on Task entity +const unsubscribe = base44.entities.Task.subscribe((event) => { + console.log(`Task ${event.id} was ${event.type}:`, event.data); + // event.type is "create", "update", or "delete" +}); + +// Later: unsubscribe to stop receiving updates +unsubscribe(); +``` + +**Event structure:** +```javascript +{ + type: "create" | "update" | "delete", + data: { ... }, // the entity data + id: "entity-id", // the affected entity's ID + timestamp: "2024-01-15T10:30:00Z" +} +``` + +## User Entity + +Every app has a built-in `User` entity with special rules: + +- Regular users can only read/update **their own** record +- Cannot create users via `entities.create()` - use `auth.register()` instead +- Service role has full access to all user records + +```javascript +// Get current user's record +const me = await base44.entities.User.get(currentUserId); + +// Service role: get any user +const anyUser = await base44.asServiceRole.entities.User.get(userId); +``` + +## Service Role Access + +For admin-level operations (bypass user permissions): + +```javascript +// Backend only +const allTasks = await base44.asServiceRole.entities.Task.list(); +const allUsers = await base44.asServiceRole.entities.User.list(); +``` + +## Permissions (RLS & FLS) + +Data access is controlled by **Row Level Security (RLS)** and **Field Level Security (FLS)** rules defined in entity schemas. + +1. **Authentication level**: anonymous, authenticated, or service role +2. **RLS rules**: Control which records (rows) users can create/read/update/delete +3. **FLS rules**: Control which fields users can read/write within accessible records + +Operations succeed or fail based on these rules - no partial results. + +RLS and FLS are configured in entity schema files (`base44/entities/*.jsonc`). See [entities-create.md](../../base44-cli/references/entities-create.md#row-level-security-rls) for configuration details. + +**Note:** `asServiceRole` sets the user's role to `"admin"` but does NOT bypass RLS. Your RLS rules must include admin access (e.g., `{ "user_condition": { "role": "admin" } }`) for service role operations to succeed. + +## Type Definitions + +### RealtimeEvent + +```typescript +/** Event types for realtime entity updates. */ +type RealtimeEventType = "create" | "update" | "delete"; + +/** Payload received when a realtime event occurs. */ +interface RealtimeEvent { + /** The type of change that occurred. */ + type: RealtimeEventType; + /** The entity data. */ + data: any; + /** The unique identifier of the affected entity. */ + id: string; + /** ISO 8601 timestamp of when the event occurred. */ + timestamp: string; +} + +/** Callback function invoked when a realtime event occurs. */ +type RealtimeCallback = (event: RealtimeEvent) => void; + +/** Function returned from subscribe, call it to unsubscribe. */ +type Subscription = () => void; +``` + +### EntityHandler + +```typescript +/** Entity handler providing CRUD operations for a specific entity type. */ +interface EntityHandler { + /** Lists records with optional pagination and sorting. */ + list(sort?: string, limit?: number, skip?: number, fields?: string[]): Promise; + + /** Filters records based on a query. */ + filter(query: Record, sort?: string, limit?: number, skip?: number, fields?: string[]): Promise; + + /** Gets a single record by ID. */ + get(id: string): Promise; + + /** Creates a new record. */ + create(data: Record): Promise; + + /** Updates an existing record. */ + update(id: string, data: Record): Promise; + + /** Deletes a single record by ID. */ + delete(id: string): Promise; + + /** Deletes multiple records matching a query. */ + deleteMany(query: Record): Promise; + + /** Creates multiple records in a single request. */ + bulkCreate(data: Record[]): Promise; + + /** Imports records from a file (frontend only). */ + importEntities(file: File): Promise; + + /** Subscribes to realtime updates. Returns unsubscribe function. */ + subscribe(callback: RealtimeCallback): Subscription; +} +``` + +### EntitiesModule + +```typescript +/** Entities module for managing app data. */ +interface EntitiesModule { + /** Access any entity by name dynamically. */ + [entityName: string]: EntityHandler; +} +``` diff --git a/evals/fixtures/anti-hallucination/skills/base44-sdk/references/functions.md b/evals/fixtures/anti-hallucination/skills/base44-sdk/references/functions.md new file mode 100644 index 0000000..8072465 --- /dev/null +++ b/evals/fixtures/anti-hallucination/skills/base44-sdk/references/functions.md @@ -0,0 +1,216 @@ +# Functions Module + +Invoke custom backend functions via `base44.functions`. + +## Contents +- [Method](#method) +- [Invoking Functions](#invoking-functions) (Frontend, File Upload, Service Role, REST API) +- [Writing Backend Functions](#writing-backend-functions) (Basic, Service Role, Secrets, Errors) +- [Setup Requirements](#setup-requirements) +- [Authentication Modes](#authentication-modes) + +## Method + +```javascript +base44.functions.invoke(functionName, data): Promise +``` + +- `functionName`: Name of the backend function +- `data`: Object of parameters (sent as JSON, or multipart if contains File objects) +- Returns: Whatever the function returns + +## Invoking Functions + +### From Frontend + +```javascript +const result = await base44.functions.invoke("processOrder", { + orderId: "order-123", + action: "ship" +}); + +console.log(result); +``` + +### With File Upload + +```javascript +const fileInput = document.querySelector('input[type="file"]'); +const file = fileInput.files[0]; + +// Automatically uses multipart/form-data when File objects present +const result = await base44.functions.invoke("uploadDocument", { + file: file, + category: "invoices" +}); +``` + +### With Service Role (Backend) + +```javascript +// Inside another backend function +const result = await base44.asServiceRole.functions.invoke("adminTask", { + userId: "user-123" +}); +``` + +### Via REST API (curl) + +Functions can be called via HTTP POST to your app domain: + +```bash +curl -X POST "https:///functions/" \ + -H "Content-Type: application/json" \ + -d '{"key": "value"}' +``` + +## Writing Backend Functions + +Backend functions run on Deno. Must export using `Deno.serve()`. + +### Required Directory Structure + +Each function must be in its own subdirectory under `base44/functions/` with a configuration file: + +``` +base44/ + functions/ + process-order/ # kebab-case directory name + function.jsonc # required configuration + index.ts # entry point +``` + +**function.jsonc:** +```jsonc +{ + "name": "process-order", + "entry": "index.ts" +} +``` + +For complete setup and deployment instructions, see [functions-create.md](../../base44-cli/references/functions-create.md) in base44-cli. + +### Basic Structure + +```javascript +// base44/functions/process-order/index.ts +import { createClientFromRequest } from "npm:@base44/sdk"; + +Deno.serve(async (req) => { + // Get authenticated client from request + const base44 = createClientFromRequest(req); + + // Parse input + const { orderId, action } = await req.json(); + + // Your logic here + const order = await base44.entities.Orders.get(orderId); + + // Return response + return Response.json({ + success: true, + order: order + }); +}); +``` + +### With Service Role Access + +```javascript +import { createClientFromRequest } from "npm:@base44/sdk"; + +Deno.serve(async (req) => { + const base44 = createClientFromRequest(req); + + // Check user is authenticated + const user = await base44.auth.me(); + if (!user) { + return Response.json({ error: "Unauthorized" }, { status: 401 }); + } + + // Use service role for admin operations + const allOrders = await base44.asServiceRole.entities.Orders.list(); + + return Response.json({ orders: allOrders }); +}); +``` + +### Using Secrets + +```javascript +Deno.serve(async (req) => { + // Access environment variables (configured in app settings) + const apiKey = Deno.env.get("STRIPE_API_KEY"); + + const response = await fetch("https://api.stripe.com/v1/charges", { + headers: { + "Authorization": `Bearer ${apiKey}` + } + }); + + return Response.json(await response.json()); +}); +``` + +### Error Handling + +```javascript +import { createClientFromRequest } from "npm:@base44/sdk"; + +Deno.serve(async (req) => { + try { + const base44 = createClientFromRequest(req); + const { orderId } = await req.json(); + + const order = await base44.entities.Orders.get(orderId); + if (!order) { + return Response.json( + { error: "Order not found" }, + { status: 404 } + ); + } + + return Response.json({ order }); + + } catch (error) { + return Response.json( + { error: error.message }, + { status: 500 } + ); + } +}); +``` + +## Setup Requirements + +1. Enable Backend Functions in app settings (requires appropriate plan) +2. Create function files in `/functions` folder +3. Configure secrets via app dashboard for API keys + +## Authentication Modes + +| Mode | Context | Permissions | +|------|---------|-------------| +| User | `base44.functions.invoke()` | Runs under calling user's permissions | +| Service Role | `base44.asServiceRole.functions.invoke()` | Admin-level access | + +Inside the function, use `createClientFromRequest(req)` to get a client that inherits the caller's auth context. + +## Type Definitions + +```typescript +/** Functions module for invoking custom backend functions. */ +interface FunctionsModule { + /** + * Invokes a custom backend function by name. + * + * If any parameter is a File object, the request will automatically be + * sent as multipart/form-data. Otherwise, it will be sent as JSON. + * + * @param functionName - The name of the function to invoke. + * @param data - An object containing named parameters for the function. + * @returns Promise resolving to the function's response. + */ + invoke(functionName: string, data: Record): Promise; +} +``` diff --git a/evals/fixtures/anti-hallucination/skills/base44-sdk/references/integrations.md b/evals/fixtures/anti-hallucination/skills/base44-sdk/references/integrations.md new file mode 100644 index 0000000..f356a18 --- /dev/null +++ b/evals/fixtures/anti-hallucination/skills/base44-sdk/references/integrations.md @@ -0,0 +1,377 @@ +# Integrations Module + +Access third-party services via `base44.integrations`. + +## Types of Integrations + +1. **Core/Built-in**: AI, email, file uploads (available by default) +2. **Catalog integrations**: Pre-built connectors from Base44 catalog +3. **Custom integrations**: Your own OpenAPI-based integrations + +## Accessing Integrations + +```javascript +// Core integrations +base44.integrations.Core.FunctionName(params) + +// Custom integrations +base44.integrations.custom.call(slug, operationId, params) +``` + +## Core Integrations + +### InvokeLLM (AI Text Generation) + +Generate text or structured JSON data using AI models. + +```javascript +// Basic prompt - returns string +const response = await base44.integrations.Core.InvokeLLM({ + prompt: "Summarize this text: ..." +}); + +// With internet context (uses Google Search, Maps, News) +const response = await base44.integrations.Core.InvokeLLM({ + prompt: "What's the current weather in NYC?", + add_context_from_internet: true +}); + +// Structured JSON response - returns object +const response = await base44.integrations.Core.InvokeLLM({ + prompt: "Analyze the sentiment of: 'Great product but slow shipping'", + response_json_schema: { + type: "object", + properties: { + sentiment: { type: "string", enum: ["positive", "negative", "mixed"] }, + score: { type: "number", description: "Score from 1-10" }, + key_points: { type: "array", items: { type: "string" } } + } + } +}); +// Returns: { sentiment: "mixed", score: 7, key_points: ["great product", "slow shipping"] } + +// With file attachments (uploaded via UploadFile) +const response = await base44.integrations.Core.InvokeLLM({ + prompt: "Describe what's in this image", + file_urls: ["https://...uploaded_image.png"] +}); +``` + +**Parameters:** +- `prompt` (string, required): The prompt text to send to the model +- `add_context_from_internet` (boolean, optional): If true, uses Google Search/Maps/News for real-time context +- `response_json_schema` (object, optional): JSON schema for structured output +- `file_urls` (string[], optional): URLs of uploaded files for context + +### GenerateImage + +Create AI-generated images from text prompts. + +```javascript +const { url } = await base44.integrations.Core.GenerateImage({ + prompt: "A serene mountain landscape with a lake" +}); +console.log(url); // https://...generated_image.png +``` + +### SendEmail + +Send emails to registered users. Every app gets this integration (no plan upgrade required). + +```javascript +await base44.integrations.Core.SendEmail({ + to: "user@example.com", + subject: "Welcome!", + body: "

Hello

Welcome to our app.

", + from_name: "My App" // optional, defaults to app name +}); +``` + +**Parameters:** +- `to` (string, required): Recipient email address +- `subject` (string, required): Email subject line +- `body` (string, required): Plain text or HTML email body +- `from_name` (string, optional): Sender name displayed to recipient + +**Limitations:** +- 1 credit per email (2 credits with custom domain) + +### UploadFile (Public) + +Upload files to public storage. + +```javascript +const fileInput = document.querySelector('input[type="file"]'); +const { file_url } = await base44.integrations.Core.UploadFile({ + file: fileInput.files[0] +}); +console.log(file_url); // https://...uploaded_file.pdf +``` + +### UploadPrivateFile + +Upload files to private storage that requires a signed URL to access. + +```javascript +const { file_uri } = await base44.integrations.Core.UploadPrivateFile({ + file: fileInput.files[0] +}); +console.log(file_uri); // "private/user123/document.pdf" + +// Create a signed URL to access the file +const { signed_url } = await base44.integrations.Core.CreateFileSignedUrl({ + file_uri, + expires_in: 3600 // URL expires in 1 hour (default: 300 seconds) +}); +console.log(signed_url); // Temporary URL to access the private file +``` + +### CreateFileSignedUrl + +Generate temporary access links for private files. + +```javascript +const { signed_url } = await base44.integrations.Core.CreateFileSignedUrl({ + file_uri: "private/user123/document.pdf", + expires_in: 7200 // 2 hours +}); +``` + +**Parameters:** +- `file_uri` (string, required): URI from UploadPrivateFile +- `expires_in` (number, optional): Expiration time in seconds (default: 300) + +### ExtractDataFromUploadedFile + +Extract structured data from uploaded files using AI. + +```javascript +// First upload the file +const { file_url } = await base44.integrations.Core.UploadFile({ file }); + +// Then extract structured data +const result = await base44.integrations.Core.ExtractDataFromUploadedFile({ + file_url, + json_schema: { + type: "object", + properties: { + invoice_number: { type: "string" }, + total_amount: { type: "number" }, + date: { type: "string" }, + vendor_name: { type: "string" } + } + } +}); +console.log(result); // { invoice_number: "INV-12345", total_amount: 1250.00, ... } +``` + +## Custom Integrations + +Custom integrations allow workspace administrators to connect any external API by importing an OpenAPI specification. Use `base44.integrations.custom.call()` to invoke them. + +### Syntax + +```javascript +const response = await base44.integrations.custom.call( + slug, // Integration identifier (set by admin) + operationId, // Endpoint in "method:path" format + params // Optional: payload, pathParams, queryParams +); +``` + +### Examples + +```javascript +// GET request with query params +const response = await base44.integrations.custom.call( + "my-crm", + "get:/contacts", + { queryParams: { limit: 10, status: "active" } } +); + +// POST request with body +const response = await base44.integrations.custom.call( + "my-crm", + "post:/contacts", + { payload: { name: "John Doe", email: "john@example.com" } } +); + +// Request with path parameters +const response = await base44.integrations.custom.call( + "github", + "post:/repos/{owner}/{repo}/issues", + { + pathParams: { owner: "myorg", repo: "myrepo" }, + payload: { title: "Bug report", body: "Something is broken" } + } +); +``` + +### Response Structure + +```javascript +{ + success: true, // Whether external API returned 2xx + status_code: 200, // HTTP status code + data: { ... } // Response from external API +} +``` + +## Requirements + +- **Core integrations**: Available on all plans +- **Catalog/Custom integrations**: Require Builder plan or higher + +## Type Definitions + +### Core Integration Parameters + +```typescript +/** Parameters for the InvokeLLM function. */ +interface InvokeLLMParams { + /** The prompt text to send to the model. */ + prompt: string; + /** If true, uses Google Search/Maps/News for real-time context. */ + add_context_from_internet?: boolean; + /** JSON schema for structured output. If provided, returns object instead of string. */ + response_json_schema?: object; + /** File URLs (from UploadFile) to provide as context. */ + file_urls?: string[]; +} + +/** Parameters for the GenerateImage function. */ +interface GenerateImageParams { + /** Description of the image to generate. */ + prompt: string; +} + +/** Result from GenerateImage. */ +interface GenerateImageResult { + /** URL of the generated image. */ + url: string; +} + +/** Parameters for the UploadFile function. */ +interface UploadFileParams { + /** The file object to upload. */ + file: File; +} + +/** Result from UploadFile. */ +interface UploadFileResult { + /** URL of the uploaded file. */ + file_url: string; +} + +/** Parameters for the SendEmail function. */ +interface SendEmailParams { + /** Recipient email address. */ + to: string; + /** Email subject line. */ + subject: string; + /** Plain text or HTML email body. */ + body: string; + /** Sender name (defaults to app name). */ + from_name?: string; +} + +/** Parameters for ExtractDataFromUploadedFile. */ +interface ExtractDataFromUploadedFileParams { + /** URL of the uploaded file. */ + file_url: string; + /** JSON schema defining fields to extract. */ + json_schema: object; +} + +/** Parameters for UploadPrivateFile. */ +interface UploadPrivateFileParams { + /** The file object to upload. */ + file: File; +} + +/** Result from UploadPrivateFile. */ +interface UploadPrivateFileResult { + /** URI of the private file (used for signed URLs). */ + file_uri: string; +} + +/** Parameters for CreateFileSignedUrl. */ +interface CreateFileSignedUrlParams { + /** URI from UploadPrivateFile. */ + file_uri: string; + /** Expiration time in seconds (default: 300). */ + expires_in?: number; +} + +/** Result from CreateFileSignedUrl. */ +interface CreateFileSignedUrlResult { + /** Temporary signed URL to access the file. */ + signed_url: string; +} +``` + +### CoreIntegrations + +```typescript +/** Core package containing built-in Base44 integration functions. */ +interface CoreIntegrations { + InvokeLLM(params: InvokeLLMParams): Promise; + GenerateImage(params: GenerateImageParams): Promise; + UploadFile(params: UploadFileParams): Promise; + SendEmail(params: SendEmailParams): Promise; + ExtractDataFromUploadedFile(params: ExtractDataFromUploadedFileParams): Promise; + UploadPrivateFile(params: UploadPrivateFileParams): Promise; + CreateFileSignedUrl(params: CreateFileSignedUrlParams): Promise; +} +``` + +### Custom Integrations + +```typescript +/** Parameters for calling a custom integration endpoint. */ +interface CustomIntegrationCallParams { + /** Request body payload. */ + payload?: Record; + /** Path parameters to substitute in the URL. */ + pathParams?: Record; + /** Query string parameters. */ + queryParams?: Record; + /** Additional headers for this request. */ + headers?: Record; +} + +/** Response from a custom integration call. */ +interface CustomIntegrationCallResponse { + /** Whether the external API returned a 2xx status code. */ + success: boolean; + /** The HTTP status code from the external API. */ + status_code: number; + /** The response data from the external API. */ + data: any; +} + +/** Module for calling custom workspace-level API integrations. */ +interface CustomIntegrationsModule { + /** + * Call a custom integration endpoint. + * @param slug - The integration's unique identifier. + * @param operationId - The operation ID from the OpenAPI spec. + * @param params - Optional payload, pathParams, queryParams, headers. + */ + call(slug: string, operationId: string, params?: CustomIntegrationCallParams): Promise; +} +``` + +### IntegrationsModule + +```typescript +/** Integrations module for calling integration endpoints. */ +type IntegrationsModule = { + /** Core package with built-in integrations. */ + Core: CoreIntegrations; + /** Custom integrations module. */ + custom: CustomIntegrationsModule; + /** Additional integration packages (dynamic). */ + [packageName: string]: any; +}; +``` diff --git a/evals/fixtures/anti-hallucination/skills/base44-sdk/references/users.md b/evals/fixtures/anti-hallucination/skills/base44-sdk/references/users.md new file mode 100644 index 0000000..4205b06 --- /dev/null +++ b/evals/fixtures/anti-hallucination/skills/base44-sdk/references/users.md @@ -0,0 +1,82 @@ +# Users Module + +Invite users to the app via `base44.users`. + +## Contents +- [Methods](#methods) +- [Examples](#examples) (Invite User) +- [Roles](#roles) +- [Notes](#notes) + +## Methods + +| Method | Signature | Description | +|--------|-----------|-------------| +| `inviteUser(user_email, role)` | `Promise` | Invite a user to the app | + +## Examples + +### Invite User + +```javascript +// Invite a user with "user" role +await base44.users.inviteUser("newuser@example.com", "user"); + +// Invite an admin +await base44.users.inviteUser("admin@example.com", "admin"); +``` + +### Invite Multiple Users + +```javascript +const usersToInvite = [ + { email: "user1@example.com", role: "user" }, + { email: "user2@example.com", role: "user" }, + { email: "manager@example.com", role: "admin" } +]; + +for (const user of usersToInvite) { + await base44.users.inviteUser(user.email, user.role); + console.log(`Invited ${user.email} as ${user.role}`); +} +``` + +## Roles + +The `role` parameter must be one of: + +| Role | Description | +|------|-------------| +| `"user"` | Standard user with default permissions | +| `"admin"` | Administrator with elevated permissions | + +**Note:** Only `"user"` and `"admin"` are valid role values. An error will be thrown if you pass any other value. + +## Notes + +- **Email invitation**: The invited user receives an email with a link to join the app +- **Duplicate handling**: Inviting an existing user will re-send the invitation +- **Also available in auth**: `base44.auth.inviteUser()` provides the same functionality +- **Role validation**: Only `"user"` or `"admin"` are accepted + +```javascript +// These are equivalent: +await base44.users.inviteUser("newuser@example.com", "user"); +await base44.auth.inviteUser("newuser@example.com", "user"); +``` + +## Type Definitions + +```typescript +/** Users module for inviting users to the app. */ +interface UsersModule { + /** + * Invite a user to the application. + * @param user_email - User's email address. + * @param role - User's role ('user' or 'admin'). + * @returns Promise resolving when the invitation is sent. + * @throws Error if role is not 'user' or 'admin'. + */ + inviteUser(user_email: string, role: "user" | "admin"): Promise; +} +``` diff --git a/evals/fixtures/cli-agents/eval.json b/evals/fixtures/cli-agents/eval.json new file mode 100644 index 0000000..1cc0117 --- /dev/null +++ b/evals/fixtures/cli-agents/eval.json @@ -0,0 +1,201 @@ +{ + "$schema": "../../eval.schema.json", + "name": "cli-agents", + "description": "Test CLI agent configuration: full CRUD, multi-entity, functions, mixed tools", + "prompts": [ + { + "name": "cli-agent-full-crud", + "description": "Create agent with full CRUD access to entity", + "prompt": "Create an agent called 'inventory_manager' that manages the product inventory. It should have full CRUD access (read, create, update, delete) to the Product entity. Give it instructions to help users add, update, and remove products from inventory. Save at base44/agents/inventory_manager.jsonc.", + "expectedSkills": [ + "base44-cli" + ], + "checks": [ + { + "type": "file-exists", + "filePath": "base44/agents/inventory_manager.jsonc", + "description": "base44/agents/inventory_manager.jsonc exists" + }, + { + "type": "valid-json", + "filePath": "base44/agents/inventory_manager.jsonc", + "description": "base44/agents/inventory_manager.jsonc is valid JSON" + }, + { + "type": "agent-config", + "filePath": "base44/agents/inventory_manager.jsonc", + "target": "file", + "description": "Agent config validates (base44/agents/inventory_manager.jsonc)" + }, + { + "type": "file-content", + "filePath": "base44/agents/inventory_manager.jsonc", + "pattern": "\"name\"\\s*:\\s*\"inventory_manager\"", + "description": "Has name field" + }, + { + "type": "file-content", + "filePath": "base44/agents/inventory_manager.jsonc", + "pattern": "\"entity_name\"\\s*:\\s*\"Product\"", + "description": "Has entity_name field" + }, + { + "type": "file-content", + "filePath": "base44/agents/inventory_manager.jsonc", + "pattern": "\"allowed_operations\"\\s*:\\s*\\[[^\\]]*\"read\"[^\\]]*\"create\"[^\\]]*\"update\"[^\\]]*\"delete\"", + "description": "Has allowed_operations field" + }, + { + "type": "file-content", + "filePath": "base44/agents/inventory_manager.jsonc", + "pattern": "\"instructions\"\\s*:", + "description": "Has instructions field" + } + ] + }, + { + "name": "cli-agent-multi-entity", + "description": "Create agent with access to multiple entities", + "prompt": "Create an agent called 'order_tracker' that helps customers track their orders. It should have read access to both Order and Customer entities. Give it instructions to look up order details and customer information. Save at base44/agents/order_tracker.jsonc.", + "expectedSkills": [ + "base44-cli" + ], + "checks": [ + { + "type": "file-exists", + "filePath": "base44/agents/order_tracker.jsonc", + "description": "base44/agents/order_tracker.jsonc exists" + }, + { + "type": "valid-json", + "filePath": "base44/agents/order_tracker.jsonc", + "description": "base44/agents/order_tracker.jsonc is valid JSON" + }, + { + "type": "agent-config", + "filePath": "base44/agents/order_tracker.jsonc", + "target": "file", + "description": "Agent config validates (base44/agents/order_tracker.jsonc)" + }, + { + "type": "file-content", + "filePath": "base44/agents/order_tracker.jsonc", + "pattern": "\"entity_name\"\\s*:\\s*\"Order\"", + "description": "Has entity_name field" + }, + { + "type": "file-content", + "filePath": "base44/agents/order_tracker.jsonc", + "pattern": "\"entity_name\"\\s*:\\s*\"Customer\"", + "description": "Has entity_name field" + }, + { + "type": "file-content", + "filePath": "base44/agents/order_tracker.jsonc", + "pattern": "\"allowed_operations\"\\s*:\\s*\\[[^\\]]*\"read\"", + "description": "Has allowed_operations field" + } + ] + }, + { + "name": "cli-agent-functions", + "description": "Create agent with function tool access", + "prompt": "Create an agent called 'shipping_bot' that helps users with shipping. It should have access to a backend function called 'calculate_shipping' (described as 'Calculate shipping cost for an order') and read access to the Order entity. Save at base44/agents/shipping_bot.jsonc.", + "expectedSkills": [ + "base44-cli" + ], + "checks": [ + { + "type": "file-exists", + "filePath": "base44/agents/shipping_bot.jsonc", + "description": "base44/agents/shipping_bot.jsonc exists" + }, + { + "type": "valid-json", + "filePath": "base44/agents/shipping_bot.jsonc", + "description": "base44/agents/shipping_bot.jsonc is valid JSON" + }, + { + "type": "agent-config", + "filePath": "base44/agents/shipping_bot.jsonc", + "target": "file", + "description": "Agent config validates (base44/agents/shipping_bot.jsonc)" + }, + { + "type": "file-content", + "filePath": "base44/agents/shipping_bot.jsonc", + "pattern": "\"function_name\"\\s*:\\s*\"calculate_shipping\"", + "description": "Has function_name field" + }, + { + "type": "file-content", + "filePath": "base44/agents/shipping_bot.jsonc", + "pattern": "\"entity_name\"\\s*:\\s*\"Order\"", + "description": "Has entity_name field" + }, + { + "type": "file-content", + "filePath": "base44/agents/shipping_bot.jsonc", + "pattern": "\"description\"\\s*:", + "description": "Has description field" + } + ] + }, + { + "name": "cli-agent-mixed", + "description": "Create agent with mixed entity and function tools", + "prompt": "Create an agent called 'store_assistant' that helps manage a store. It should have: 1) Full CRUD access to Product entity, 2) Read access to Order entity, 3) Access to a function called 'send_receipt' (described as 'Send order receipt email'). Include detailed instructions for the agent. Save at base44/agents/store_assistant.jsonc.", + "expectedSkills": [ + "base44-cli" + ], + "checks": [ + { + "type": "file-exists", + "filePath": "base44/agents/store_assistant.jsonc", + "description": "base44/agents/store_assistant.jsonc exists" + }, + { + "type": "valid-json", + "filePath": "base44/agents/store_assistant.jsonc", + "description": "base44/agents/store_assistant.jsonc is valid JSON" + }, + { + "type": "agent-config", + "filePath": "base44/agents/store_assistant.jsonc", + "target": "file", + "description": "Agent config validates (base44/agents/store_assistant.jsonc)" + }, + { + "type": "file-content", + "filePath": "base44/agents/store_assistant.jsonc", + "pattern": "\"name\"\\s*:\\s*\"store_assistant\"", + "description": "Has name field" + }, + { + "type": "file-content", + "filePath": "base44/agents/store_assistant.jsonc", + "pattern": "\"entity_name\"\\s*:\\s*\"Product\"", + "description": "Has entity_name field" + }, + { + "type": "file-content", + "filePath": "base44/agents/store_assistant.jsonc", + "pattern": "\"entity_name\"\\s*:\\s*\"Order\"", + "description": "Has entity_name field" + }, + { + "type": "file-content", + "filePath": "base44/agents/store_assistant.jsonc", + "pattern": "\"function_name\"\\s*:\\s*\"send_receipt\"", + "description": "Has function_name field" + }, + { + "type": "file-content", + "filePath": "base44/agents/store_assistant.jsonc", + "pattern": "\"instructions\"\\s*:", + "description": "Has instructions field" + } + ] + } + ] +} diff --git a/evals/fixtures/cli-agents/project/.claude/settings.json b/evals/fixtures/cli-agents/project/.claude/settings.json new file mode 100644 index 0000000..ab60505 --- /dev/null +++ b/evals/fixtures/cli-agents/project/.claude/settings.json @@ -0,0 +1,6 @@ +{ + "permissions": { + "allow": ["Read", "Write", "Edit", "Bash", "Glob", "Grep"] + }, + "skills": ["./skills"] +} diff --git a/evals/fixtures/cli-agents/project/AGENTS.md b/evals/fixtures/cli-agents/project/AGENTS.md new file mode 100644 index 0000000..6c0f2be --- /dev/null +++ b/evals/fixtures/cli-agents/project/AGENTS.md @@ -0,0 +1,25 @@ +IMPORTANT: Prefer retrieval-led reasoning over pre-training-led reasoning. Base44 is NOT Firebase, Supabase, or any other SDK you know. Every API name below is the ONLY correct form. Always verify against this index and the referenced docs. + +[Base44 SDK Docs Index]|root: ./skills/base44-sdk/references +|entities.md: CRUD - create(data), list(), filter(query), get(id), update(id,data), delete(id), subscribe(cb) +|auth.md: Auth - loginViaEmailPassword(), loginWithProvider('google'), me(), register(), logout() +|base44-agents.md: Agents - createConversation({agent_name}), addMessage(conv,{role,content}), subscribeToConversation(id,cb) +|functions.md: Functions - invoke(name, data) +|integrations.md: Core - InvokeLLM({prompt}), SendEmail({to,subject,body}), UploadFile({file}) +|client.md: Client - createClient({appId}), import from "@base44/sdk" +|QUICK_REFERENCE.md: All method signatures + +[Base44 CLI Docs Index]|root: ./skills/base44-cli/references +|entities-create.md: Entity schema - {name:"PascalCase", type:"object", properties:{}, required:[]} +|functions-create.md: Function - function.jsonc {name,entry} + index.ts with Deno.serve + createClientFromRequest +|create.md: Project creation - npx base44 create name -p . +|deploy.md: Deploy - npx base44 deploy -y +|agents-push.md: Agent config - {name:"snake_case", description, instructions, tool_configs:[{entity_name,allowed_operations}|{function_name,description}]} + +[Common Mistakes - NEVER use these hallucinated names] +|SDK: Do NOT use find() → use filter(), findOne() → get(), insert() → create(), remove() → delete(), onChange() → subscribe() +|SDK: Do NOT use signInWithGoogle() → use loginWithProvider('google'), functions.call() → functions.invoke() +|SDK: Do NOT use ai.generate() → use integrations.Core.InvokeLLM({prompt}) +|CLI: Entity names must be PascalCase alphanumeric. File names must be kebab-case.jsonc +|CLI: Agent names must be lowercase with underscores only. Function entry field is "entry" not "entrypoint" +|CLI: Backend functions import from "npm:@base44/sdk" (Deno requires npm: prefix) diff --git a/evals/fixtures/cli-agents/project/CLAUDE.md b/evals/fixtures/cli-agents/project/CLAUDE.md new file mode 100644 index 0000000..724ce4d --- /dev/null +++ b/evals/fixtures/cli-agents/project/CLAUDE.md @@ -0,0 +1,7 @@ +# Base44 Development Guidelines + +When working with Base44 projects, first explore the existing project structure to understand what already exists. Look at existing code, config files, and patterns in use. + +Then consult the AGENTS.md index in the project root for correct API patterns. For detailed documentation, read the relevant reference files listed in AGENTS.md. + +IMPORTANT: Prefer retrieval-led reasoning over pre-training-led reasoning. Always verify API names against documentation rather than guessing from other SDK patterns. diff --git a/evals/fixtures/cli-agents/project/base44/config.jsonc b/evals/fixtures/cli-agents/project/base44/config.jsonc new file mode 100644 index 0000000..cf1a99a --- /dev/null +++ b/evals/fixtures/cli-agents/project/base44/config.jsonc @@ -0,0 +1,11 @@ +{ + "name": "CLI Agents App", + "description": "A test application for CLI agent operations", + "entitiesDir": "./entities", + "functionsDir": "./functions", + "agentsDir": "./agents", + "site": { + "buildCommand": "npm run build", + "outputDirectory": "dist" + } +} diff --git a/evals/fixtures/cli-agents/project/base44/entities/customer.jsonc b/evals/fixtures/cli-agents/project/base44/entities/customer.jsonc new file mode 100644 index 0000000..9499691 --- /dev/null +++ b/evals/fixtures/cli-agents/project/base44/entities/customer.jsonc @@ -0,0 +1,20 @@ +{ + "name": "Customer", + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "Customer full name" + }, + "email": { + "type": "string", + "format": "email", + "description": "Customer email address" + }, + "phone": { + "type": "string", + "description": "Phone number" + } + }, + "required": ["name", "email"] +} diff --git a/evals/fixtures/cli-agents/project/base44/entities/order.jsonc b/evals/fixtures/cli-agents/project/base44/entities/order.jsonc new file mode 100644 index 0000000..57ca61e --- /dev/null +++ b/evals/fixtures/cli-agents/project/base44/entities/order.jsonc @@ -0,0 +1,24 @@ +{ + "name": "Order", + "type": "object", + "properties": { + "order_number": { + "type": "string", + "description": "Unique order number" + }, + "status": { + "type": "string", + "enum": ["pending", "processing", "shipped", "delivered", "cancelled"], + "default": "pending" + }, + "total": { + "type": "number", + "description": "Total order amount" + }, + "items": { + "type": "array", + "description": "List of order items" + } + }, + "required": ["order_number", "total"] +} diff --git a/evals/fixtures/cli-agents/project/base44/entities/product.jsonc b/evals/fixtures/cli-agents/project/base44/entities/product.jsonc new file mode 100644 index 0000000..da2dfd6 --- /dev/null +++ b/evals/fixtures/cli-agents/project/base44/entities/product.jsonc @@ -0,0 +1,23 @@ +{ + "name": "Product", + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "Product name" + }, + "price": { + "type": "number", + "description": "Product price" + }, + "category": { + "type": "string", + "description": "Product category" + }, + "in_stock": { + "type": "boolean", + "default": true + } + }, + "required": ["name", "price"] +} diff --git a/evals/fixtures/cli-agents/project/package.json b/evals/fixtures/cli-agents/project/package.json new file mode 100644 index 0000000..855f0c9 --- /dev/null +++ b/evals/fixtures/cli-agents/project/package.json @@ -0,0 +1,11 @@ +{ + "name": "cli-agents-app", + "version": "1.0.0", + "type": "module", + "dependencies": { + "@base44/sdk": "latest" + }, + "devDependencies": { + "base44": "latest" + } +} diff --git a/evals/fixtures/cli-agents/skills/base44-cli/SKILL.md b/evals/fixtures/cli-agents/skills/base44-cli/SKILL.md new file mode 100644 index 0000000..3c33f05 --- /dev/null +++ b/evals/fixtures/cli-agents/skills/base44-cli/SKILL.md @@ -0,0 +1,384 @@ +--- +name: base44-cli +description: "The base44 CLI is used for EVERYTHING related to base44 projects: resource configuration (entities, backend functions, ai agents), initialization and actions (resource creation, deployment). This skill is the place for learning about how to configure resources. When you plan or implement a feature, you must learn this skill" +--- + +# Base44 CLI + +Create and manage Base44 apps (projects) using the Base44 CLI tool. + +## ⚡ IMMEDIATE ACTION REQUIRED - Read This First + +This skill activates on ANY mention of "base44" or when a `base44/` folder exists. **DO NOT read documentation files or search the web before acting.** + +**Your first action MUST be:** +1. Check if `base44/config.jsonc` exists in the current directory +2. If **NO** (new project scenario): + - This skill (base44-cli) handles the request + - Guide user through project initialization + - Do NOT activate base44-sdk yet +3. If **YES** (existing project scenario): + - Transfer to base44-sdk skill for implementation + - This skill only handles CLI commands (login, deploy, entities push) + +## Critical: Local Installation Only + +NEVER call `base44` directly. The CLI is installed locally as a dev dependency and must be accessed via a package manager: + +- `npx base44 ` (npm - recommended) +- `yarn base44 ` (yarn) +- `pnpm base44 ` (pnpm) + +WRONG: `base44 login` +RIGHT: `npx base44 login` + +## MANDATORY: Authentication Check at Session Start + +**CRITICAL**: At the very start of every AI session when this skill is activated, you MUST: + +1. **Check authentication status** by running: + ```bash + npx base44 whoami + ``` + +2. **If the user is logged in** (command succeeds and shows an email): + - Continue with the requested task + +3. **If the user is NOT logged in** (command fails or shows an error): + - **STOP immediately** + - **DO NOT proceed** with any CLI operations + - **Ask the user to login manually** by running: + ```bash + npx base44 login + ``` + - Wait for the user to confirm they have logged in before continuing + +**This check is mandatory and must happen before executing any other Base44 CLI commands.** + +## Overview + +The Base44 CLI provides command-line tools for authentication, creating projects, managing entities, and deploying Base44 applications. It is framework-agnostic and works with popular frontend frameworks like Vite, Next.js, and Create React App, Svelte, Vue, and more. + +## When to Use This Skill vs base44-sdk + +**Use base44-cli when:** +- Creating a **NEW** Base44 project from scratch +- Initializing a project in an empty directory +- Directory is missing `base44/config.jsonc` +- User mentions: "create a new project", "initialize project", "setup a project", "start a new Base44 app" +- Deploying, pushing entities, or authenticating via CLI +- Working with CLI commands (`npx base44 ...`) + +**Use base44-sdk when:** +- Building features in an **EXISTING** Base44 project +- `base44/config.jsonc` already exists +- Writing JavaScript/TypeScript code using Base44 SDK +- Implementing functionality, components, or features +- User mentions: "implement", "build a feature", "add functionality", "write code" + +**Skill Dependencies:** +- `base44-cli` is a **prerequisite** for `base44-sdk` in new projects +- If user wants to "create an app" and no Base44 project exists, use `base44-cli` first +- `base44-sdk` assumes a Base44 project is already initialized + +**State Check Logic:** +Before selecting a skill, check: +- IF (user mentions "create/build app" OR "make a project"): + - IF (directory is empty OR no `base44/config.jsonc` exists): + → Use **base44-cli** (project initialization needed) + - ELSE: + → Use **base44-sdk** (project exists, build features) + +## Project Structure + +A Base44 project combines a standard frontend project with a `base44/` configuration folder: + +``` +my-app/ +├── base44/ # Base44 configuration (created by CLI) +│ ├── config.jsonc # Project settings, site config +│ ├── entities/ # Entity schema definitions +│ │ ├── task.jsonc +│ │ └── board.jsonc +│ ├── functions/ # Backend functions (optional) +│ │ └── my-function/ +│ │ ├── function.jsonc +│ │ └── index.ts +│ └── agents/ # Agent configurations (optional) +│ └── support_agent.jsonc +├── src/ # Frontend source code +│ ├── api/ +│ │ └── base44Client.js # Base44 SDK client +│ ├── pages/ +│ ├── components/ +│ └── main.jsx +├── index.html # SPA entry point +├── package.json +└── vite.config.js # Or your framework's config +``` + +**Key files:** +- `base44/config.jsonc` - Project name, description, site build settings +- `base44/entities/*.jsonc` - Data model schemas (see Entity Schema section) +- `base44/agents/*.jsonc` - Agent configurations (optional) +- `src/api/base44Client.js` - Pre-configured SDK client for frontend use + +**config.jsonc example:** +```jsonc +{ + "name": "My App", // Required: project name + "description": "App description", // Optional: project description + "entitiesDir": "./entities", // Optional: default "entities" + "functionsDir": "./functions", // Optional: default "functions" + "agentsDir": "./agents", // Optional: default "agents" + "site": { // Optional: site deployment config + "installCommand": "npm install", // Optional: install dependencies + "buildCommand": "npm run build", // Optional: build command + "serveCommand": "npm run dev", // Optional: local dev server + "outputDirectory": "./dist" // Optional: build output directory + } +} +``` + +**Config properties:** + +| Property | Description | Default | +|----------|-------------|---------| +| `name` | Project name (required) | - | +| `description` | Project description | - | +| `entitiesDir` | Directory for entity schemas | `"entities"` | +| `functionsDir` | Directory for backend functions | `"functions"` | +| `agentsDir` | Directory for agent configs | `"agents"` | +| `site.installCommand` | Command to install dependencies | - | +| `site.buildCommand` | Command to build the project | - | +| `site.serveCommand` | Command to run dev server | - | +| `site.outputDirectory` | Build output directory for deployment | - | + +## Installation + +Install the Base44 CLI as a dev dependency in your project: + +```bash +npm install --save-dev base44 +``` + +**Important:** Never assume or hardcode the `base44` package version. Always install without a version specifier to get the latest version. + +Then run commands using `npx`: + +```bash +npx base44 +``` + +**Note:** All commands in this documentation use `npx base44`. You can also use `yarn base44`, or `pnpm base44` if preferred. + +## Available Commands + +### Authentication + +| Command | Description | Reference | +| --------------- | ----------------------------------------------- | ------------------------------------------- | +| `base44 login` | Authenticate with Base44 using device code flow | [auth-login.md](references/auth-login.md) | +| `base44 logout` | Logout from current device | [auth-logout.md](references/auth-logout.md) | +| `base44 whoami` | Display current authenticated user | [auth-whoami.md](references/auth-whoami.md) | + +### Project Management + +| Command | Description | Reference | +|---------|-------------|-----------| +| `base44 create` | Create a new Base44 project from a template | [create.md](references/create.md) ⚠️ **MUST READ** | +| `base44 link` | Link an existing local project to Base44 | [link.md](references/link.md) | +| `base44 dashboard` | Open the app dashboard in your browser | [dashboard.md](references/dashboard.md) | + +### Deployment + +| Command | Description | Reference | +|---------|-------------|-----------| +| `base44 deploy` | Deploy all resources (entities, functions, site) | [deploy.md](references/deploy.md) | + +### Entity Management + +| Action / Command | Description | Reference | +| ---------------------- | ------------------------------------------- | --------------------------------------------------- | +| Create Entities | Define entities in `base44/entities` folder | [entities-create.md](references/entities-create.md) | +| `base44 entities push` | Push local entities to Base44 | [entities-push.md](references/entities-push.md) | + +#### Entity Schema (Quick Reference) + +ALWAYS follow this exact structure when creating entity files: + +**File naming:** `base44/entities/{kebab-case-name}.jsonc` (e.g., `team-member.jsonc` for `TeamMember`) + +**Schema template:** +```jsonc +{ + "name": "EntityName", + "type": "object", + "properties": { + "field_name": { + "type": "string", + "description": "Field description" + } + }, + "required": ["field_name"] +} +``` + +**Field types:** `string`, `number`, `integer`, `boolean`, `array`, `object`, `binary` +**String formats:** `date`, `date-time`, `time`, `email`, `uri`, `hostname`, `ipv4`, `ipv6`, `uuid`, `file`, `regex`, `richtext` +**For enums:** Add `"enum": ["value1", "value2"]` and optionally `"default": "value1"` +**Entity names:** Must be alphanumeric only (pattern: `/^[a-zA-Z0-9]+$/`) + +For complete documentation, see [entities-create.md](references/entities-create.md). + +### Function Management + +| Action / Command | Description | Reference | +| ------------------------- | --------------------------------------------- | ------------------------------------------------------- | +| Create Functions | Define functions in `base44/functions` folder | [functions-create.md](references/functions-create.md) | +| `base44 functions deploy` | Deploy local functions to Base44 | [functions-deploy.md](references/functions-deploy.md) | + +### Agent Management + +Agents are conversational AI assistants that can interact with users, access your app's entities, and call backend functions. Use these commands to manage agent configurations. + +| Action / Command | Description | Reference | +| ----------------------- | --------------------------------------- | ----------------------------------------------- | +| Create Agents | Define agents in `base44/agents` folder | See Agent Schema below | +| `base44 agents pull` | Pull remote agents to local files | [agents-pull.md](references/agents-pull.md) | +| `base44 agents push` | Push local agents to Base44 | [agents-push.md](references/agents-push.md) | + +**Note:** Agent commands perform full synchronization - pushing replaces all remote agents with local ones, and pulling replaces all local agents with remote ones. + +#### Agent Schema (Quick Reference) + +**File naming:** `base44/agents/{agent_name}.jsonc` (e.g., `support_agent.jsonc`) + +**Schema template:** +```jsonc +{ + "name": "agent_name", + "description": "Brief description of what this agent does", + "instructions": "Detailed instructions for the agent's behavior", + "tool_configs": [ + // Entity tool - gives agent access to entity operations + { "entity_name": "tasks", "allowed_operations": ["read", "create", "update", "delete"] }, + // Backend function tool - gives agent access to a function + { "function_name": "send_email", "description": "Send an email notification" } + ], + "whatsapp_greeting": "Hello! How can I help you today?" +} +``` + +**Naming rules:** +- Agent names must match pattern: `/^[a-z0-9_]+$/` (lowercase alphanumeric with underscores, 1-100 chars) +- Valid: `support_agent`, `order_bot` +- Invalid: `Support-Agent`, `OrderBot` + +**Required fields:** `name`, `description`, `instructions` +**Optional fields:** `tool_configs` (defaults to `[]`), `whatsapp_greeting` + +**Tool config types:** +- **Entity tools**: `entity_name` + `allowed_operations` (array of: `read`, `create`, `update`, `delete`) +- **Backend function tools**: `function_name` + `description` + +### Site Deployment + +| Command | Description | Reference | +| -------------------- | ----------------------------------------- | ------------------------------------------- | +| `base44 site deploy` | Deploy built site files to Base44 hosting | [site-deploy.md](references/site-deploy.md) | + +**SPA only**: Base44 hosting supports Single Page Applications with a single `index.html` entry point. All routes are served from `index.html` (client-side routing). + +## Quick Start + +1. Install the CLI in your project: + ```bash + npm install --save-dev base44 + ``` + +2. Authenticate with Base44: + ```bash + npx base44 login + ``` + +3. Create a new project (ALWAYS provide name and `--path` flag): + ```bash + npx base44 create my-app -p . + ``` + +4. Build and deploy everything: + ```bash + npm run build + npx base44 deploy -y + ``` + +Or deploy individual resources: +- `npx base44 entities push` - Push entities only +- `npx base44 functions deploy` - Deploy functions only +- `npx base44 agents push` - Push agents only +- `npx base44 site deploy -y` - Deploy site only + +## Common Workflows + +### Creating a New Project + +**⚠️ MANDATORY: Before running `base44 create`, you MUST read [create.md](references/create.md) for:** +- **Template selection** - Choose the correct template (`backend-and-client` vs `backend-only`) +- **Correct workflow** - Different templates require different setup steps +- **Common pitfalls** - Avoid folder creation errors that cause failures + +Failure to follow the create.md instructions will result in broken project scaffolding. + +### Linking an Existing Project +```bash +# If you have base44/config.jsonc but no .app.jsonc +npx base44 link --create --name my-app +``` + +### Deploying All Changes +```bash +# Build your project first +npm run build + +# Deploy everything (entities, functions, and site) +npx base44 deploy -y +``` + +### Deploying Individual Resources +```bash +# Push only entities +npx base44 entities push + +# Deploy only functions +npx base44 functions deploy + +# Push only agents +npx base44 agents push + +# Deploy only site +npx base44 site deploy -y +``` + +### Opening the Dashboard +```bash +# Open app dashboard in browser +npx base44 dashboard +``` + +## Authentication + +Most commands require authentication. If you're not logged in, the CLI will automatically prompt you to login. Your session is stored locally and persists across CLI sessions. + +## Troubleshooting + +| Error | Solution | +| --------------------------- | ----------------------------------------------------------------------------------- | +| Not authenticated | Run `npx base44 login` first | +| No entities found | Ensure entities exist in `base44/entities/` directory | +| Entity not recognized | Ensure file uses kebab-case naming (e.g., `team-member.jsonc` not `TeamMember.jsonc`) | +| No functions found | Ensure functions exist in `base44/functions/` with valid `function.jsonc` configs | +| No agents found | Ensure agents exist in `base44/agents/` directory with valid `.jsonc` configs | +| Invalid agent name | Agent names must be lowercase alphanumeric with underscores only | +| No site configuration found | Check that `site.outputDirectory` is configured in project config | +| Site deployment fails | Ensure you ran `npm run build` first and the build succeeded | diff --git a/evals/fixtures/cli-agents/skills/base44-cli/references/agents-pull.md b/evals/fixtures/cli-agents/skills/base44-cli/references/agents-pull.md new file mode 100644 index 0000000..6a700f0 --- /dev/null +++ b/evals/fixtures/cli-agents/skills/base44-cli/references/agents-pull.md @@ -0,0 +1,73 @@ +# base44 agents pull + +Pull AI agent configurations from Base44 to local files. Agents are conversational AI assistants that can interact with users, access your app's entities, and call backend functions. + +## Syntax + +```bash +npx base44 agents pull +``` + +## Authentication + +**Required**: Yes. If not authenticated, you'll be prompted to login first. + +## What It Does + +1. Fetches all agents from Base44 +2. Writes agent files to the `base44/agents/` directory +3. Deletes local agent files that don't exist remotely +4. Reports written and deleted agents + +## Prerequisites + +- Must be run from a Base44 project directory +- Project must be linked to a Base44 app + +## Output + +```bash +$ npx base44 agents pull + +Fetching agents from Base44... +✓ Agents fetched successfully + +Writing agent files... +✓ Agent files written successfully + +Written: support_agent, order_bot +Deleted: old_agent + +Pulled 2 agents to base44/agents +``` + +## Agent Synchronization + +The pull operation synchronizes remote agents to your local files: + +- **Written**: Agent files created or updated from remote +- **Deleted**: Local agent files removed (didn't exist remotely) + +**Warning**: This operation replaces all local agent configurations with remote versions. Any local changes not pushed to Base44 will be overwritten. + +## Error Handling + +If no agents exist on Base44: +```bash +$ npx base44 agents pull +No agents found on Base44 +``` + +## Use Cases + +- Sync agent configurations to a new development machine +- Get the latest agent configurations from your team +- Restore local agent files after accidental deletion +- Start working on an existing project with agents + +## Notes + +- This command syncs agent configurations, not conversation data +- Agent files are stored as `.jsonc` in the `base44/agents/` directory +- The directory location is configurable via `agentsDir` in `config.jsonc` +- Use `base44 agents push` to upload local changes to Base44 diff --git a/evals/fixtures/cli-agents/skills/base44-cli/references/agents-push.md b/evals/fixtures/cli-agents/skills/base44-cli/references/agents-push.md new file mode 100644 index 0000000..fa2795b --- /dev/null +++ b/evals/fixtures/cli-agents/skills/base44-cli/references/agents-push.md @@ -0,0 +1,156 @@ +# base44 agents push + +Push local AI agent configurations to Base44. Agents are conversational AI assistants that can interact with users, access your app's entities, and call backend functions. + +## Syntax + +```bash +npx base44 agents push +``` + +## Authentication + +**Required**: Yes. If not authenticated, you'll be prompted to login first. + +## What It Does + +1. Reads all agent files from the `base44/agents/` directory +2. Validates agent configurations +3. Displays the count of agents to be pushed +4. Uploads agents to the Base44 backend +5. Reports the results: created, updated, and deleted agents + +## Prerequisites + +- Must be run from a Base44 project directory +- Project must have agent definitions in the `base44/agents/` folder + +## Output + +```bash +$ npx base44 agents push + +Found 2 agents to push +Pushing agents to Base44... + +Created: support_agent +Updated: order_bot +Deleted: old_agent + +✓ Agents pushed to Base44 +``` + +## Agent Synchronization + +The push operation synchronizes your local agents with Base44: + +- **Created**: New agents that didn't exist in Base44 +- **Updated**: Existing agents with modified configuration +- **Deleted**: Agents that were removed from your local configuration + +**Warning**: This is a full sync operation. Agents removed locally will be deleted from Base44. + +## Error Handling + +If no agents are found in your project: +```bash +$ npx base44 agents push +No local agents found - this will delete all remote agents +``` + +If an agent has an invalid name: +```bash +$ npx base44 agents push +Error: Agent name must be lowercase alphanumeric with underscores +``` + +## Agent Configuration Schema + +Each agent file should be a `.jsonc` file in `base44/agents/` with this structure: + +```jsonc +{ + "name": "agent_name", // Required: lowercase alphanumeric with underscores, 1-100 chars + "description": "Brief description of what this agent does", // Required: min 1 char + "instructions": "Detailed instructions for the agent's behavior", // Required: min 1 char + "tool_configs": [ // Optional: defaults to [] + // Entity tool - gives agent access to entity operations + { "entity_name": "Task", "allowed_operations": ["read", "create", "update", "delete"] }, + // Backend function tool - gives agent access to a function + { "function_name": "send_email", "description": "Send an email notification" } + ], + "whatsapp_greeting": "Hello! How can I help you today?" // Optional +} +``` + +**Naming rules:** +- **Agent names** must match pattern: `/^[a-z0-9_]+$/` (lowercase alphanumeric with underscores only, 1-100 characters) + - Valid: `support_agent`, `order_bot`, `task_helper` + - Invalid: `Support-Agent`, `OrderBot`, `task helper` +- **Agent file names** must use underscores (matching the agent name) + - Valid: `support_agent.jsonc`, `order_bot.jsonc` + - Invalid: `support-agent.jsonc` (hyphens not allowed) +- **Entity names in `tool_configs`** must use PascalCase (matching the entity's `name` field) + - Valid: `"entity_name": "Task"`, `"entity_name": "TeamMember"` + - Invalid: `"entity_name": "task"`, `"entity_name": "team_member"` + +**Required fields:** +- `name`: Required, must follow naming rules above +- `description`: Required, minimum 1 character +- `instructions`: Required, minimum 1 character +- `tool_configs`: Optional, defaults to empty array +- `whatsapp_greeting`: Optional + +### Common Mistake: Wrong tool_configs Format + +**WRONG** - Do NOT use `tools` with `type` and `entity`: +```jsonc +{ + "name": "my_agent", + "tools": [ // ❌ WRONG + { "type": "entity_query", "entity": "Task" } + ] +} +``` + +**CORRECT** - Use `tool_configs` with `entity_name` and `allowed_operations`: +```jsonc +{ + "name": "my_agent", + "tool_configs": [ // ✅ CORRECT + { "entity_name": "Task", "allowed_operations": ["read"] } + ] +} +``` + +### Best Practices for Agent Instructions + +When giving agents access to entities, be explicit in the instructions about using the tools: + +```jsonc +{ + "name": "support_agent", + "instructions": "You are a helpful support agent.\n\nIMPORTANT: You have access to customer data through entity tools. When users ask about their orders or account:\n1. ALWAYS use the Order entity tool to query their order history\n2. Use the Customer entity tool to look up account details\n3. Analyze the data and provide personalized responses\n\nAlways query the relevant entities first before answering questions about user data.", + "tool_configs": [ + { "entity_name": "Order", "allowed_operations": ["read"] }, + { "entity_name": "Customer", "allowed_operations": ["read"] } + ] +} +``` + +Without explicit instructions to use the entity tools, the agent may not proactively query user data when asked. + +## Use Cases + +- After defining new agents in your project +- When modifying existing agent configurations +- To sync agent changes before testing +- As part of your development workflow when agent behavior changes + +## Notes + +- This command syncs the agent configuration, not conversation data +- Changes are applied to your Base44 project immediately +- Make sure to test agent changes in a development environment first +- Agent definitions are located in the `base44/agents/` directory +- Use `base44 agents pull` to download agents from Base44 diff --git a/evals/fixtures/cli-agents/skills/base44-cli/references/auth-login.md b/evals/fixtures/cli-agents/skills/base44-cli/references/auth-login.md new file mode 100644 index 0000000..adebd27 --- /dev/null +++ b/evals/fixtures/cli-agents/skills/base44-cli/references/auth-login.md @@ -0,0 +1,54 @@ +# base44 login + +Authenticate with Base44 using device code flow. + +## Syntax + +```bash +npx base44 login +``` + +## Authentication + +**Required**: No (this is the login command itself) + +## How It Works + +The login command uses OAuth 2.0 device code flow for authentication: + +1. Generates a device code for authentication +2. Displays a verification code and verification URI +3. Directs you to visit the URI and enter the code +4. Polls for authentication completion (up to device code expiration) +5. Retrieves access and refresh tokens upon successful authentication +6. Fetches and displays your user information +7. Saves authentication data locally with expiration timestamp + +## Interactive Flow + +```bash +$ npx base44 login + +Please visit: https://auth.base44.com/device +Enter code: ABCD-EFGH + +Waiting for authentication... +✓ Successfully authenticated! + +Logged in as: user@example.com +``` + +## Session Management + +- Authentication tokens are stored locally on your device +- Tokens include expiration timestamps +- The session persists across CLI sessions +- Other commands will automatically use your stored credentials +- Use `npx base44 logout` to clear your session +- Use `npx base44 whoami` to check your current authentication status + +## Notes + +- You only need to login once per device +- If your session expires, you'll be prompted to login again when running authenticated commands +- The CLI automatically prompts for login when you run commands that require authentication diff --git a/evals/fixtures/cli-agents/skills/base44-cli/references/auth-logout.md b/evals/fixtures/cli-agents/skills/base44-cli/references/auth-logout.md new file mode 100644 index 0000000..33c2ddf --- /dev/null +++ b/evals/fixtures/cli-agents/skills/base44-cli/references/auth-logout.md @@ -0,0 +1,32 @@ +# base44 logout + +Logout from current device and clear stored authentication data. + +## Syntax + +```bash +npx base44 logout +``` + +## Authentication + +**Required**: No + +## What It Does + +- Deletes stored authentication data from your device +- Clears your local session +- Removes access and refresh tokens + +## Output + +```bash +$ npx base44 logout +Logged out successfully +``` + +## Notes + +- You can logout even if you're not currently logged in (no error) +- After logout, you'll need to run `npx base44 login` again to use authenticated commands +- This only affects the current device; your Base44 account remains active diff --git a/evals/fixtures/cli-agents/skills/base44-cli/references/auth-whoami.md b/evals/fixtures/cli-agents/skills/base44-cli/references/auth-whoami.md new file mode 100644 index 0000000..abe450c --- /dev/null +++ b/evals/fixtures/cli-agents/skills/base44-cli/references/auth-whoami.md @@ -0,0 +1,37 @@ +# base44 whoami + +Display the currently authenticated user. + +## Syntax + +```bash +npx base44 whoami +``` + +## Authentication + +**Required**: Yes. If not authenticated, you'll be prompted to login first. + +## What It Does + +- Reads stored authentication data +- Displays the email of the currently logged-in user + +## Output + +```bash +$ npx base44 whoami +Logged in as: user@example.com +``` + +## Use Cases + +- Verify you're logged in before running other commands +- Check which account you're currently using +- Confirm authentication is working properly +- Useful in scripts or automation to verify credentials + +## Notes + +- If you're not logged in, the command will prompt you to authenticate first +- The email displayed matches your Base44 account email diff --git a/evals/fixtures/cli-agents/skills/base44-cli/references/create.md b/evals/fixtures/cli-agents/skills/base44-cli/references/create.md new file mode 100644 index 0000000..7580645 --- /dev/null +++ b/evals/fixtures/cli-agents/skills/base44-cli/references/create.md @@ -0,0 +1,111 @@ +# base44 create + +Creates a new Base44 project from a template. This command is framework-agnostic and can either scaffold a complete project or add Base44 configuration to an existing project. + +## Critical: Non-Interactive Mode Required + +ALWAYS provide both the project name AND `--path` flag. Without both, the command opens an interactive TUI which agents cannot use properly. + +WRONG: `npx base44 create` +WRONG: `npx base44 create my-app` +RIGHT: `npx base44 create my-app -p ./my-app` + +## Syntax + +```bash +npx base44 create [name] --path [options] +``` + +## Arguments & Options + +| Argument/Option | Description | Required | +|--------|-------------|----------| +| `name` | Project name (positional argument) | Yes* | +| `-p, --path ` | Path where to create the project | Yes* | +| `-t, --template ` | Template ID (see templates below) | No | +| `--deploy` | Build and deploy the site (includes pushing entities) | No | +| `--no-skills` | Skip AI agent skills installation (skills are added by default) | No | + +*Required for non-interactive mode. Both `name` and `--path` must be provided together. + +## Template Selection (CRITICAL - Choose Appropriately) + +**You MUST select the most appropriate template based on user requirements:** + +| Template ID | When to Use | Example Scenarios | +|-------------|-------------|-------------------| +| `backend-and-client` | Creating a NEW full-stack web app from scratch | "Create a task app", "Build me a dashboard", "Make a SaaS app" | +| `backend-only` | Adding Base44 to an EXISTING project OR using a different framework (Next.js, Vue, Svelte, etc.) | "Add Base44 to my project", "I want to use Next.js", "I already have a frontend" | + +**Default Choice:** When the user asks to "create an app" or "build a project" without specifying a particular framework, use `backend-and-client` to provide a complete, production-ready application with Vite + React + Tailwind. + +## The `--path` Flag + +- **For `backend-and-client` template (new projects):** Use a new subfolder path + ```bash + npx base44 create my-app -p ./my-app -t backend-and-client + ``` +- **For `backend-only` template (existing projects):** Use `-p .` in the current directory + ```bash + npx base44 create my-app -p . + ``` + +## Workflow: Using `backend-only` with External Frameworks + +**CRITICAL: The project folder MUST exist BEFORE running `base44 create` with `backend-only`** + +The `backend-only` template only adds Base44 configuration files - it does NOT create a frontend. If you need a frontend with a specific framework: + +```bash +# Step 1: Initialize the frontend project FIRST +npm create vite@latest my-app -- --template react # or vue, svelte, etc. +# OR: npx create-next-app@latest my-app +# OR: any other framework's init command + +# Step 2: Navigate into the created folder +cd my-app + +# Step 3: Install Base44 CLI +npm install --save-dev base44 + +# Step 4: Add Base44 configuration +npx base44 create my-app -p . +``` + +**WARNING:** Do NOT: +- Create an empty folder manually, then try to run `npx create vite` inside it (will fail - folder exists) +- Run `base44 create` with `backend-only` expecting it to create a frontend (it won't) + +**DO:** +- Run the external framework's init command FIRST (it creates its own folder) +- Then run `base44 create` inside that folder with `-p .` + +## Examples + +```bash +# RECOMMENDED: Create full-stack project (for new apps) +npx base44 create my-app -p ./my-app -t backend-and-client + +# Create full-stack and deploy in one step +npx base44 create my-app -p ./my-app -t backend-and-client --deploy + +# Add Base44 to EXISTING project (must be inside the project folder) +npx base44 create my-app -p . + +# Add Base44 to existing project and deploy +npx base44 create my-app -p . --deploy + +# Create without adding AI agent skills +npx base44 create my-app -p . --no-skills +``` + +## What It Does + +1. Applies the selected template to the target path +2. Creates a `base44/` folder with configuration files +3. Registers the project with Base44 backend +4. Creates `base44/.app.jsonc` with the app ID +5. If `--deploy` is used: + - Pushes any entities defined in `base44/entities/` + - Runs install and build commands (for templates with frontend) + - Deploys the site to Base44 hosting diff --git a/evals/fixtures/cli-agents/skills/base44-cli/references/dashboard.md b/evals/fixtures/cli-agents/skills/base44-cli/references/dashboard.md new file mode 100644 index 0000000..95a534a --- /dev/null +++ b/evals/fixtures/cli-agents/skills/base44-cli/references/dashboard.md @@ -0,0 +1,35 @@ +# base44 dashboard + +Opens the Base44 app dashboard in your default web browser. + +## Syntax + +```bash +npx base44 dashboard +``` + +## Options + +This command has no options. + +## What It Does + +1. Reads the project's app ID from `base44/.app.jsonc` +2. Opens the dashboard URL in your default browser + +## Example + +```bash +# Open dashboard for current project +npx base44 dashboard +``` + +## Requirements + +- Must be run from a linked Base44 project directory (contains `base44/.app.jsonc`) +- Must be authenticated (run `npx base44 login` first) + +## Notes + +- The dashboard provides a web interface to manage your app's entities, functions, users, and settings +- If you're not in a project directory, the command will fail with an error diff --git a/evals/fixtures/cli-agents/skills/base44-cli/references/deploy.md b/evals/fixtures/cli-agents/skills/base44-cli/references/deploy.md new file mode 100644 index 0000000..500526f --- /dev/null +++ b/evals/fixtures/cli-agents/skills/base44-cli/references/deploy.md @@ -0,0 +1,86 @@ +# base44 deploy + +Deploys all project resources (entities, functions, and site) to Base44 in a single command. + +## Syntax + +```bash +npx base44 deploy [options] +``` + +## Options + +| Option | Description | +|--------|-------------| +| `-y, --yes` | Skip confirmation prompt | + +## What It Deploys + +The command automatically detects and deploys: + +1. **Entities** - All `.jsonc` files in `base44/entities/` +2. **Functions** - All functions in `base44/functions/` +3. **Agents** - All agent configurations in `base44/agents/` +4. **Site** - Built files from `site.outputDirectory` (if configured) + +## Examples + +```bash +# Interactive mode - shows what will be deployed and asks for confirmation +npx base44 deploy + +# Non-interactive - skip confirmation (for CI/CD or agent use) +npx base44 deploy -y +``` + +## Typical Workflow + +```bash +# 1. Make your changes (entities, functions, frontend code) + +# 2. Build the frontend (if you have one) +npm run build + +# 3. Deploy everything +npx base44 deploy -y +``` + +## What It Does + +1. Reads project configuration from `base44/config.jsonc` +2. Detects available resources (entities, functions, site) +3. Shows a summary of what will be deployed +4. Asks for confirmation (unless `-y` flag is used) +5. Deploys all resources in sequence: + - Pushes entity schemas + - Deploys functions + - Pushes agent configurations + - Uploads site files +6. Displays the dashboard URL and app URL (if site was deployed) + +## Requirements + +- Must be run from a linked Base44 project directory +- Must be authenticated (run `npx base44 login` first) +- For site deployment, must run `npm run build` first + +## Output + +After successful deployment: +- **Dashboard**: Link to your app's management dashboard +- **App URL**: Your deployed site's public URL (if site was included) + +## Notes + +- If no resources are found, the command exits with a message +- Use individual commands (`entities push`, `functions deploy`, `site deploy`) if you only want to deploy specific resources +- The site must be built before deployment - this command does not run `npm run build` for you + +## Related Commands + +| Command | Description | +|---------|-------------| +| `base44 entities push` | Push only entities | +| `base44 functions deploy` | Deploy only functions | +| `base44 agents push` | Push only agents | +| `base44 site deploy` | Deploy only the site | diff --git a/evals/fixtures/cli-agents/skills/base44-cli/references/entities-create.md b/evals/fixtures/cli-agents/skills/base44-cli/references/entities-create.md new file mode 100644 index 0000000..29b40aa --- /dev/null +++ b/evals/fixtures/cli-agents/skills/base44-cli/references/entities-create.md @@ -0,0 +1,552 @@ +# Creating Entities + +Base44 entities are defined locally in your project and then pushed to the Base44 backend. + +## Critical: File Naming + +Entity files MUST use kebab-case naming: `{kebab-case-name}.jsonc` + +| Entity Name | File Name | +|-------------|-----------| +| `Task` | `task.jsonc` | +| `TeamMember` | `team-member.jsonc` | +| `ActivityLog` | `activity-log.jsonc` | + +WRONG: `TeamMember.jsonc`, `teamMember.jsonc` +RIGHT: `team-member.jsonc` + +## Table of Contents + +- [Creating Entities](#creating-entities) + - [Entity Directory](#entity-directory) + - [How to Create an Entity](#how-to-create-an-entity) + - [Entity Schema Structure](#entity-schema-structure) + - [Supported Field Types](#supported-field-types) + - [Field Properties](#field-properties) + - [Complete Example](#complete-example) + - [Naming Conventions](#naming-conventions) + - [Relationships Between Entities](#relationships-between-entities) + - [Row Level Security (RLS)](#row-level-security-rls) + - [Field Level Security (FLS)](#field-level-security-fls) + - [Pushing Entities](#pushing-entities) + +## Entity Directory + +All entity definitions must be placed in the `base44/entities/` folder in your project root. Each entity is defined in its own `.jsonc` file. + +Example structure: +``` +my-app/ + base44/ + entities/ + user.jsonc + product.jsonc + order.jsonc +``` + +## How to Create an Entity + +1. Create a new `.jsonc` file in the `base44/entities/` directory +2. Define your entity schema following the structure below +3. Push the changes to Base44 using the CLI + +## Entity Schema Structure + +Each entity file follows a JSON Schema-like structure: + +```jsonc +{ + "name": "EntityName", // PascalCase entity name + "type": "object", // Always "object" + "properties": { + // Define your fields here + }, + "required": ["field1"] // Array of required field names +} +``` + +### Common Mistake: Nested Schema Property + +**WRONG** - Do NOT wrap properties in a `schema` object: +```jsonc +{ + "name": "Task", + "description": "A task entity", + "schema": { // ❌ WRONG - don't use nested "schema" + "type": "object", + "properties": { ... } + } +} +``` + +**CORRECT** - Put `type` and `properties` at the top level: +```jsonc +{ + "name": "Task", + "description": "A task entity", + "type": "object", // ✅ CORRECT - top level + "properties": { ... } // ✅ CORRECT - top level +} +``` + +This is a common mistake that will cause "Invalid schema: Schema must have a 'type' field" errors when pushing entities. + +## Supported Field Types + +### String + +Basic text field: +```jsonc +{ + "title": { + "type": "string", + "description": "Task title" + } +} +``` + +With format: +```jsonc +{ + "due_date": { + "type": "string", + "format": "date", + "description": "Due date" + } +} +``` + +Available formats: `date`, `date-time`, `time`, `email`, `uri`, `hostname`, `ipv4`, `ipv6`, `uuid`, `file`, `regex`, `richtext` + +### String with Enum + +Constrained to specific values: +```jsonc +{ + "status": { + "type": "string", + "enum": ["todo", "in_progress", "done"], + "default": "todo", + "description": "Current status" + } +} +``` + +### Number + +```jsonc +{ + "position": { + "type": "number", + "description": "Position for ordering" + } +} +``` + +### Integer + +For whole numbers only: +```jsonc +{ + "quantity": { + "type": "integer", + "description": "Item quantity", + "minimum": 0, + "maximum": 1000 + } +} +``` + +### Binary + +For file/blob data: +```jsonc +{ + "attachment": { + "type": "binary", + "description": "File attachment" + } +} +``` + +### Boolean + +```jsonc +{ + "notify_on_change": { + "type": "boolean", + "default": true, + "description": "Enable notifications" + } +} +``` + +### Array of Strings + +```jsonc +{ + "labels": { + "type": "array", + "items": { "type": "string" }, + "description": "Task labels/tags" + } +} +``` + +### Array of Objects + +```jsonc +{ + "attachments": { + "type": "array", + "description": "File attachments", + "items": { + "type": "object", + "properties": { + "name": { "type": "string" }, + "url": { "type": "string" }, + "type": { "type": "string" } + } + } + } +} +``` + +## Field Properties + +| Property | Description | +| ------------- | ---------------------------------------------------------------------------------------- | +| `type` | Data type: `string`, `number`, `integer`, `boolean`, `array`, `object`, `binary` | +| `description` | Human-readable description of the field | +| `enum` | Array of allowed values (for strings) | +| `enumNames` | Human-readable labels for enum values (same order as `enum`) | +| `default` | Default value when not provided | +| `format` | Format hint: `date`, `date-time`, `time`, `email`, `uri`, `hostname`, `ipv4`, `ipv6`, `uuid`, `file`, `regex`, `richtext` | +| `items` | Schema for array items | +| `properties` | Nested properties for object types | +| `$ref` | Reference to another schema definition | +| `minLength` | Minimum string length | +| `maxLength` | Maximum string length | +| `pattern` | Regex pattern for string validation | +| `minimum` | Minimum value for numbers | +| `maximum` | Maximum value for numbers | +| `rls` | Field-level security rules (see Field Level Security section) | + +## Complete Example + +Here's a complete entity definition for a Task: + +```jsonc +{ + "name": "Task", + "type": "object", + "properties": { + "title": { + "type": "string", + "description": "Task title" + }, + "description": { + "type": "string", + "description": "Task description" + }, + "status": { + "type": "string", + "enum": ["todo", "in_progress", "done"], + "default": "todo", + "description": "Current status of the task" + }, + "board_id": { + "type": "string", + "description": "Board this task belongs to" + }, + "assignee_email": { + "type": "string", + "description": "Email of assigned user" + }, + "priority": { + "type": "string", + "enum": ["low", "medium", "high"], + "default": "medium", + "description": "Task priority" + }, + "due_date": { + "type": "string", + "format": "date", + "description": "Due date" + }, + "labels": { + "type": "array", + "items": { "type": "string" }, + "description": "Task labels/tags" + } + }, + "required": ["title"] +} +``` + +## Naming Conventions + +- **Entity name**: Use PascalCase with alphanumeric characters only (e.g., `Task`, `TeamMember`, `ActivityLog`) + - Must match pattern: `/^[a-zA-Z0-9]+$/` + - Valid: `Task`, `TeamMember`, `Order123` + - Invalid: `Team_Member`, `Team-Member`, `Team Member` +- **File name**: Use kebab-case matching the entity (e.g., `task.jsonc`, `team-member.jsonc`, `activity-log.jsonc`) +- **Field names**: Use snake_case (e.g., `board_id`, `user_email`, `due_date`) + +## Relationships Between Entities + +To create relationships between entities, use ID reference fields: + +```jsonc +{ + "board_id": { + "type": "string", + "description": "Board this task belongs to" + }, + "team_id": { + "type": "string", + "description": "Associated team ID" + } +} +``` + +## Row Level Security (RLS) + +Row Level Security (RLS) controls which records users can access based on their identity and attributes. RLS rules are defined per entity inside the `rls` field of the schema. + +**Important:** If no RLS is defined, all records are accessible to all users. + +### RLS Operations + +RLS supports five operations: + +| Operation | Description | +|-----------|-------------| +| `create` | Control who can add new records | +| `read` | Control who can view records | +| `update` | Control who can modify records | +| `delete` | Control who can remove records | +| `write` | Shorthand for `create`, `update`, and `delete` combined | + +### Permission Values + +Each operation accepts one of the following values: + +1. **`true`** - Allow all users (including anonymous/unauthenticated) +2. **`false`** - Block all users +3. **Condition object** - Allow users matching the condition + +### Template Variables + +Use template variables to reference the current user's attributes: + +| Template | Description | +|----------|-------------| +| `{{user.id}}` | The user's ID | +| `{{user.email}}` | The user's email | +| `{{user.role}}` | The user's role | +| `{{user.data.field_name}}` | Custom field from the user's `data` object | + +### Built-in Entity Attributes + +Every entity record has these built-in attributes available for RLS rules: + +| Attribute | Description | +|-----------|-------------| +| `id` | Unique record identifier | +| `created_date` | Timestamp when record was created | +| `updated_date` | Timestamp when record was last updated | +| `created_by` | Email of the user who created the record | + +### Rule Types + +There are two condition types you can use: + +**1. Entity-to-user comparison** - Compare record fields to the current user's values: +```jsonc +{ + "created_by": "{{user.email}}" +} +``` + +**2. User condition check** - Check user properties directly using `user_condition`: +```jsonc +{ + "user_condition": { "role": "admin" } +} +``` + +**Important limitations:** +- `user_condition` only supports **simple equality** (e.g., `{ "role": "admin" }`) +- For `data.*` field comparisons, you can use operators: `$in`, `$nin`, `$ne`, `$all` +- Logical operators `$or`, `$and`, `$nor` are available for combining conditions +- You cannot filter by entity field values directly (e.g., `{"status": "published"}`) +- Only user-related conditions are allowed + +### RLS Examples + +**Owner-only access:** +```jsonc +{ + "created_by": "{{user.email}}" +} +``` + +**Department-based access:** +```jsonc +{ + "data.department": "{{user.data.department}}" +} +``` + +**Admin-only access:** +```jsonc +{ + "user_condition": { "role": "admin" } +} +``` + +**Complete RLS configuration:** +```jsonc +{ + "name": "Task", + "type": "object", + "properties": { + "title": { + "type": "string", + "description": "Task title" + }, + "status": { + "type": "string", + "enum": ["todo", "in_progress", "done"], + "default": "todo" + } + }, + "required": ["title"], + "rls": { + "create": true, + "read": { "created_by": "{{user.email}}" }, + "update": { "created_by": "{{user.email}}" }, + "delete": { "created_by": "{{user.email}}" } + } +} +``` + +### Common RLS Patterns + +**Public create, admin-only management (e.g., contact forms, waitlists):** +```jsonc +{ + "rls": { + "create": true, + "read": { "user_condition": { "role": "admin" } }, + "update": { "user_condition": { "role": "admin" } }, + "delete": { "user_condition": { "role": "admin" } } + } +} +``` + +**Owner-only access:** +```jsonc +{ + "rls": { + "create": true, + "read": { "created_by": "{{user.email}}" }, + "update": { "created_by": "{{user.email}}" }, + "delete": { "created_by": "{{user.email}}" } + } +} +``` + +**Logged-in users only:** +```jsonc +{ + "rls": { + "create": { "user_condition": { "id": "{{user.id}}" } }, + "read": true, + "update": { "created_by": "{{user.email}}" }, + "delete": { "created_by": "{{user.email}}" } + } +} +``` + +### Limitations + +- **user_condition is equality only:** `user_condition` only supports exact match (e.g., `{ "role": "admin" }`) - no operators +- **No comparison operators on user_condition:** `$gt`, `$lt`, `$regex`, `$expr`, `$where` are NOT supported for user conditions +- **No entity field filtering:** Cannot filter by entity field values (e.g., `{"status": "published"}`) +- **No deeply nested templates:** Templates like `{{user.data.profile.department}}` may not work + +**Supported operators:** +- **Logical operators:** `$or`, `$and`, `$nor` for combining multiple conditions +- **Field operators (for `data.*` fields only):** `$in`, `$nin`, `$ne`, `$all` + +### Complex Access Patterns + +For complex access patterns that require multiple conditions (e.g., "owner OR admin"), you have two options: + +1. **Use the Base44 Dashboard UI** - The dashboard allows adding multiple rules per operation with OR logic +2. **Use separate entities** - Split data into multiple entities with different access rules +3. **Use backend functions** - Implement custom access logic in backend functions + +## Field Level Security (FLS) + +Field Level Security allows you to control access to individual fields within an entity. FLS rules are defined within each field's schema using the `rls` property. + +### FLS Operations + +FLS supports the same operations as entity-level RLS: + +| Operation | Description | +|-----------|-------------| +| `create` | Control who can set this field when creating records | +| `read` | Control who can view this field | +| `update` | Control who can modify this field | +| `delete` | Control who can clear this field | +| `write` | Shorthand for `create`, `update`, and `delete` combined | + +### FLS Example + +```jsonc +{ + "name": "Employee", + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "Employee name" + }, + "salary": { + "type": "number", + "description": "Employee salary", + "rls": { + "read": { "user_condition": { "role": "hr" } }, + "update": { "user_condition": { "role": "hr" } } + } + }, + "department": { + "type": "string", + "description": "Department name" + } + }, + "required": ["name"] +} +``` + +In this example, only users with the `hr` role can read or update the `salary` field. All users with access to the entity can read/update other fields. + +### FLS Notes + +- If no field-level RLS is defined, the field inherits the entity-level RLS rules +- FLS rules follow the same condition format as entity-level RLS +- Use FLS for sensitive fields like salary, SSN, or internal notes + +## Pushing Entities + +The `entities push` command will push all entities that exist in the `base44/entities` folder. + +```bash +npx base44 entities push +``` + +For more details on the push command, see [entities-push.md](entities-push.md). diff --git a/evals/fixtures/cli-agents/skills/base44-cli/references/entities-push.md b/evals/fixtures/cli-agents/skills/base44-cli/references/entities-push.md new file mode 100644 index 0000000..63b92d7 --- /dev/null +++ b/evals/fixtures/cli-agents/skills/base44-cli/references/entities-push.md @@ -0,0 +1,71 @@ +# base44 entities push + +Push local entity definitions to Base44. + +## Syntax + +```bash +npx base44 entities push +``` + +## Authentication + +**Required**: Yes. If not authenticated, you'll be prompted to login first. + +## What It Does + +1. Pushes all entities that exist in the `base44/entities` folder +2. Validates that entities exist in the folder +3. Displays the count of entities to be pushed +4. Uploads entities to the Base44 backend +5. Reports the results: created, updated, and deleted entities + +## Prerequisites + +- Must be run from a Base44 project directory +- Project must have entity definitions in the `base44/entities` folder + +## Output + +```bash +$ npx base44 entities push + +Found 3 entities to push +Pushing entities to Base44... + +Created: User, Post +Updated: Comment +Deleted: OldEntity + +✓ Entities pushed successfully +``` + +## Entity Synchronization + +The push operation synchronizes your local entity schema with Base44: + +- **Created**: New entities that didn't exist in Base44 +- **Updated**: Existing entities with modified schema or configuration +- **Deleted**: Entities that were removed from your local configuration + +## Error Handling + +If no entities are found in your project: +```bash +$ npx base44 entities push +No entities found in project +``` + +## Use Cases + +- After defining new entities in your project +- When modifying existing entity schemas +- To sync entity changes before deploying +- As part of your development workflow when data models change + +## Notes + +- This command syncs the entity schema/structure, not the actual data +- Changes are applied to your Base44 project immediately +- Make sure to test entity changes in a development environment first +- Entity definitions are located in the `base44/entities/` directory diff --git a/evals/fixtures/cli-agents/skills/base44-cli/references/functions-create.md b/evals/fixtures/cli-agents/skills/base44-cli/references/functions-create.md new file mode 100644 index 0000000..c523cdc --- /dev/null +++ b/evals/fixtures/cli-agents/skills/base44-cli/references/functions-create.md @@ -0,0 +1,229 @@ +# Creating Functions + +Base44 functions are serverless backend functions that run on Deno. They are defined locally in your project and deployed to the Base44 backend. + +## Function Directory + +All function definitions must be placed in the `base44/functions/` folder in your project. Each function lives in its own subdirectory with a configuration file and entry point. + +Example structure: +``` +my-app/ + base44/ + functions/ + process-order/ + function.jsonc + index.ts + send-notification/ + function.jsonc + index.ts +``` + +## How to Create a Function + +1. Create a new directory in `base44/functions/` with your function name (use kebab-case) +2. Create a `function.jsonc` configuration file in the directory +3. Create the entry point file (e.g., `index.ts`) +4. Deploy the function using the CLI + +## Function Configuration + +Each function requires a `function.jsonc` configuration file: + +```jsonc +{ + "name": "my-function", + "entry": "index.ts" +} +``` + +### Configuration Properties + +| Property | Description | Required | +|----------|-------------|----------| +| `name` | Function name (must match `/^[^.]+$/` - no dots allowed) | Yes | +| `entry` | Entry point file path relative to the function directory (min 1 char) | Yes | + +## Entry Point File + +Functions run on Deno and must export using `Deno.serve()`. Use `npm:` prefix for npm packages. + +```typescript +import { createClientFromRequest } from "npm:@base44/sdk"; + +Deno.serve(async (req) => { + // Get authenticated client from request + const base44 = createClientFromRequest(req); + + // Parse input + const { orderId, action } = await req.json(); + + // Your logic here + const order = await base44.entities.Orders.get(orderId); + + // Return response + return Response.json({ + success: true, + order: order + }); +}); +``` + +### Request Object + +The function receives a standard Deno `Request` object: +- `req.json()` - Parse JSON body +- `req.text()` - Get raw text body +- `req.headers` - Access request headers +- `req.method` - HTTP method + +### Response Object + +Return using `Response.json()` for JSON responses: + +```typescript +// Success response +return Response.json({ data: result }); + +// Error response with status code +return Response.json({ error: "Something went wrong" }, { status: 400 }); + +// Not found +return Response.json({ error: "Order not found" }, { status: 404 }); +``` + +## Complete Example + +### Directory Structure +``` +base44/ + functions/ + process-order/ + function.jsonc + index.ts +``` + +### function.jsonc +```jsonc +{ + "name": "process-order", + "entry": "index.ts" +} +``` + +### index.ts +```typescript +import { createClientFromRequest } from "npm:@base44/sdk"; + +Deno.serve(async (req) => { + try { + const base44 = createClientFromRequest(req); + const { orderId } = await req.json(); + + // Validate input + if (!orderId) { + return Response.json( + { error: "Order ID is required" }, + { status: 400 } + ); + } + + // Fetch and process the order + const order = await base44.entities.Orders.get(orderId); + if (!order) { + return Response.json( + { error: "Order not found" }, + { status: 404 } + ); + } + + return Response.json({ + success: true, + orderId: order.id, + processedAt: new Date().toISOString() + }); + + } catch (error) { + return Response.json( + { error: error.message }, + { status: 500 } + ); + } +}); +``` + +## Using Service Role Access + +For admin-level operations, use `asServiceRole`: + +```typescript +import { createClientFromRequest } from "npm:@base44/sdk"; + +Deno.serve(async (req) => { + const base44 = createClientFromRequest(req); + + // Check user is authenticated + const user = await base44.auth.me(); + if (!user) { + return Response.json({ error: "Unauthorized" }, { status: 401 }); + } + + // Use service role for admin operations + const allOrders = await base44.asServiceRole.entities.Orders.list(); + + return Response.json({ orders: allOrders }); +}); +``` + +## Using Secrets + +Access environment variables configured in the app dashboard: + +```typescript +Deno.serve(async (req) => { + // Access environment variables (configured in app settings) + const apiKey = Deno.env.get("STRIPE_API_KEY"); + + const response = await fetch("https://api.stripe.com/v1/charges", { + headers: { + "Authorization": `Bearer ${apiKey}` + } + }); + + return Response.json(await response.json()); +}); +``` + +## Naming Conventions + +- **Directory name**: Use kebab-case (e.g., `process-order`, `send-notification`) +- **Function name**: Match the directory name, must match pattern `/^[^.]+$/` (no dots allowed) + - Valid: `process-order`, `send_notification`, `myFunction` + - Invalid: `process.order`, `send.notification.v2` +- **Entry file**: Typically `index.ts` or `index.js` + +## Deploying Functions + +After creating your function, deploy it to Base44: + +```bash +npx base44 functions deploy +``` + +For more details on deploying, see [functions-deploy.md](functions-deploy.md). + +## Notes + +- Functions run on Deno runtime, not Node.js +- Use `npm:` prefix for npm packages (e.g., `npm:@base44/sdk`) +- Use `createClientFromRequest(req)` to get a client that inherits the caller's auth context +- Configure secrets via app dashboard for API keys +- Make sure to handle errors gracefully and return appropriate HTTP status codes + +## Common Mistakes + +| Wrong | Correct | Why | +|-------|---------|-----| +| `functions/myFunction.js` (single file) | `functions/my-function/index.ts` + `function.jsonc` | Functions require subdirectory with config | +| `import { ... } from "@base44/sdk"` | `import { ... } from "npm:@base44/sdk"` | Deno requires `npm:` prefix for npm packages | +| `MyFunction` or `myFunction` directory | `my-function` directory | Use kebab-case for directory names | diff --git a/evals/fixtures/cli-agents/skills/base44-cli/references/functions-deploy.md b/evals/fixtures/cli-agents/skills/base44-cli/references/functions-deploy.md new file mode 100644 index 0000000..f89d796 --- /dev/null +++ b/evals/fixtures/cli-agents/skills/base44-cli/references/functions-deploy.md @@ -0,0 +1,78 @@ +# base44 functions deploy + +Deploy local function definitions to Base44. + +## Syntax + +```bash +npx base44 functions deploy +``` + +## Authentication + +**Required**: Yes. If not authenticated, you'll be prompted to login first. + +## What It Does + +1. Scans the `base44/functions/` directory for function definitions +2. Validates that functions exist and have valid configurations +3. Displays the count of functions to be deployed +4. Uploads function code and configuration to Base44 +5. Reports the results: deployed and deleted functions + +## Prerequisites + +- Must be run from a Base44 project directory +- Project must have function definitions in the `base44/functions/` folder +- Each function must have a valid `function.jsonc` config file + +## Output + +```bash +$ npx base44 functions deploy + +Found 2 functions to deploy +Deploying functions to Base44... + +Deployed: process-order, send-notification +Deleted: old-function + +✓ Functions deployed successfully +``` + +## Function Synchronization + +The deploy operation synchronizes your local functions with Base44: + +- **Deployed**: Functions that were created or updated +- **Deleted**: Functions that were removed from your local configuration + +## Error Handling + +If no functions are found in your project: +```bash +$ npx base44 functions deploy +No functions found. Create functions in the 'functions' directory. +``` + +If a function has configuration errors: +```bash +$ npx base44 functions deploy +Function deployment errors: +'my-function' function: Entry point cannot be empty +``` + +## Use Cases + +- After creating new functions in your project +- When modifying existing function code or configuration +- To sync function changes before testing +- As part of your development workflow when backend logic changes + +## Notes + +- This command deploys the function code and configuration +- Changes are applied to your Base44 project immediately +- Make sure to test functions in a development environment first +- Function definitions are located in the `base44/functions/` directory +- For how to create functions, see [functions-create.md](functions-create.md) diff --git a/evals/fixtures/cli-agents/skills/base44-cli/references/link.md b/evals/fixtures/cli-agents/skills/base44-cli/references/link.md new file mode 100644 index 0000000..0b009ce --- /dev/null +++ b/evals/fixtures/cli-agents/skills/base44-cli/references/link.md @@ -0,0 +1,81 @@ +# base44 link + +Links an existing local Base44 project to a Base44 app in the cloud. Use this when you have a `base44/config.jsonc` but haven't connected it to a Base44 app yet. + +## Critical: When to Use Link vs Create + +| Scenario | Command | +|----------|---------| +| Starting fresh, no `base44/` folder | `npx base44 create` | +| Have `base44/config.jsonc` but no `.app.jsonc` | `npx base44 link` | +| Project already linked (has `.app.jsonc`) | Already done, use `deploy` | + +## Syntax + +```bash +npx base44 link [options] +``` + +## Options + +| Option | Description | Required | +|--------|-------------|----------| +| `-c, --create` | Create a new project (skip selection prompt) | No | +| `-n, --name ` | Project name (required when `--create` is used) | With `--create` | +| `-d, --description ` | Project description | No | +| `-p, --projectId ` | Project ID to link to an existing project (skip selection prompt) | No | + +## Non-Interactive Mode + +For CI/CD or agent use: + +**Create a new project:** +```bash +npx base44 link --create --name my-app +``` + +**Link to an existing project:** +```bash +npx base44 link --projectId +``` + +WRONG: `npx base44 link --create` (missing --name) +WRONG: `npx base44 link --create --projectId ` (cannot use both) +RIGHT: `npx base44 link --create --name my-app` +RIGHT: `npx base44 link --projectId ` + +## Examples + +```bash +# Interactive mode - prompts for project details +npx base44 link + +# Non-interactive - create and link in one step +npx base44 link --create --name my-app + +# With description +npx base44 link --create --name my-app --description "My awesome app" + +# Link to a specific existing project by ID +npx base44 link --projectId abc123 +``` + +## What It Does + +1. Finds the `base44/config.jsonc` in the current directory (or parent directories) +2. Verifies no `.app.jsonc` exists (project not already linked) +3. Either: + - Creates a new Base44 app in the cloud (with `--create`), OR + - Links to an existing project (with `--projectId` or interactive selection) +4. Writes the app ID to `base44/.app.jsonc` + +## Requirements + +- Must have `base44/config.jsonc` in the project +- Must NOT have `base44/.app.jsonc` (use `deploy` if already linked) +- Must be authenticated (run `npx base44 login` first) + +## Notes + +- After linking, you can deploy resources with `npx base44 deploy` +- The `.app.jsonc` file should be git-ignored (contains your app ID) diff --git a/evals/fixtures/cli-agents/skills/base44-cli/references/rls-examples.md b/evals/fixtures/cli-agents/skills/base44-cli/references/rls-examples.md new file mode 100644 index 0000000..7d3ef42 --- /dev/null +++ b/evals/fixtures/cli-agents/skills/base44-cli/references/rls-examples.md @@ -0,0 +1,463 @@ +# RLS Examples + +Practical Row-Level Security patterns for common application types. + +**Important:** Base44 RLS supports: +- **Logical operators:** `$or`, `$and`, `$nor` for combining conditions +- **Field operators (for `data.*` fields):** `$in`, `$nin`, `$ne`, `$all` +- **user_condition:** Equality only (no operators) + +## Contents +- [Simple Patterns (JSON Schema)](#simple-patterns-json-schema) +- [Using Operators](#using-operators) +- [Field-Level Security Examples](#field-level-security-examples) +- [Complex Patterns (Dashboard UI or Backend)](#complex-patterns-dashboard-ui-or-backend) +- [Best Practices](#best-practices) + +--- + +## Simple Patterns (JSON Schema) + +These patterns work with the JSON schema RLS format. + +### Todo App - Owner-only access + +Users see and manage only their own tasks. + +```jsonc +{ + "name": "Task", + "type": "object", + "properties": { + "title": { "type": "string" }, + "description": { "type": "string" }, + "completed": { "type": "boolean" }, + "priority": { "type": "string", "enum": ["low", "medium", "high"] }, + "due_date": { "type": "string", "format": "date" } + }, + "rls": { + "create": true, + "read": { "created_by": "{{user.email}}" }, + "update": { "created_by": "{{user.email}}" }, + "delete": { "created_by": "{{user.email}}" } + } +} +``` + +### Contact Form - Public create, admin-only read + +Anyone can submit, only admins can view submissions. + +```jsonc +{ + "name": "ContactSubmission", + "type": "object", + "properties": { + "name": { "type": "string" }, + "email": { "type": "string", "format": "email" }, + "message": { "type": "string" } + }, + "rls": { + "create": true, + "read": { "user_condition": { "role": "admin" } }, + "update": { "user_condition": { "role": "admin" } }, + "delete": { "user_condition": { "role": "admin" } } + } +} +``` + +### User Profile - Self-management + +Users can only access their own profile. + +```jsonc +{ + "name": "UserProfile", + "type": "object", + "properties": { + "name": { "type": "string" }, + "avatar_url": { "type": "string" }, + "bio": { "type": "string" }, + "preferences": { "type": "object" } + }, + "rls": { + "create": true, + "read": { "created_by": "{{user.email}}" }, + "update": { "created_by": "{{user.email}}" }, + "delete": { "created_by": "{{user.email}}" } + } +} +``` + +### Department Data - Same department access + +Users can only see records from their department. + +```jsonc +{ + "name": "DepartmentAnnouncement", + "type": "object", + "properties": { + "title": { "type": "string" }, + "content": { "type": "string" }, + "department": { "type": "string" } + }, + "rls": { + "create": { "user_condition": { "role": "manager" } }, + "read": { "data.department": "{{user.data.department}}" }, + "update": { "user_condition": { "role": "manager" } }, + "delete": { "user_condition": { "role": "admin" } } + } +} +``` + +### Subscription - Admin-managed, user-readable via email field + +```jsonc +{ + "name": "Subscription", + "type": "object", + "properties": { + "user_email": { "type": "string" }, + "tier": { "type": "string", "enum": ["free", "basic", "pro", "enterprise"] }, + "credits": { "type": "number" }, + "renewal_date": { "type": "string", "format": "date" } + }, + "rls": { + "create": { "user_condition": { "role": "admin" } }, + "read": { "data.user_email": "{{user.email}}" }, + "update": { "user_condition": { "role": "admin" } }, + "delete": { "user_condition": { "role": "admin" } } + } +} +``` + +**Note:** This pattern only allows users to read their own subscription. Admins need to use the Dashboard UI to configure additional read access for themselves. + +### Private Data - Owner-only + +```jsonc +{ + "name": "PrivateNotes", + "type": "object", + "properties": { + "title": { "type": "string" }, + "content": { "type": "string" }, + "tags": { "type": "array", "items": { "type": "string" } } + }, + "rls": { + "create": true, + "read": { "created_by": "{{user.email}}" }, + "update": { "created_by": "{{user.email}}" }, + "delete": { "created_by": "{{user.email}}" } + } +} +``` + +### Public Read, Authenticated Write + +Anyone can read, only logged-in users can create/edit their own records. + +```jsonc +{ + "name": "BlogPost", + "type": "object", + "properties": { + "title": { "type": "string" }, + "content": { "type": "string" }, + "author_email": { "type": "string" } + }, + "rls": { + "create": true, + "read": true, + "update": { "created_by": "{{user.email}}" }, + "delete": { "created_by": "{{user.email}}" } + } +} +``` + +--- + +## Using Operators + +### Logical Operators + +Combine multiple conditions using `$or`, `$and`, or `$nor`: + +**Owner OR Admin access:** +```jsonc +{ + "name": "Document", + "type": "object", + "properties": { + "title": { "type": "string" }, + "content": { "type": "string" } + }, + "rls": { + "create": true, + "read": { + "$or": [ + { "created_by": "{{user.email}}" }, + { "user_condition": { "role": "admin" } } + ] + }, + "update": { + "$or": [ + { "created_by": "{{user.email}}" }, + { "user_condition": { "role": "admin" } } + ] + }, + "delete": { "user_condition": { "role": "admin" } } + } +} +``` + +**Multiple roles with $or:** +```jsonc +{ + "rls": { + "read": { + "$or": [ + { "user_condition": { "role": "admin" } }, + { "user_condition": { "role": "manager" } }, + { "user_condition": { "role": "hr" } } + ] + } + } +} +``` + +### Field Operators for data.* Fields + +Use `$in`, `$nin`, `$ne`, `$all` for comparing entity data fields: + +**Access based on tags ($in):** +```jsonc +{ + "rls": { + "read": { + "data.category": { "$in": ["public", "shared"] } + } + } +} +``` + +**Exclude specific statuses ($nin):** +```jsonc +{ + "rls": { + "read": { + "data.status": { "$nin": ["archived", "deleted"] } + } + } +} +``` + +**Not equal ($ne):** +```jsonc +{ + "rls": { + "read": { + "data.visibility": { "$ne": "private" } + } + } +} +``` + +**All tags must match ($all):** +```jsonc +{ + "rls": { + "read": { + "data.required_tags": { "$all": ["approved", "reviewed"] } + } + } +} +``` + +### Combining Logical and Field Operators + +```jsonc +{ + "rls": { + "read": { + "$and": [ + { "data.status": { "$ne": "draft" } }, + { + "$or": [ + { "created_by": "{{user.email}}" }, + { "data.visibility": "public" } + ] + } + ] + } + } +} +``` + +--- + +## Field-Level Security Examples + +Control access to specific fields within an entity. + +### Sensitive Salary Field + +```jsonc +{ + "name": "Employee", + "type": "object", + "properties": { + "name": { "type": "string" }, + "email": { "type": "string", "format": "email" }, + "salary": { + "type": "number", + "description": "Annual salary", + "rls": { + "read": { "user_condition": { "role": "hr" } }, + "write": { "user_condition": { "role": "hr" } } + } + }, + "performance_notes": { + "type": "string", + "description": "Manager notes", + "rls": { + "read": { + "$or": [ + { "user_condition": { "role": "manager" } }, + { "user_condition": { "role": "hr" } } + ] + }, + "write": { "user_condition": { "role": "manager" } } + } + } + } +} +``` + +### Admin-Only Internal Fields + +```jsonc +{ + "name": "Order", + "type": "object", + "properties": { + "order_number": { "type": "string" }, + "total": { "type": "number" }, + "internal_notes": { + "type": "string", + "description": "Internal processing notes", + "rls": { + "read": { "user_condition": { "role": "admin" } }, + "write": { "user_condition": { "role": "admin" } } + } + }, + "profit_margin": { + "type": "number", + "description": "Profit margin percentage", + "rls": { + "read": { "user_condition": { "role": "admin" } }, + "write": false + } + } + } +} +``` + +--- + +## Complex Patterns (Dashboard UI or Backend) + +Some patterns may still require the Dashboard UI or backend functions. + +### Bidirectional Relationships (e.g., Friendships, Matches) + +**Requirement:** Either party in a relationship should have access. + +**Now possible with $or:** +```jsonc +{ + "rls": { + "read": { + "$or": [ + { "data.user_a_email": "{{user.email}}" }, + { "data.user_b_email": "{{user.email}}" } + ] + } + } +} +``` + +**Alternative solutions:** +1. **Entity redesign:** Store two records per relationship (one for each party) +2. **Backend function:** Query with custom logic + +### Complex Business Logic + +**Requirement:** Access depends on multiple entity fields with complex conditions. + +**JSON Schema limitation:** While operators help, very complex business logic may still be hard to express. + +**Solution options:** +1. **Backend function:** Implement custom access logic +2. **Combine simpler rules:** Break complex rules into simpler entity-level and field-level rules + +--- + +## Best Practices + +### Security Strategy + +Use a combination of entity-level RLS and field-level security: + +| Data Type | Approach | Example | +|-----------|----------|---------| +| User-editable | Entity RLS: Owner-only | UserProfile with `created_by` check | +| Sensitive fields | Field-level RLS | Salary field with HR role check | +| Multi-role access | `$or` with user_condition | Admin OR Manager access | +| Conditional access | Field operators | `$in`, `$ne` on data fields | +| Public content | Entity RLS: `read: true` | PublicPost | +| Private content | Entity RLS: Owner-only | PrivateNote | + +### When to Use Each Approach + +| Requirement | Approach | +|-------------|----------| +| Single condition (owner, admin, department) | JSON Schema RLS | +| Multiple OR/AND conditions | JSON Schema RLS with `$or`/`$and` | +| Field value checks with `$in`/`$ne`/etc. | JSON Schema RLS for `data.*` fields | +| Field-level access control | JSON Schema FLS (field-level `rls`) | +| Complex comparison operators (`$gt`, `$lt`) | Backend functions | +| Very complex business logic | Backend functions | + +### Common Role Patterns + +| Role | Typical Access | +|------|----------------| +| `admin` | Full access to all records | +| `moderator` | Read/update access, limited delete | +| `manager` | Department-scoped access | +| `user` | Own records only | + +### Supported Operators Summary + +| Operator | Supported | Notes | +|----------|-----------|-------| +| `$or` | Yes | Combine multiple conditions | +| `$and` | Yes | All conditions must match | +| `$nor` | Yes | None of the conditions match | +| `$in` | Yes | For `data.*` fields only | +| `$nin` | Yes | For `data.*` fields only | +| `$ne` | Yes | For `data.*` fields only | +| `$all` | Yes | For `data.*` fields only | +| `$gt`, `$lt`, `$gte`, `$lte` | No | Use backend functions | +| `$regex` | No | Use backend functions | + +### Limitations Summary + +| Not Supported | Alternative | +|---------------|-------------| +| Operators on `user_condition` | Use equality only for user checks | +| Comparison operators (`$gt`, `$lt`) | Backend functions | +| Regex matching (`$regex`) | Backend functions | +| Cross-entity relationships | Backend functions | diff --git a/evals/fixtures/cli-agents/skills/base44-cli/references/site-deploy.md b/evals/fixtures/cli-agents/skills/base44-cli/references/site-deploy.md new file mode 100644 index 0000000..929d302 --- /dev/null +++ b/evals/fixtures/cli-agents/skills/base44-cli/references/site-deploy.md @@ -0,0 +1,118 @@ +# base44 site deploy + +Deploy built site files to Base44 hosting. + +## Table of Contents + +- [Syntax](#syntax) +- [Authentication](#authentication) +- [Prerequisites](#prerequisites) +- [How It Works](#how-it-works) +- [Interactive Flow](#interactive-flow) +- [Typical Workflow](#typical-workflow) +- [Configuration](#configuration) +- [Error Handling](#error-handling) +- [Use Cases](#use-cases) +- [Notes](#notes) + +## Syntax + +```bash +npx base44 site deploy [options] +``` + +## Options + +| Option | Description | +| ------------ | ------------------------- | +| `-y, --yes` | Skip confirmation prompt | + +Use `-y` flag for non-interactive/automated deployments: + +```bash +npx base44 site deploy -y +``` + +## Authentication + +**Required**: Yes. If not authenticated, you'll be prompted to login first. + +## Prerequisites + +- Must be run from a Base44 project directory +- Project must have `site.outputDirectory` configured in project config +- Site must be built before deploying (run your build command first) +- **SPA only**: Base44 hosting supports Single Page Applications with a single `index.html` entry point. All routes are served from `index.html` (client-side routing). + +## How It Works + +1. Reads project configuration +2. Validates that site configuration exists +3. Prompts for deployment confirmation showing the output directory +4. Creates an archive of site files from the output directory +5. Deploys to Base44 hosting +6. Returns the app URL + +## Interactive Flow + +```bash +$ npx base44 site deploy + +Deploy site from ./dist? (yes/no) yes + +Creating archive... +Uploading to Base44... +Deploying... + +✓ Deployment successful! + +Visit your site at: https://my-app.base44.app +``` + +## Typical Workflow + +```bash +# 1. Build your site using your framework's build command +npm run build + +# 2. Deploy to Base44 +npx base44 site deploy +``` + +## Configuration + +The `site.outputDirectory` in your project configuration should point to where your framework outputs built files: + +- Vite: typically `./dist` +- Next.js: typically `./.next` or `./out` +- Create React App: typically `./build` +- Custom: whatever your build tool outputs to + +## Error Handling + +If site configuration is missing: +```bash +$ npx base44 site deploy +Error: No site configuration found in project +``` + +If you cancel the deployment: +```bash +Deploy site from ./dist? (yes/no) no +Deployment cancelled +``` + +## Use Cases + +- Deploy your site after making changes +- Push new versions of your application +- Deploy after updating content or functionality +- Part of your CI/CD pipeline + +## Notes + +- Always build your site before deploying +- The command deploys whatever is in your output directory +- Make sure your build completed successfully before deploying +- Previous deployments are preserved (versioned) in Base44 +- Deployment is immediate and updates your live site diff --git a/evals/fixtures/cli-agents/skills/base44-sdk/SKILL.md b/evals/fixtures/cli-agents/skills/base44-sdk/SKILL.md new file mode 100644 index 0000000..01ddd7f --- /dev/null +++ b/evals/fixtures/cli-agents/skills/base44-sdk/SKILL.md @@ -0,0 +1,296 @@ +--- +name: base44-sdk +description: "The base44 SDK is the library to communicate with base44 services. In projects, you use it to communicate with remote resources (entities, backend functions, ai agents) and to write backend functions. This skill is the place for learning about available modules and types. When you plan or implement a feature, you must learn this skill" +--- + +# Base44 Coder + +Build apps on the Base44 platform using the Base44 JavaScript SDK. + +## ⚡ IMMEDIATE ACTION REQUIRED - Read This First + +This skill activates on ANY mention of "base44" or when a `base44/` folder exists. **DO NOT read documentation files or search the web before acting.** + +**Your first action MUST be:** +1. Check if `base44/config.jsonc` exists in the current directory +2. If **YES** (existing project scenario): + - This skill (base44-sdk) handles the request + - Implement features using Base44 SDK + - Do NOT use base44-cli unless user explicitly requests CLI commands +3. If **NO** (new project scenario): + - Transfer to base44-cli skill for project initialization + - This skill cannot help until project is initialized + +## When to Use This Skill vs base44-cli + +**Use base44-sdk when:** +- Building features in an **EXISTING** Base44 project +- `base44/config.jsonc` already exists in the project +- Base44 SDK imports are present (`@base44/sdk`) +- Writing JavaScript/TypeScript code using Base44 SDK modules +- Implementing functionality, components, or features +- User mentions: "implement", "build a feature", "add functionality", "write code for" +- User says "create a [type] app" **and** a Base44 project already exists + +**DO NOT USE base44-sdk for:** +- ❌ Initializing new Base44 projects (use `base44-cli` instead) +- ❌ Empty directories without Base44 configuration +- ❌ When user says "create a new Base44 project/app/site" and no project exists +- ❌ CLI commands like `npx base44 create`, `npx base44 deploy`, `npx base44 login` (use `base44-cli`) + +**Skill Dependencies:** +- `base44-sdk` assumes a Base44 project is **already initialized** +- `base44-cli` is a **prerequisite** for `base44-sdk` in new projects +- If user wants to "create an app" and no Base44 project exists, use `base44-cli` first + +**State Check Logic:** +Before selecting this skill, verify: +- IF (user mentions "create/build app" OR "make a project"): + - IF (directory is empty OR no `base44/config.jsonc` exists): + → Use **base44-cli** (project initialization needed) + - ELSE: + → Use **base44-sdk** (project exists, build features) + +## Quick Start + +```javascript +// In Base44-generated apps, base44 client is pre-configured and available + +// CRUD operations +const task = await base44.entities.Task.create({ title: "New task", status: "pending" }); +const tasks = await base44.entities.Task.list(); +await base44.entities.Task.update(task.id, { status: "done" }); + +// Get current user +const user = await base44.auth.me(); +``` + +```javascript +// External apps +import { createClient } from "@base44/sdk"; + +// IMPORTANT: Use 'appId' (NOT 'clientId' or 'id') +const base44 = createClient({ appId: "your-app-id" }); +await base44.auth.loginViaEmailPassword("user@example.com", "password"); +``` + +## ⚠️ CRITICAL: Do Not Hallucinate APIs + +**Before writing ANY Base44 code, verify method names against this table or [QUICK_REFERENCE.md](references/QUICK_REFERENCE.md).** + +Base44 SDK has unique method names. Do NOT assume patterns from Firebase, Supabase, or other SDKs. + +### Authentication - WRONG vs CORRECT + +| ❌ WRONG (hallucinated) | ✅ CORRECT | +|------------------------|-----------| +| `signInWithGoogle()` | `loginWithProvider('google')` | +| `signInWithProvider('google')` | `loginWithProvider('google')` | +| `auth.google()` | `loginWithProvider('google')` | +| `signInWithEmailAndPassword(email, pw)` | `loginViaEmailPassword(email, pw)` | +| `signIn(email, pw)` | `loginViaEmailPassword(email, pw)` | +| `createUser()` / `signUp()` | `register({email, password})` | +| `onAuthStateChanged()` | `me()` (no listener, call when needed) | +| `currentUser` | `await auth.me()` | + +### Functions - WRONG vs CORRECT + +| ❌ WRONG (hallucinated) | ✅ CORRECT | +|------------------------|-----------| +| `functions.call('name', data)` | `functions.invoke('name', data)` | +| `functions.run('name', data)` | `functions.invoke('name', data)` | +| `callFunction('name', data)` | `functions.invoke('name', data)` | +| `httpsCallable('name')(data)` | `functions.invoke('name', data)` | + +### Integrations - WRONG vs CORRECT + +| ❌ WRONG (hallucinated) | ✅ CORRECT | +|------------------------|-----------| +| `ai.generate(prompt)` | `integrations.Core.InvokeLLM({prompt})` | +| `openai.chat(prompt)` | `integrations.Core.InvokeLLM({prompt})` | +| `llm(prompt)` | `integrations.Core.InvokeLLM({prompt})` | +| `sendEmail(to, subject, body)` | `integrations.Core.SendEmail({to, subject, body})` | +| `email.send()` | `integrations.Core.SendEmail({to, subject, body})` | +| `uploadFile(file)` | `integrations.Core.UploadFile({file})` | +| `storage.upload(file)` | `integrations.Core.UploadFile({file})` | + +### Entities - WRONG vs CORRECT + +| ❌ WRONG (hallucinated) | ✅ CORRECT | +|------------------------|-----------| +| `entities.Task.find({...})` | `entities.Task.filter({...})` | +| `entities.Task.findOne(id)` | `entities.Task.get(id)` | +| `entities.Task.insert(data)` | `entities.Task.create(data)` | +| `entities.Task.remove(id)` | `entities.Task.delete(id)` | +| `entities.Task.onChange(cb)` | `entities.Task.subscribe(cb)` | + +## SDK Modules + +| Module | Purpose | Reference | +|--------|---------|-----------| +| `entities` | CRUD operations on data models | [entities.md](references/entities.md) | +| `auth` | Login, register, user management | [auth.md](references/auth.md) | +| `agents` | AI conversations and messages | [base44-agents.md](references/base44-agents.md) | +| `functions` | Backend function invocation | [functions.md](references/functions.md) | +| `integrations` | AI, email, file uploads, custom APIs | [integrations.md](references/integrations.md) | +| `connectors` | OAuth tokens (service role only) | [connectors.md](references/connectors.md) | +| `analytics` | Track custom events and user activity | [analytics.md](references/analytics.md) | +| `appLogs` | Log user activity in app | [app-logs.md](references/app-logs.md) | +| `users` | Invite users to the app | [users.md](references/users.md) | + +For client setup and authentication modes, see [client.md](references/client.md). + +**TypeScript Support:** Each reference file includes a "Type Definitions" section with TypeScript interfaces and types for the module's methods, parameters, and return values. + +## Installation + +Install the Base44 SDK: + +```bash +npm install @base44/sdk +``` + +**Important:** Never assume or hardcode the `@base44/sdk` package version. Always install without a version specifier to get the latest version. + +## Creating a Client (External Apps) + +When creating a client in external apps, **ALWAYS use `appId` as the parameter name**: + +```javascript +import { createClient } from "@base44/sdk"; + +// ✅ CORRECT +const base44 = createClient({ appId: "your-app-id" }); + +// ❌ WRONG - Do NOT use these: +// const base44 = createClient({ clientId: "your-app-id" }); // WRONG +// const base44 = createClient({ id: "your-app-id" }); // WRONG +``` + +**Required parameter:** `appId` (string) - Your Base44 application ID + +**Optional parameters:** +- `token` (string) - Pre-authenticated user token +- `options` (object) - Configuration options + - `options.onError` (function) - Global error handler + +**Example with error handler:** +```javascript +const base44 = createClient({ + appId: "your-app-id", + options: { + onError: (error) => { + console.error("Base44 error:", error); + } + } +}); +``` + +## Module Selection + +**Working with app data?** +- Create/read/update/delete records → `entities` +- Import data from file → `entities.importEntities()` +- Realtime updates → `entities.EntityName.subscribe()` + +**User management?** +- Login/register/logout → `auth` +- Get current user → `auth.me()` +- Update user profile → `auth.updateMe()` +- Invite users → `users.inviteUser()` + +**AI features?** +- Chat with AI agents → `agents` (requires logged-in user) +- Create new conversation → `agents.createConversation()` +- Manage conversations → `agents.getConversations()` +- Generate text/JSON with AI → `integrations.Core.InvokeLLM()` +- Generate images → `integrations.Core.GenerateImage()` + +**Custom backend logic?** +- Run server-side code → `functions.invoke()` +- Need admin access → `base44.asServiceRole.functions.invoke()` + +**External services?** +- Send emails → `integrations.Core.SendEmail()` +- Upload files → `integrations.Core.UploadFile()` +- Custom APIs → `integrations.custom.call()` +- OAuth tokens (Google, Slack) → `connectors` (backend only) + +**Tracking and analytics?** +- Track custom events → `analytics.track()` +- Log page views/activity → `appLogs.logUserInApp()` + +## Common Patterns + +### Filter and Sort Data + +```javascript +const pendingTasks = await base44.entities.Task.filter( + { status: "pending", assignedTo: userId }, // query + "-created_date", // sort (descending) + 10, // limit + 0 // skip +); +``` + +### Protected Routes (check auth) + +```javascript +const user = await base44.auth.me(); +if (!user) { + // Navigate to your custom login page + navigate('/login', { state: { returnTo: window.location.pathname } }); + return; +} +``` + +### Backend Function Call + +```javascript +// Frontend +const result = await base44.functions.invoke("processOrder", { + orderId: "123", + action: "ship" +}); + +// Backend function (Deno) +import { createClientFromRequest } from "npm:@base44/sdk"; + +Deno.serve(async (req) => { + const base44 = createClientFromRequest(req); + const { orderId, action } = await req.json(); + // Process with service role for admin access + const order = await base44.asServiceRole.entities.Orders.get(orderId); + return Response.json({ success: true }); +}); +``` + +### Service Role Access + +Use `asServiceRole` in backend functions for admin-level operations: + +```javascript +// User mode - respects permissions +const myTasks = await base44.entities.Task.list(); + +// Service role - full access (backend only) +const allTasks = await base44.asServiceRole.entities.Task.list(); +const token = await base44.asServiceRole.connectors.getAccessToken("slack"); +``` + +## Frontend vs Backend + +| Capability | Frontend | Backend | +|------------|----------|---------| +| `entities` (user's data) | Yes | Yes | +| `auth` | Yes | Yes | +| `agents` | Yes | Yes | +| `functions.invoke()` | Yes | Yes | +| `integrations` | Yes | Yes | +| `analytics` | Yes | Yes | +| `appLogs` | Yes | Yes | +| `users` | Yes | Yes | +| `asServiceRole.*` | No | Yes | +| `connectors` | No | Yes | + +Backend functions use `Deno.serve()` and `createClientFromRequest(req)` to get a properly authenticated client. diff --git a/evals/fixtures/cli-agents/skills/base44-sdk/references/QUICK_REFERENCE.md b/evals/fixtures/cli-agents/skills/base44-sdk/references/QUICK_REFERENCE.md new file mode 100644 index 0000000..d357384 --- /dev/null +++ b/evals/fixtures/cli-agents/skills/base44-sdk/references/QUICK_REFERENCE.md @@ -0,0 +1,155 @@ +# Base44 SDK Quick Reference + +Compact method signatures for all SDK modules. **Verify against this before writing code.** + +--- + +## Auth (`base44.auth.*`) + +``` +loginViaEmailPassword(email, password, turnstileToken?) → Promise<{access_token, user}> +loginWithProvider('google' | 'microsoft' | 'facebook', fromUrl?) → void +me() → Promise +updateMe(data) → Promise +isAuthenticated() → Promise +logout(redirectUrl?) → void +redirectToLogin(nextUrl) → void # ⚠️ Avoid - prefer custom login UI +register({email, password, turnstile_token?, referral_code?}) → Promise +verifyOtp({email, otpCode}) → Promise +resendOtp(email) → Promise +inviteUser(userEmail, role) → Promise +resetPasswordRequest(email) → Promise +resetPassword({resetToken, newPassword}) → Promise +changePassword({userId, currentPassword, newPassword}) → Promise +setToken(token, saveToStorage?) → void +``` + +--- + +## Entities (`base44.entities.EntityName.*`) + +``` +create(data) → Promise +bulkCreate(dataArray) → Promise +list(sort?, limit?, skip?, fields?) → Promise +filter(query, sort?, limit?, skip?, fields?) → Promise +get(id) → Promise +update(id, data) → Promise +delete(id) → Promise +deleteMany(query) → Promise +importEntities(file) → Promise // frontend only +subscribe(callback) → () => void // returns unsubscribe fn +``` + +**Sort:** Use `-fieldName` for descending (e.g., `-created_date`) + +--- + +## Functions (`base44.functions.*`) + +``` +invoke(functionName, data) → Promise +``` + +**Backend:** Use `base44.asServiceRole.functions.invoke()` for admin access. + +--- + +## Integrations (`base44.integrations.Core.*`) + +``` +InvokeLLM({prompt, add_context_from_internet?, response_json_schema?, file_urls?}) → Promise +GenerateImage({prompt}) → Promise<{url}> +SendEmail({to, subject, body, from_name?}) → Promise +UploadFile({file}) → Promise<{file_url}> +UploadPrivateFile({file}) → Promise<{file_uri}> +CreateFileSignedUrl({file_uri, expires_in?}) → Promise<{signed_url}> +ExtractDataFromUploadedFile({file_url, json_schema}) → Promise +``` + +### Custom Integrations (`base44.integrations.custom.*`) + +``` +call(slug, operationId, {payload?, pathParams?, queryParams?, headers?}?) → Promise<{success, status_code, data}> +``` + +**operationId format:** `"method:/path"` (e.g., `"get:/contacts"`, `"post:/users/{id}"`) + +--- + +## Analytics (`base44.analytics.*`) + +``` +track({eventName, properties?}) → void +``` + +--- + +## App Logs (`base44.appLogs.*`) + +``` +logUserInApp(pageName) → Promise +``` + +--- + +## Users (`base44.users.*`) + +``` +inviteUser(userEmail, role) → Promise // role: 'user' | 'admin' +``` + +--- + +## Connectors (`base44.asServiceRole.connectors.*`) + +**Backend only, service role required.** + +``` +getAccessToken(integrationType) → Promise +``` + +**Types:** `'googlecalendar'`, `'googledrive'`, `'slack'`, `'notion'`, `'salesforce'`, `'hubspot'`, `'linkedin'`, `'tiktok'`, `'github'` + +--- + +## Service Role Access + +**Backend functions only.** Prefix any module with `asServiceRole` for admin access: + +```javascript +base44.asServiceRole.entities.Task.list() +base44.asServiceRole.functions.invoke('name', data) +base44.asServiceRole.connectors.getAccessToken('slack') +``` + +--- + +## Backend Function Template + +```javascript +import { createClientFromRequest } from "@base44/sdk"; + +Deno.serve(async (req) => { + const base44 = createClientFromRequest(req); + const data = await req.json(); + + // User context + const user = await base44.auth.me(); + + // Service role for admin operations + const allRecords = await base44.asServiceRole.entities.Task.list(); + + return Response.json({ success: true }); +}); +``` + +--- + +## Client Initialization (External Apps) + +```javascript +import { createClient } from "@base44/sdk"; + +const base44 = createClient({ appId: "your-app-id" }); // MUST use 'appId' +``` diff --git a/evals/fixtures/cli-agents/skills/base44-sdk/references/analytics.md b/evals/fixtures/cli-agents/skills/base44-sdk/references/analytics.md new file mode 100644 index 0000000..d1b2c69 --- /dev/null +++ b/evals/fixtures/cli-agents/skills/base44-sdk/references/analytics.md @@ -0,0 +1,113 @@ +# Analytics Module + +Track custom events and user activity via `base44.analytics`. + +## Contents +- [Methods](#methods) +- [Examples](#examples) +- [Automatic Tracking](#automatic-tracking) +- [Best Practices](#best-practices) + +## Methods + +| Method | Signature | Description | +|--------|-----------|-------------| +| `track(params)` | `void` | Track a custom event | + +## Examples + +### Track Custom Event + +```javascript +// Track a simple event +base44.analytics.track({ + eventName: "button_clicked" +}); + +// Track event with properties +base44.analytics.track({ + eventName: "purchase_completed", + properties: { + product_id: "prod-123", + amount: 99.99, + currency: "USD" + } +}); +``` + +### Track User Actions + +```javascript +// Track page view +base44.analytics.track({ + eventName: "page_view", + properties: { + page: "/dashboard", + referrer: document.referrer + } +}); + +// Track feature usage +base44.analytics.track({ + eventName: "feature_used", + properties: { + feature: "export_data", + format: "csv" + } +}); +``` + +## Automatic Tracking + +The analytics module automatically tracks: +- **Initialization events**: When the app loads +- **Heartbeat events**: Periodic activity signals +- **Session duration**: Time spent in the app + +These internal events help measure user engagement without manual instrumentation. + +## Best Practices + +1. **Use descriptive event names**: `order_completed` instead of `click` +2. **Include relevant properties**: Add context that helps analyze the event +3. **Be consistent**: Use the same event names and property keys across your app +4. **Don't track sensitive data**: Avoid PII in event properties + +```javascript +// Good: Descriptive with relevant properties +base44.analytics.track({ + eventName: "subscription_started", + properties: { + plan: "pro", + billing_cycle: "annual" + } +}); + +// Avoid: Vague event name, no context +base44.analytics.track({ + eventName: "click" +}); +``` + +## Type Definitions + +```typescript +/** Properties that can be attached to a tracked event. */ +type TrackEventProperties = { + [key: string]: string | number | boolean | null | undefined; +}; + +/** Parameters for the track() method. */ +interface TrackEventParams { + /** The name of the event to track. */ + eventName: string; + /** Optional properties to attach to the event. */ + properties?: TrackEventProperties; +} + +/** The analytics module interface. */ +interface AnalyticsModule { + /** Track a custom event with optional properties. */ + track(params: TrackEventParams): void; +} +``` diff --git a/evals/fixtures/cli-agents/skills/base44-sdk/references/app-logs.md b/evals/fixtures/cli-agents/skills/base44-sdk/references/app-logs.md new file mode 100644 index 0000000..0439805 --- /dev/null +++ b/evals/fixtures/cli-agents/skills/base44-sdk/references/app-logs.md @@ -0,0 +1,78 @@ +# App Logs Module + +Log user activity in your app via `base44.appLogs`. + +## Contents +- [Methods](#methods) +- [Examples](#examples) +- [Use Cases](#use-cases) + +## Methods + +| Method | Signature | Description | +|--------|-----------|-------------| +| `logUserInApp(pageName)` | `Promise` | Log user activity on a page | + +## Examples + +### Log User Activity + +```javascript +// Log when user visits a page +await base44.appLogs.logUserInApp("dashboard"); + +// Log specific page visits +await base44.appLogs.logUserInApp("settings"); +await base44.appLogs.logUserInApp("profile"); + +// Log feature usage +await base44.appLogs.logUserInApp("export-button-click"); +``` + +The page name doesn't have to be an actual page - it can be any string you want to track. + +## Use Cases + +### Track Page Views in React + +```javascript +// Log page views on route change +useEffect(() => { + base44.appLogs.logUserInApp(window.location.pathname); +}, [location.pathname]); +``` + +### Track Feature Usage + +```javascript +// Log when user uses specific features +function handleExport() { + base44.appLogs.logUserInApp("export-data"); + // ... export logic +} + +function handleSettingsChange() { + base44.appLogs.logUserInApp("settings-updated"); + // ... save settings +} +``` + +## Notes + +- Logs appear in the Analytics page of your app dashboard +- App logs track page-level and feature-level activity +- Use `analytics.track()` for custom events with properties, `appLogs.logUserInApp()` for simple page/feature tracking + +## Type Definitions + +```typescript +/** App Logs module for tracking and analyzing app usage. */ +interface AppLogsModule { + /** + * Log user activity in the app. + * @param pageName - Name of the page or section being visited. + * @returns Promise that resolves when the log is recorded. + */ + logUserInApp(pageName: string): Promise; +} +``` diff --git a/evals/fixtures/cli-agents/skills/base44-sdk/references/auth.md b/evals/fixtures/cli-agents/skills/base44-sdk/references/auth.md new file mode 100644 index 0000000..5195b3a --- /dev/null +++ b/evals/fixtures/cli-agents/skills/base44-sdk/references/auth.md @@ -0,0 +1,761 @@ +# Auth Module + +User authentication, registration, and session management via `base44.auth`. + +## Contents +- [TypeScript Types](#typescript-types) +- [Methods](#methods) +- [Examples](#examples) +- [Error Handling](#error-handling) +- [Auth Providers](#auth-providers) +- [Environment Availability](#environment-availability) +- [App Visibility](#app-visibility) +- [Limitations](#limitations) + +--- + +## TypeScript Types + +### User Interface +```typescript +interface User { + id: string; + created_date: string; + updated_date: string; + email: string; + full_name: string | null; + disabled: boolean | null; + is_verified: boolean; + app_id: string; + is_service: boolean; + role: string; + [key: string]: any; // Custom schema fields +} +``` + +### LoginResponse Interface +```typescript +interface LoginResponse { + access_token: string; // JWT token + user: User; // Complete user object +} +``` + +### Parameter Interfaces + +#### RegisterParams +```typescript +interface RegisterParams { + email: string; // Required + password: string; // Required + turnstile_token?: string | null; // Optional: Cloudflare Turnstile for bot protection + referral_code?: string | null; // Optional: Referral code +} +``` + +#### VerifyOtpParams +```typescript +interface VerifyOtpParams { + email: string; // User's email + otpCode: string; // OTP code from email +} +``` + +#### ResetPasswordParams +```typescript +interface ResetPasswordParams { + resetToken: string; // Token from password reset email + newPassword: string; // New password to set +} +``` + +#### ChangePasswordParams +```typescript +interface ChangePasswordParams { + userId: string; // User ID + currentPassword: string; // Current password for verification + newPassword: string; // New password to set +} +``` + +### Provider Type +```typescript +type Provider = 'google' | 'microsoft' | 'facebook'; +``` + +--- + +## Methods + +### Module Interface +```typescript +interface AuthModule { + // User Info + me(): Promise; + updateMe(data: Partial>): Promise; + isAuthenticated(): Promise; + + // Login/Logout + loginViaEmailPassword(email: string, password: string, turnstileToken?: string): Promise; + loginWithProvider(provider: Provider, fromUrl?: string): void; + logout(redirectUrl?: string): void; + redirectToLogin(nextUrl: string): void; + + // Token Management + setToken(token: string, saveToStorage?: boolean): void; + + // Registration + register(params: RegisterParams): Promise; + verifyOtp(params: VerifyOtpParams): Promise; + resendOtp(email: string): Promise; + + // User Management + inviteUser(userEmail: string, role: string): Promise; + + // Password Management + resetPasswordRequest(email: string): Promise; + resetPassword(params: ResetPasswordParams): Promise; + changePassword(params: ChangePasswordParams): Promise; +} +``` + +### Method Reference Table + +| Method | Parameters | Return Type | Description | +|--------|-----------|-------------|-------------| +| `register()` | `params: RegisterParams` | `Promise` | Create new user account | +| `loginViaEmailPassword()` | `email: string, password: string, turnstileToken?: string` | `Promise` | Authenticate with email/password | +| `loginWithProvider()` | `provider: Provider, fromUrl?: string` | `void` | Initiate OAuth login flow | +| `me()` | None | `Promise` | Get current authenticated user | +| `updateMe()` | `data: Partial` | `Promise` | Update current user's profile | +| `logout()` | `redirectUrl?: string` | `void` | Clear session, optionally redirect | +| `redirectToLogin()` | `nextUrl: string` | `void` | ⚠️ **Avoid** - Prefer custom login UI with `loginViaEmailPassword()` or `loginWithProvider()` | +| `isAuthenticated()` | None | `Promise` | Check if user is logged in | +| `setToken()` | `token: string, saveToStorage?: boolean` | `void` | Manually set auth token | +| `inviteUser()` | `userEmail: string, role: string` | `Promise` | Send invitation email | +| `verifyOtp()` | `params: VerifyOtpParams` | `Promise` | Verify OTP code | +| `resendOtp()` | `email: string` | `Promise` | Resend OTP code | +| `resetPasswordRequest()` | `email: string` | `Promise` | Request password reset | +| `resetPassword()` | `params: ResetPasswordParams` | `Promise` | Reset password with token | +| `changePassword()` | `params: ChangePasswordParams` | `Promise` | Change user password | + +--- + +## Examples + +### Register New User (Complete Flow) + +Registration requires email verification before login. Complete flow: + +1. **Register** - Create the user account +2. **Verification email sent** - User receives an OTP code +3. **Verify OTP** - User enters code to verify email +4. **Login** - User can now log in + +```javascript +try { + // Step 1: Register the user + await base44.auth.register({ + email: "user@example.com", + password: "securePassword123", + referral_code: "OPTIONAL_CODE", // optional + turnstile_token: "CAPTCHA_TOKEN" // optional, for bot protection + }); + console.log('Registration successful. Check email for OTP code.'); + + // Step 2: User receives email with OTP code (e.g., "123456") + + // Step 3: Verify the OTP code + await base44.auth.verifyOtp({ + email: "user@example.com", + otpCode: "123456" // code from verification email + }); + console.log('Email verified successfully.'); + + // Step 4: Now the user can log in + const loginResponse = await base44.auth.loginViaEmailPassword( + "user@example.com", + "securePassword123" + ); + console.log('Login successful:', loginResponse.user); + +} catch (error) { + console.error('Registration flow failed:', error.message); + // Handle specific errors (see Error Handling section) +} +``` + +> **Important**: Users cannot log in until they complete OTP verification. Attempting to call `loginViaEmailPassword` before verification will fail. + +### Login with Email/Password + +```javascript +try { + const response = await base44.auth.loginViaEmailPassword( + "user@example.com", + "password123", + turnstileToken // optional: for bot protection + ); + + console.log('Login successful'); + console.log('User:', response.user); + console.log('Token:', response.access_token); + + // JWT is automatically stored for subsequent requests + +} catch (error) { + console.error('Login failed:', error.message); + if (error.status === 401) { + console.error('Invalid credentials'); + } else if (error.status === 403) { + console.error('Email not verified. Please check your email for OTP.'); + } +} +``` + +### Login with OAuth Provider + +```javascript +// Redirect to Google OAuth +base44.auth.loginWithProvider('google'); + +// Redirect to Google OAuth and return to current page after +base44.auth.loginWithProvider('google', window.location.href); + +// Supported providers: 'google', 'microsoft', 'facebook' +base44.auth.loginWithProvider('microsoft'); +``` + +### Get Current User + +```javascript +try { + const user = await base44.auth.me(); + + if (user) { + console.log('User ID:', user.id); + console.log('Email:', user.email); + console.log('Name:', user.full_name); + console.log('Role:', user.role); + console.log('Verified:', user.is_verified); + } else { + console.log('User not authenticated'); + } + +} catch (error) { + console.error('Failed to fetch user:', error.message); + if (error.status === 401) { + // Token expired or invalid - navigate to your custom login page + navigate('/login'); + } +} +``` + +### Update User Profile + +```javascript +try { + const updatedUser = await base44.auth.updateMe({ + full_name: "John Doe", + // Custom schema fields can be updated here + phone: "+1234567890", + preferences: { theme: "dark" } + }); + + console.log('Profile updated:', updatedUser); + +} catch (error) { + console.error('Profile update failed:', error.message); + if (error.status === 400) { + console.error('Invalid data provided'); + } else if (error.status === 401) { + console.error('Authentication required'); + navigate('/login'); + } +} +``` + +### Check Authentication Status + +```javascript +try { + const isLoggedIn = await base44.auth.isAuthenticated(); + + if (isLoggedIn) { + // Show authenticated UI + console.log('User is authenticated'); + } else { + // Show login button + console.log('User is not authenticated'); + } + +} catch (error) { + console.error('Failed to check authentication:', error.message); + // On error, treat as not authenticated +} +``` + +### Logout + +```javascript +// Simple logout +base44.auth.logout(); + +// Logout and redirect to goodbye page +base44.auth.logout("/goodbye"); + +// Logout and redirect to homepage +base44.auth.logout("/"); +``` + +### Protected Route Pattern + +```javascript +// Using a navigation function (e.g., React Router's useNavigate, Next.js router) +async function requireAuth(navigate) { + try { + const user = await base44.auth.me(); + + if (!user) { + // Navigate to your custom login page + navigate('/login', { state: { returnTo: window.location.pathname } }); + return null; + } + + return user; + + } catch (error) { + console.error('Authentication check failed:', error.message); + navigate('/login', { state: { returnTo: window.location.pathname } }); + return null; + } +} + +// Usage in your app +async function loadProtectedPage(navigate) { + const user = await requireAuth(navigate); + if (!user) { + // Will navigate to login + return; + } + + // Continue with authenticated logic + console.log('Welcome,', user.full_name); +} +``` + +### Set Authentication Token + +```javascript +// SECURITY WARNING: Never hardcode tokens or expose them in client code +// Tokens should only be received from secure authentication flows + +// Set token and save to localStorage (default) +base44.auth.setToken(receivedToken); + +// Set token without saving to localStorage (temporary session) +base44.auth.setToken(receivedToken, false); + +// Verify token was set +try { + const isAuthenticated = await base44.auth.isAuthenticated(); + if (!isAuthenticated) { + console.error('Token validation failed'); + } +} catch (error) { + console.error('Failed to set token:', error.message); +} +``` + +### Invite User to Application + +```javascript +try { + // Note: Typically requires admin privileges + const response = await base44.auth.inviteUser( + "newuser@example.com", + "user" // or "admin" + ); + + console.log('Invitation sent successfully'); + +} catch (error) { + console.error('Failed to invite user:', error.message); + if (error.status === 403) { + console.error('Insufficient permissions to invite users'); + } else if (error.status === 400) { + console.error('Invalid email or role'); + } +} +``` + +### OTP Verification + +```javascript +try { + // Verify OTP code sent to user's email + await base44.auth.verifyOtp({ + email: "user@example.com", + otpCode: "123456" + }); + + console.log('OTP verified successfully'); + +} catch (error) { + console.error('OTP verification failed:', error.message); + if (error.status === 400) { + console.error('Invalid or expired OTP code'); + } else if (error.status === 429) { + console.error('Too many attempts. Please try again later.'); + } +} + +// Resend OTP if needed +try { + await base44.auth.resendOtp("user@example.com"); + console.log('OTP resent successfully'); + +} catch (error) { + console.error('Failed to resend OTP:', error.message); + if (error.status === 429) { + console.error('Too many requests. Please wait before trying again.'); + } +} +``` + +### Password Reset Flow + +```javascript +// Step 1: Request password reset +try { + await base44.auth.resetPasswordRequest("user@example.com"); + console.log('Password reset email sent. Check your inbox.'); + +} catch (error) { + console.error('Password reset request failed:', error.message); + if (error.status === 429) { + console.error('Too many requests. Please try again later.'); + } + // Note: For security, don't reveal if email exists +} + +// Step 2: Reset password with token from email +try { + await base44.auth.resetPassword({ + resetToken: "token-from-email", + newPassword: "newSecurePassword123" + }); + + console.log('Password reset successfully. You can now log in.'); + +} catch (error) { + console.error('Password reset failed:', error.message); + if (error.status === 400) { + console.error('Invalid or expired reset token'); + } else if (error.status === 422) { + console.error('Password does not meet requirements'); + } +} +``` + +### Change Password + +```javascript +try { + const currentUser = await base44.auth.me(); + + if (!currentUser) { + throw new Error('User must be authenticated to change password'); + } + + await base44.auth.changePassword({ + userId: currentUser.id, + currentPassword: "oldPassword123", + newPassword: "newSecurePassword456" + }); + + console.log('Password changed successfully'); + +} catch (error) { + console.error('Password change failed:', error.message); + if (error.status === 401) { + console.error('Current password is incorrect'); + } else if (error.status === 422) { + console.error('New password does not meet requirements'); + } else if (error.status === 403) { + console.error('Not authorized to change this password'); + } +} +``` + +--- + +## Error Handling + +### Common Error Scenarios + +The auth module can throw various errors. Here are common scenarios and how to handle them: + +#### Authentication Errors (401/403) +```javascript +try { + const user = await base44.auth.me(); +} catch (error) { + if (error.status === 401) { + // Token expired or invalid - navigate to your custom login page + navigate('/login'); + } else if (error.status === 403) { + // Email not verified or insufficient permissions + console.error('Access denied:', error.message); + } +} +``` + +#### Validation Errors (400/422) +```javascript +try { + await base44.auth.register({ + email: "invalid-email", + password: "weak" + }); +} catch (error) { + if (error.status === 400) { + console.error('Invalid input:', error.message); + // Handle validation errors + } else if (error.status === 422) { + console.error('Data validation failed:', error.details); + } +} +``` + +#### Rate Limiting (429) +```javascript +try { + await base44.auth.resendOtp("user@example.com"); +} catch (error) { + if (error.status === 429) { + console.error('Too many requests. Please wait before trying again.'); + // Show countdown or disable button temporarily + } +} +``` + +#### Generic Error Handler +```javascript +function handleAuthError(error) { + switch (error.status) { + case 400: + return 'Invalid input. Please check your data.'; + case 401: + return 'Authentication required. Redirecting to login...'; + case 403: + return 'Access denied. You may need to verify your email.'; + case 404: + return 'Resource not found.'; + case 422: + return 'Validation failed. Please check your input.'; + case 429: + return 'Too many requests. Please try again later.'; + case 500: + return 'Server error. Please try again.'; + default: + return 'An unexpected error occurred.'; + } +} + +// Usage +try { + await base44.auth.loginViaEmailPassword(email, password); +} catch (error) { + console.error(handleAuthError(error)); +} +``` + +--- + +## Auth Providers + +Configure authentication providers in your app dashboard: + +### Available Providers + +**Built-in (All Plans):** +- **Email/Password** - Default, always enabled +- **Google** - OAuth authentication +- **Microsoft** - OAuth authentication +- **Facebook** - OAuth authentication + +**SSO Providers (Elite Plan):** +- **Okta** +- **Azure AD** +- **GitHub** + +### Using OAuth Providers + +```javascript +// Initiate OAuth login flow +base44.auth.loginWithProvider('google'); + +// Return to specific page after authentication +base44.auth.loginWithProvider('microsoft', '/dashboard'); + +// Supported values: 'google', 'microsoft', 'facebook' +``` + +--- + +## Environment Availability + +| Environment | Availability | Notes | +|------------|--------------|-------| +| **Frontend** | ✅ Yes | All methods available | +| **Backend Functions** | ✅ Yes | Use `createClientFromRequest(req)` for authenticated client | +| **Service Role** | ❌ No | Auth methods not available in service role context | + +### Frontend Usage +```javascript +// Standard browser usage +const user = await base44.auth.me(); +``` + +### Backend Functions Usage +```javascript +import { createClientFromRequest } from "@base44/sdk"; + +Deno.serve(async (req) => { + const base44 = createClientFromRequest(req); + + // Get user from request context + const user = await base44.auth.me(); + + // User operations here... + return Response.json({ user }); +}); +``` + +--- + +## App Visibility + +Control who can access your app in the app settings: + +### Public Apps +- No login required for basic access +- Users can view public content without authentication +- Authenticated users get additional features/data + +### Private Apps +- Login required to access any content +- Unauthenticated users are automatically redirected to login +- All content is protected by default + +--- + +## Limitations + +### Authentication UI Options +- **Recommended:** Build custom login/signup UI using `loginViaEmailPassword()` and `loginWithProvider()` for full control over user experience and branding +- **Alternative:** `redirectToLogin()` uses Base44's hosted authentication pages with limited customization + +### Hosted Login (via redirectToLogin) +- `redirectToLogin()` shows both login and signup options on the same page +- No separate `redirectToSignup()` method +- Users can switch between login/signup on the hosted page +- ⚠️ **Note:** Prefer building custom login UI for better user experience + +### Password Requirements +- Minimum length and complexity requirements enforced +- Requirements not exposed via API +- Validation errors returned when requirements not met + +### Rate Limiting +- OTP requests are rate-limited to prevent abuse +- Password reset requests are rate-limited +- Login attempts may be rate-limited with Turnstile protection + +### Token Management +- JWTs are automatically stored in localStorage by default +- Token expiration and refresh not exposed in API +- Call `me()` or `isAuthenticated()` to verify token validity + +--- + +## Best Practices + +### 1. Always Handle Errors +```javascript +try { + await base44.auth.loginViaEmailPassword(email, password); +} catch (error) { + // Always handle authentication errors + console.error('Login failed:', error.message); +} +``` + +### 2. Verify Authentication Before Protected Actions +```javascript +const user = await requireAuth(); +if (!user) return; // Will redirect to login +// Proceed with authenticated action +``` + +### 3. Use Type Safety with TypeScript +```typescript +import type { User, LoginResponse } from '@base44/sdk'; + +const user: User = await base44.auth.me(); +const response: LoginResponse = await base44.auth.loginViaEmailPassword(email, password); +``` + +### 4. Don't Hardcode Credentials +```javascript +// ❌ BAD +base44.auth.setToken("hardcoded-token-here"); + +// ✅ GOOD +const token = await secureAuthFlow(); +base44.auth.setToken(token); +``` + +### 5. Provide User Feedback +```javascript +try { + await base44.auth.register(params); + showSuccessMessage('Registration successful! Check your email for verification code.'); +} catch (error) { + showErrorMessage('Registration failed: ' + error.message); +} +``` + +### 6. Handle Token Expiration Gracefully +```javascript +try { + const user = await base44.auth.me(); +} catch (error) { + if (error.status === 401) { + // Clear local state and navigate to login + localStorage.clear(); + navigate('/login'); + } +} +``` + +### 7. Build Custom Login UI (Recommended) +```javascript +// ✅ RECOMMENDED - Custom login form with direct methods +const handleLogin = async (email, password) => { + try { + const { user } = await base44.auth.loginViaEmailPassword(email, password); + navigate('/dashboard'); + } catch (error) { + setError(error.message); + } +}; + +const handleGoogleLogin = () => { + base44.auth.loginWithProvider('google', '/dashboard'); +}; + +// ❌ AVOID - Redirecting to hosted login page +// base44.auth.redirectToLogin(window.location.href); +``` diff --git a/evals/fixtures/cli-agents/skills/base44-sdk/references/base44-agents.md b/evals/fixtures/cli-agents/skills/base44-sdk/references/base44-agents.md new file mode 100644 index 0000000..ee55dee --- /dev/null +++ b/evals/fixtures/cli-agents/skills/base44-sdk/references/base44-agents.md @@ -0,0 +1,364 @@ +# Agents Module + +AI agent conversations and messages via `base44.agents`. + +> **Note:** This module requires a logged-in user. All agent methods work in the context of the authenticated user. + +## Contents +- [Concepts](#concepts) +- [Methods](#methods) +- [Examples](#examples) (Create, Get Conversations, List, Subscribe, Send Message, WhatsApp) +- [Message Structure](#message-structure) +- [Conversation Structure](#conversation-structure) +- [Common Patterns](#common-patterns) + +## Concepts + +- **Conversation**: A dialogue between user and an AI agent. Has unique ID, agent name, user reference, and metadata. +- **Message**: Single message in a conversation. Has role (`user`, `assistant`, `system`), content, timestamps, and optional metadata. + +## Methods + +| Method | Signature | Description | +|--------|-----------|-------------| +| `createConversation(params)` | `Promise` | Create a new conversation with an agent | +| `getConversations()` | `Promise` | Get all user's conversations | +| `getConversation(id)` | `Promise` | Get conversation with messages | +| `listConversations(filterParams)` | `Promise` | Filter/sort/paginate conversations | +| `subscribeToConversation(id, onUpdate?)` | `() => void` | Real-time updates via WebSocket (returns unsubscribe function) | +| `addMessage(conversation, message)` | `Promise` | Send a message | +| `getWhatsAppConnectURL(agentName)` | `string` | Get WhatsApp connection URL for agent | + +## Examples + +### Create Conversation + +```javascript +const conversation = await base44.agents.createConversation({ + agent_name: "support-agent", + metadata: { + order_id: "ORD-123", + category: "billing" + } +}); + +console.log(conversation.id); +``` + +### Get All Conversations + +```javascript +const conversations = await base44.agents.getConversations(); + +conversations.forEach(conv => { + console.log(conv.id, conv.agent_name, conv.created_date); +}); +``` + +### Get Single Conversation (with messages) + +```javascript +const conversation = await base44.agents.getConversation("conv-id-123"); + +console.log(conversation.messages); +``` + +### List with Filters + +```javascript +// Using filterParams object with q, sort, limit, skip, fields +const recent = await base44.agents.listConversations({ + q: { agent_name: "support-agent" }, + sort: "-created_date", + limit: 10, + skip: 0 +}); + +// Filter by metadata +const highPriority = await base44.agents.listConversations({ + q: { + agent_name: "support-agent", + "metadata.priority": "high" + }, + sort: "-updated_date", + limit: 20 +}); +``` + +### Subscribe to Updates (Real-time) + +```javascript +const unsubscribe = base44.agents.subscribeToConversation( + "conv-id-123", + (updatedConversation) => { + // Called when new messages arrive + console.log("New messages:", updatedConversation.messages); + } +); + +// Later: unsubscribe +unsubscribe(); +``` + +### Send a Message + +```javascript +const conversation = await base44.agents.getConversation("conv-id-123"); + +await base44.agents.addMessage(conversation, { + role: "user", + content: "What's the weather like today?" +}); +``` + +### Get WhatsApp Connection URL + +```javascript +const whatsappUrl = base44.agents.getWhatsAppConnectURL("support-agent"); +// Returns URL for users to connect with agent via WhatsApp +console.log(whatsappUrl); +``` + +## Message Structure + +```javascript +{ + role: "user" | "assistant" | "system", + content: "Message text or structured object", + created_date: "2024-01-15T10:30:00Z", + updated_date: "2024-01-15T10:30:00Z", + + // Optional fields + reasoning: { + content: "Agent's reasoning process", + timing: 1500 + }, + tool_calls: [{ + name: "search", + arguments: { query: "weather" }, + result: { ... }, + status: "success" + }], + file_urls: ["https://..."], + usage: { + prompt_tokens: 150, + completion_tokens: 50 + }, + metadata: { ... }, + custom_context: { ... } +} +``` + +## Conversation Structure + +```javascript +{ + id: "conv-id-123", + app_id: "app-id", + agent_name: "support-agent", + created_by_id: "user-id", + created_date: "2024-01-15T10:00:00Z", + updated_date: "2024-01-15T10:30:00Z", + messages: [ ... ], + metadata: { ... } +} +``` + +## Common Patterns + +### Chat Interface + +```javascript +// Load conversation +const conv = await base44.agents.getConversation(conversationId); +setMessages(conv.messages); + +// Subscribe to updates +const unsubscribe = base44.agents.subscribeToConversation(conversationId, (updated) => { + setMessages(updated.messages); +}); + +// Send message +async function sendMessage(text) { + await base44.agents.addMessage(conv, { role: "user", content: text }); +} + +// Cleanup on unmount +return () => unsubscribe(); +``` + +## Type Definitions + +### AgentConversation + +```typescript +/** An agent conversation containing messages exchanged with an AI agent. */ +interface AgentConversation { + /** Unique identifier for the conversation. */ + id: string; + /** Application ID. */ + app_id: string; + /** Name of the agent in this conversation. */ + agent_name: string; + /** ID of the user who created the conversation. */ + created_by_id: string; + /** When the conversation was created. */ + created_date: string; + /** When the conversation was last updated. */ + updated_date: string; + /** Array of messages in the conversation. */ + messages: AgentMessage[]; + /** Optional metadata associated with the conversation. */ + metadata?: Record; +} +``` + +### AgentMessage + +```typescript +/** A message in an agent conversation. */ +interface AgentMessage { + /** Unique identifier for the message. */ + id: string; + /** Role of the message sender. */ + role: "user" | "assistant" | "system"; + /** When the message was created. */ + created_date: string; + /** When the message was last updated. */ + updated_date: string; + /** Message content. */ + content?: string | Record; + /** Optional reasoning information for the message. */ + reasoning?: AgentMessageReasoning | null; + /** URLs to files attached to the message. */ + file_urls?: string[]; + /** Tool calls made by the agent. */ + tool_calls?: AgentMessageToolCall[]; + /** Token usage statistics. */ + usage?: AgentMessageUsage; + /** Whether the message is hidden from the user. */ + hidden?: boolean; + /** Custom context provided with the message. */ + custom_context?: AgentMessageCustomContext[]; + /** Model used to generate the message. */ + model?: string; + /** Checkpoint ID for the message. */ + checkpoint_id?: string; + /** Metadata about when and by whom the message was created. */ + metadata?: AgentMessageMetadata; +} +``` + +### Supporting Types + +```typescript +/** Reasoning information for an agent message. */ +interface AgentMessageReasoning { + /** When reasoning started. */ + start_date: string; + /** When reasoning ended. */ + end_date?: string; + /** Reasoning content. */ + content: string; +} + +/** A tool call made by the agent. */ +interface AgentMessageToolCall { + /** Tool call ID. */ + id: string; + /** Name of the tool called. */ + name: string; + /** Arguments passed to the tool as JSON string. */ + arguments_string: string; + /** Status of the tool call. */ + status: "running" | "success" | "error" | "stopped"; + /** Results from the tool call. */ + results?: string; +} + +/** Token usage statistics for an agent message. */ +interface AgentMessageUsage { + /** Number of tokens in the prompt. */ + prompt_tokens?: number; + /** Number of tokens in the completion. */ + completion_tokens?: number; +} + +/** Custom context provided with an agent message. */ +interface AgentMessageCustomContext { + /** Context message. */ + message: string; + /** Associated data for the context. */ + data: Record; + /** Type of context. */ + type: string; +} + +/** Metadata about when and by whom a message was created. */ +interface AgentMessageMetadata { + /** When the message was created. */ + created_date: string; + /** Email of the user who created the message. */ + created_by_email: string; + /** Full name of the user who created the message. */ + created_by_full_name: string; +} +``` + +### CreateConversationParams + +```typescript +/** Parameters for creating a new conversation. */ +interface CreateConversationParams { + /** The name of the agent to create a conversation with. */ + agent_name: string; + /** Optional metadata to attach to the conversation. */ + metadata?: Record; +} +``` + +### ModelFilterParams + +```typescript +/** Parameters for filtering, sorting, and paginating conversations. */ +interface ModelFilterParams { + /** Query object with field-value pairs for filtering. */ + q?: Record; + /** Sort parameter (e.g., "-created_date" for descending). */ + sort?: string | null; + /** Maximum number of results to return. */ + limit?: number | null; + /** Number of results to skip for pagination. */ + skip?: number | null; + /** Array of field names to include in the response. */ + fields?: string[] | null; +} +``` + +### AgentsModule + +```typescript +/** Agents module for managing AI agent conversations. */ +interface AgentsModule { + /** Gets all conversations from all agents in the app. */ + getConversations(): Promise; + + /** Gets a specific conversation by ID. */ + getConversation(conversationId: string): Promise; + + /** Lists conversations with filtering, sorting, and pagination. */ + listConversations(filterParams: ModelFilterParams): Promise; + + /** Creates a new conversation with an agent. */ + createConversation(conversation: CreateConversationParams): Promise; + + /** Adds a message to a conversation. */ + addMessage(conversation: AgentConversation, message: Partial): Promise; + + /** Subscribes to real-time updates for a conversation. Returns unsubscribe function. */ + subscribeToConversation(conversationId: string, onUpdate?: (conversation: AgentConversation) => void): () => void; + + /** Gets WhatsApp connection URL for an agent. */ + getWhatsAppConnectURL(agentName: string): string; +} +``` diff --git a/evals/fixtures/cli-agents/skills/base44-sdk/references/client.md b/evals/fixtures/cli-agents/skills/base44-sdk/references/client.md new file mode 100644 index 0000000..554bf2b --- /dev/null +++ b/evals/fixtures/cli-agents/skills/base44-sdk/references/client.md @@ -0,0 +1,257 @@ +# Client Setup + +How to create and configure the Base44 client. + +## Contents +- [In Base44-Generated Apps](#in-base44-generated-apps) +- [In External Apps](#in-external-apps) +- [In Backend Functions](#in-backend-functions) +- [Authentication Modes](#authentication-modes) (Anonymous, User, Service Role) +- [Available Modules](#available-modules) +- [Client Methods](#client-methods) +- [Client Configuration Options](#client-configuration-options) + +## In Base44-Generated Apps + +The client is pre-configured and available as `base44`. Just use it: + +```javascript +const tasks = await base44.entities.Task.list(); +``` + +## In External Apps + +Install the SDK and create a client: + +```bash +npm install @base44/sdk +``` + +```javascript +import { createClient } from "@base44/sdk"; + +// IMPORTANT: The parameter name is 'appId' (NOT 'clientId', NOT 'id') +// IMPORTANT: onError must be nested inside 'options' object +const base44 = createClient({ + appId: "your-app-id", // Required: Use 'appId' parameter + token: "optional-user-token", // Optional: for pre-authenticated requests + options: { // Optional: configuration options + onError: (error) => { // Optional: error handler (must be in options) + console.error("Base44 error:", error); + } + } +}); +``` + +**Common Mistakes:** +- ❌ `createClient({ clientId: "..." })` - WRONG parameter name +- ❌ `createClient({ id: "..." })` - WRONG parameter name +- ❌ `createClient({ appId: "...", onError: ... })` - WRONG: onError must be in options +- ✅ `createClient({ appId: "..." })` - CORRECT parameter name +- ✅ `createClient({ appId: "...", options: { onError: ... } })` - CORRECT: onError in options + +## In Backend Functions + +Use `createClientFromRequest` to get a client with the caller's auth context: + +```javascript +import { createClientFromRequest } from "@base44/sdk"; + +Deno.serve(async (req) => { + const base44 = createClientFromRequest(req); + + // Client inherits authentication from the request + const user = await base44.auth.me(); + + return Response.json({ user }); +}); +``` + +## Authentication Modes + +| Mode | How to Get | Permissions | +|------|-----------|-------------| +| **Anonymous** | `createClient({ appId })` without token | Public data only | +| **User** | After `loginViaEmailPassword()` or via `createClientFromRequest` | User's own data | +| **Service Role** | `base44.asServiceRole.*` in backend | Full admin access | + +## Anonymous Mode + +No authentication. Can only access public resources. + +```javascript +const base44 = createClient({ appId: "your-app-id" }); + +// Only works if Task entity allows anonymous read +const publicTasks = await base44.entities.Task.list(); +``` + +## User Mode + +After user logs in, the client automatically includes their token. + +```javascript +const base44 = createClient({ appId: "your-app-id" }); + +// Login sets the token +await base44.auth.loginViaEmailPassword("user@example.com", "password"); + +// Subsequent requests are authenticated +const user = await base44.auth.me(); +const myTasks = await base44.entities.Task.list(); // filtered by permissions +``` + +## Service Role Mode + +Admin-level access. **Backend only.** + +```javascript +// Inside a backend function +Deno.serve(async (req) => { + const base44 = createClientFromRequest(req); + + // User mode - respects permissions + const myTasks = await base44.entities.Task.list(); + + // Service role - bypasses permissions + const allTasks = await base44.asServiceRole.entities.Task.list(); + const allUsers = await base44.asServiceRole.entities.User.list(); + const oauthToken = await base44.asServiceRole.connectors.getAccessToken("slack"); + + return Response.json({ myTasks, allTasks }); +}); +``` + +## Available Modules + +The client exposes these modules: + +```javascript +base44.entities // CRUD operations +base44.auth // Authentication +base44.agents // AI conversations +base44.functions // Backend function invocation +base44.integrations // Third-party services +base44.analytics // Event tracking +base44.appLogs // App usage logging +base44.users // User invitations + +// Service role only (backend) +base44.asServiceRole.entities +base44.asServiceRole.agents +base44.asServiceRole.functions +base44.asServiceRole.integrations +base44.asServiceRole.appLogs +base44.asServiceRole.connectors +``` + +## Client Methods + +The client provides these methods: + +```javascript +// Set authentication token for all subsequent requests +base44.setToken(newToken); + +// Cleanup WebSocket connections (call when done with client) +base44.cleanup(); +``` + +### setToken + +Updates the authentication token for all subsequent API requests and WebSocket connections. + +```javascript +// After receiving a token (e.g., from external auth) +base44.setToken("new-jwt-token"); +``` + +### cleanup + +Disconnects WebSocket connections. Call when you're done with the client or when the component unmounts. + +```javascript +// Cleanup on component unmount (React example) +useEffect(() => { + return () => base44.cleanup(); +}, []); +``` + +## Client Configuration Options + +```javascript +createClient({ + appId: "your-app-id", // Required: MUST use 'appId' (not 'clientId' or 'id') + token: "jwt-token", // Optional: pre-set auth token + options: { // Optional: configuration options + onError: (error) => {} // Optional: global error handler (must be in options) + } +}); +``` + +**⚠️ Critical:** +- The parameter name is `appId`, not `clientId` or `id`. Using the wrong parameter name will cause errors. +- The `onError` handler must be nested inside the `options` object, not at the top level. + +## Type Definitions + +### CreateClientConfig + +```typescript +/** Configuration for creating a Base44 client. */ +interface CreateClientConfig { + /** The Base44 app ID (required). */ + appId: string; + /** User authentication token. Used to authenticate as a specific user. */ + token?: string; + /** Service role authentication token (backend only). */ + serviceToken?: string; + /** Additional client options. */ + options?: CreateClientOptions; +} + +/** Options for creating a Base44 client. */ +interface CreateClientOptions { + /** Optional error handler called whenever an API error occurs. */ + onError?: (error: Error) => void; +} +``` + +### Base44Client + +```typescript +/** The Base44 client instance. */ +interface Base44Client { + /** Entities module for CRUD operations on your data models. */ + entities: EntitiesModule; + /** Integrations module for calling pre-built integration endpoints. */ + integrations: IntegrationsModule; + /** Auth module for user authentication and management. */ + auth: AuthModule; + /** Functions module for invoking custom backend functions. */ + functions: FunctionsModule; + /** Agents module for managing AI agent conversations. */ + agents: AgentsModule; + /** App logs module for tracking app usage. */ + appLogs: AppLogsModule; + /** Analytics module for tracking custom events. */ + analytics: AnalyticsModule; + + /** Cleanup function to disconnect WebSocket connections. */ + cleanup(): void; + + /** Sets a new authentication token for all subsequent requests. */ + setToken(newToken: string): void; + + /** Provides access to modules with elevated service role permissions (backend only). */ + readonly asServiceRole: { + entities: EntitiesModule; + integrations: IntegrationsModule; + connectors: ConnectorsModule; + functions: FunctionsModule; + agents: AgentsModule; + appLogs: AppLogsModule; + cleanup(): void; + }; +} +``` diff --git a/evals/fixtures/cli-agents/skills/base44-sdk/references/connectors.md b/evals/fixtures/cli-agents/skills/base44-sdk/references/connectors.md new file mode 100644 index 0000000..f3fbcb4 --- /dev/null +++ b/evals/fixtures/cli-agents/skills/base44-sdk/references/connectors.md @@ -0,0 +1,113 @@ +# Connectors Module + +OAuth token management for external services. **Service role only** (backend functions). + +## Method + +```javascript +base44.asServiceRole.connectors.getAccessToken(integrationType): Promise +``` + +Returns a raw OAuth access token for the specified service. + +## Supported Services + +| Integration Type | Service | +|-----------------|---------| +| `"googlecalendar"` | Google Calendar | +| `"googledrive"` | Google Drive | +| `"slack"` | Slack | +| `"notion"` | Notion | +| `"salesforce"` | Salesforce | +| `"hubspot"` | HubSpot | +| `"linkedin"` | LinkedIn | +| `"tiktok"` | TikTok | +| `"github"` | GitHub | + +## Example Usage + +```javascript +// Backend function only +Deno.serve(async (req) => { + const base44 = createClientFromRequest(req); + + // Get OAuth token for Slack + const slackToken = await base44.asServiceRole.connectors.getAccessToken("slack"); + + // Use token directly with Slack API + const response = await fetch("https://slack.com/api/chat.postMessage", { + method: "POST", + headers: { + "Authorization": `Bearer ${slackToken}`, + "Content-Type": "application/json" + }, + body: JSON.stringify({ + channel: "#general", + text: "Hello from Base44!" + }) + }); + + return Response.json(await response.json()); +}); +``` + +## Google Calendar Example + +```javascript +Deno.serve(async (req) => { + const base44 = createClientFromRequest(req); + + const token = await base44.asServiceRole.connectors.getAccessToken("googlecalendar"); + + // List upcoming events + const response = await fetch( + "https://www.googleapis.com/calendar/v3/calendars/primary/events?" + + new URLSearchParams({ + maxResults: "10", + orderBy: "startTime", + singleEvents: "true", + timeMin: new Date().toISOString() + }), + { + headers: { "Authorization": `Bearer ${token}` } + } + ); + + const events = await response.json(); + return Response.json(events); +}); +``` + +## Setup Requirements + +1. **Builder plan** or higher +2. **Backend functions** enabled +3. **Connector configured** in Base44 dashboard (OAuth flow completed) + +## Important Notes + +- **One account per connector per app**: All users share the same connected account +- **Backend only**: `connectors` module not available in frontend code +- **Service role required**: Must use `base44.asServiceRole.connectors` +- **You handle the API calls**: Base44 only provides the token; you make the actual API requests +- **Token refresh**: Base44 handles token refresh automatically + +## Type Definitions + +```typescript +/** + * The type of external integration/connector. + * Examples: 'googlecalendar', 'slack', 'github', 'notion', etc. + */ +type ConnectorIntegrationType = string; + +/** Connectors module for managing OAuth tokens for external services. */ +interface ConnectorsModule { + /** + * Retrieves an OAuth access token for a specific external integration type. + * @param integrationType - The type of integration (e.g., 'googlecalendar', 'slack'). + * @returns Promise resolving to the access token string. + */ + getAccessToken(integrationType: ConnectorIntegrationType): Promise; +} +``` diff --git a/evals/fixtures/cli-agents/skills/base44-sdk/references/entities.md b/evals/fixtures/cli-agents/skills/base44-sdk/references/entities.md new file mode 100644 index 0000000..a7bdf35 --- /dev/null +++ b/evals/fixtures/cli-agents/skills/base44-sdk/references/entities.md @@ -0,0 +1,253 @@ +# Entities Module + +CRUD operations on data models. Access via `base44.entities.EntityName.method()`. + +## Contents +- [Methods](#methods) +- [Examples](#examples) (Create, Bulk Create, List, Filter, Get, Update, Delete, Subscribe) +- [User Entity](#user-entity) +- [Service Role Access](#service-role-access) +- [Permissions](#permissions) + +## Methods + +| Method | Signature | Description | +|--------|-----------|-------------| +| `create(data)` | `Promise` | Create one record | +| `bulkCreate(dataArray)` | `Promise` | Create multiple records | +| `list(sort?, limit?, skip?, fields?)` | `Promise` | Get all records (paginated) | +| `filter(query, sort?, limit?, skip?, fields?)` | `Promise` | Get records matching conditions | +| `get(id)` | `Promise` | Get single record by ID | +| `update(id, data)` | `Promise` | Update record (partial update) | +| `delete(id)` | `Promise` | Delete record by ID | +| `deleteMany(query)` | `Promise` | Delete all matching records | +| `importEntities(file)` | `Promise` | Import from CSV (frontend only) | +| `subscribe(callback)` | `() => void` | Subscribe to realtime updates (returns unsubscribe function) | + +## Examples + +### Create + +```javascript +const task = await base44.entities.Task.create({ + title: "Complete documentation", + status: "pending", + dueDate: "2024-12-31" +}); +``` + +### Bulk Create + +```javascript +const tasks = await base44.entities.Task.bulkCreate([ + { title: "Task 1", status: "pending" }, + { title: "Task 2", status: "pending" } +]); +``` + +### List with Pagination + +```javascript +// Get first 10 records, sorted by created_date descending +const tasks = await base44.entities.Task.list( + "-created_date", // sort (string: prefix with - for descending) + 10, // limit + 0 // skip +); + +// Get next page +const page2 = await base44.entities.Task.list("-created_date", 10, 10); +``` + +### Filter + +```javascript +// Simple filter +const pending = await base44.entities.Task.filter({ status: "pending" }); + +// Multiple conditions +const myPending = await base44.entities.Task.filter({ + status: "pending", + assignedTo: userId +}); + +// With sort, limit, skip +const recent = await base44.entities.Task.filter( + { status: "pending" }, + "-created_date", // sort (string: prefix with - for descending) + 5, + 0 +); + +// Select specific fields +const titles = await base44.entities.Task.filter( + { status: "pending" }, + null, + null, + null, + ["id", "title"] +); +``` + +### Get by ID + +```javascript +const task = await base44.entities.Task.get("task-id-123"); +``` + +### Update + +```javascript +// Partial update - only specified fields change +await base44.entities.Task.update("task-id-123", { + status: "completed", + completedAt: new Date().toISOString() +}); +``` + +### Delete + +```javascript +// Single record +await base44.entities.Task.delete("task-id-123"); + +// Multiple records matching query +await base44.entities.Task.deleteMany({ status: "archived" }); +``` + +### Subscribe to Realtime Updates + +```javascript +// Subscribe to all changes on Task entity +const unsubscribe = base44.entities.Task.subscribe((event) => { + console.log(`Task ${event.id} was ${event.type}:`, event.data); + // event.type is "create", "update", or "delete" +}); + +// Later: unsubscribe to stop receiving updates +unsubscribe(); +``` + +**Event structure:** +```javascript +{ + type: "create" | "update" | "delete", + data: { ... }, // the entity data + id: "entity-id", // the affected entity's ID + timestamp: "2024-01-15T10:30:00Z" +} +``` + +## User Entity + +Every app has a built-in `User` entity with special rules: + +- Regular users can only read/update **their own** record +- Cannot create users via `entities.create()` - use `auth.register()` instead +- Service role has full access to all user records + +```javascript +// Get current user's record +const me = await base44.entities.User.get(currentUserId); + +// Service role: get any user +const anyUser = await base44.asServiceRole.entities.User.get(userId); +``` + +## Service Role Access + +For admin-level operations (bypass user permissions): + +```javascript +// Backend only +const allTasks = await base44.asServiceRole.entities.Task.list(); +const allUsers = await base44.asServiceRole.entities.User.list(); +``` + +## Permissions (RLS & FLS) + +Data access is controlled by **Row Level Security (RLS)** and **Field Level Security (FLS)** rules defined in entity schemas. + +1. **Authentication level**: anonymous, authenticated, or service role +2. **RLS rules**: Control which records (rows) users can create/read/update/delete +3. **FLS rules**: Control which fields users can read/write within accessible records + +Operations succeed or fail based on these rules - no partial results. + +RLS and FLS are configured in entity schema files (`base44/entities/*.jsonc`). See [entities-create.md](../../base44-cli/references/entities-create.md#row-level-security-rls) for configuration details. + +**Note:** `asServiceRole` sets the user's role to `"admin"` but does NOT bypass RLS. Your RLS rules must include admin access (e.g., `{ "user_condition": { "role": "admin" } }`) for service role operations to succeed. + +## Type Definitions + +### RealtimeEvent + +```typescript +/** Event types for realtime entity updates. */ +type RealtimeEventType = "create" | "update" | "delete"; + +/** Payload received when a realtime event occurs. */ +interface RealtimeEvent { + /** The type of change that occurred. */ + type: RealtimeEventType; + /** The entity data. */ + data: any; + /** The unique identifier of the affected entity. */ + id: string; + /** ISO 8601 timestamp of when the event occurred. */ + timestamp: string; +} + +/** Callback function invoked when a realtime event occurs. */ +type RealtimeCallback = (event: RealtimeEvent) => void; + +/** Function returned from subscribe, call it to unsubscribe. */ +type Subscription = () => void; +``` + +### EntityHandler + +```typescript +/** Entity handler providing CRUD operations for a specific entity type. */ +interface EntityHandler { + /** Lists records with optional pagination and sorting. */ + list(sort?: string, limit?: number, skip?: number, fields?: string[]): Promise; + + /** Filters records based on a query. */ + filter(query: Record, sort?: string, limit?: number, skip?: number, fields?: string[]): Promise; + + /** Gets a single record by ID. */ + get(id: string): Promise; + + /** Creates a new record. */ + create(data: Record): Promise; + + /** Updates an existing record. */ + update(id: string, data: Record): Promise; + + /** Deletes a single record by ID. */ + delete(id: string): Promise; + + /** Deletes multiple records matching a query. */ + deleteMany(query: Record): Promise; + + /** Creates multiple records in a single request. */ + bulkCreate(data: Record[]): Promise; + + /** Imports records from a file (frontend only). */ + importEntities(file: File): Promise; + + /** Subscribes to realtime updates. Returns unsubscribe function. */ + subscribe(callback: RealtimeCallback): Subscription; +} +``` + +### EntitiesModule + +```typescript +/** Entities module for managing app data. */ +interface EntitiesModule { + /** Access any entity by name dynamically. */ + [entityName: string]: EntityHandler; +} +``` diff --git a/evals/fixtures/cli-agents/skills/base44-sdk/references/functions.md b/evals/fixtures/cli-agents/skills/base44-sdk/references/functions.md new file mode 100644 index 0000000..8072465 --- /dev/null +++ b/evals/fixtures/cli-agents/skills/base44-sdk/references/functions.md @@ -0,0 +1,216 @@ +# Functions Module + +Invoke custom backend functions via `base44.functions`. + +## Contents +- [Method](#method) +- [Invoking Functions](#invoking-functions) (Frontend, File Upload, Service Role, REST API) +- [Writing Backend Functions](#writing-backend-functions) (Basic, Service Role, Secrets, Errors) +- [Setup Requirements](#setup-requirements) +- [Authentication Modes](#authentication-modes) + +## Method + +```javascript +base44.functions.invoke(functionName, data): Promise +``` + +- `functionName`: Name of the backend function +- `data`: Object of parameters (sent as JSON, or multipart if contains File objects) +- Returns: Whatever the function returns + +## Invoking Functions + +### From Frontend + +```javascript +const result = await base44.functions.invoke("processOrder", { + orderId: "order-123", + action: "ship" +}); + +console.log(result); +``` + +### With File Upload + +```javascript +const fileInput = document.querySelector('input[type="file"]'); +const file = fileInput.files[0]; + +// Automatically uses multipart/form-data when File objects present +const result = await base44.functions.invoke("uploadDocument", { + file: file, + category: "invoices" +}); +``` + +### With Service Role (Backend) + +```javascript +// Inside another backend function +const result = await base44.asServiceRole.functions.invoke("adminTask", { + userId: "user-123" +}); +``` + +### Via REST API (curl) + +Functions can be called via HTTP POST to your app domain: + +```bash +curl -X POST "https:///functions/" \ + -H "Content-Type: application/json" \ + -d '{"key": "value"}' +``` + +## Writing Backend Functions + +Backend functions run on Deno. Must export using `Deno.serve()`. + +### Required Directory Structure + +Each function must be in its own subdirectory under `base44/functions/` with a configuration file: + +``` +base44/ + functions/ + process-order/ # kebab-case directory name + function.jsonc # required configuration + index.ts # entry point +``` + +**function.jsonc:** +```jsonc +{ + "name": "process-order", + "entry": "index.ts" +} +``` + +For complete setup and deployment instructions, see [functions-create.md](../../base44-cli/references/functions-create.md) in base44-cli. + +### Basic Structure + +```javascript +// base44/functions/process-order/index.ts +import { createClientFromRequest } from "npm:@base44/sdk"; + +Deno.serve(async (req) => { + // Get authenticated client from request + const base44 = createClientFromRequest(req); + + // Parse input + const { orderId, action } = await req.json(); + + // Your logic here + const order = await base44.entities.Orders.get(orderId); + + // Return response + return Response.json({ + success: true, + order: order + }); +}); +``` + +### With Service Role Access + +```javascript +import { createClientFromRequest } from "npm:@base44/sdk"; + +Deno.serve(async (req) => { + const base44 = createClientFromRequest(req); + + // Check user is authenticated + const user = await base44.auth.me(); + if (!user) { + return Response.json({ error: "Unauthorized" }, { status: 401 }); + } + + // Use service role for admin operations + const allOrders = await base44.asServiceRole.entities.Orders.list(); + + return Response.json({ orders: allOrders }); +}); +``` + +### Using Secrets + +```javascript +Deno.serve(async (req) => { + // Access environment variables (configured in app settings) + const apiKey = Deno.env.get("STRIPE_API_KEY"); + + const response = await fetch("https://api.stripe.com/v1/charges", { + headers: { + "Authorization": `Bearer ${apiKey}` + } + }); + + return Response.json(await response.json()); +}); +``` + +### Error Handling + +```javascript +import { createClientFromRequest } from "npm:@base44/sdk"; + +Deno.serve(async (req) => { + try { + const base44 = createClientFromRequest(req); + const { orderId } = await req.json(); + + const order = await base44.entities.Orders.get(orderId); + if (!order) { + return Response.json( + { error: "Order not found" }, + { status: 404 } + ); + } + + return Response.json({ order }); + + } catch (error) { + return Response.json( + { error: error.message }, + { status: 500 } + ); + } +}); +``` + +## Setup Requirements + +1. Enable Backend Functions in app settings (requires appropriate plan) +2. Create function files in `/functions` folder +3. Configure secrets via app dashboard for API keys + +## Authentication Modes + +| Mode | Context | Permissions | +|------|---------|-------------| +| User | `base44.functions.invoke()` | Runs under calling user's permissions | +| Service Role | `base44.asServiceRole.functions.invoke()` | Admin-level access | + +Inside the function, use `createClientFromRequest(req)` to get a client that inherits the caller's auth context. + +## Type Definitions + +```typescript +/** Functions module for invoking custom backend functions. */ +interface FunctionsModule { + /** + * Invokes a custom backend function by name. + * + * If any parameter is a File object, the request will automatically be + * sent as multipart/form-data. Otherwise, it will be sent as JSON. + * + * @param functionName - The name of the function to invoke. + * @param data - An object containing named parameters for the function. + * @returns Promise resolving to the function's response. + */ + invoke(functionName: string, data: Record): Promise; +} +``` diff --git a/evals/fixtures/cli-agents/skills/base44-sdk/references/integrations.md b/evals/fixtures/cli-agents/skills/base44-sdk/references/integrations.md new file mode 100644 index 0000000..f356a18 --- /dev/null +++ b/evals/fixtures/cli-agents/skills/base44-sdk/references/integrations.md @@ -0,0 +1,377 @@ +# Integrations Module + +Access third-party services via `base44.integrations`. + +## Types of Integrations + +1. **Core/Built-in**: AI, email, file uploads (available by default) +2. **Catalog integrations**: Pre-built connectors from Base44 catalog +3. **Custom integrations**: Your own OpenAPI-based integrations + +## Accessing Integrations + +```javascript +// Core integrations +base44.integrations.Core.FunctionName(params) + +// Custom integrations +base44.integrations.custom.call(slug, operationId, params) +``` + +## Core Integrations + +### InvokeLLM (AI Text Generation) + +Generate text or structured JSON data using AI models. + +```javascript +// Basic prompt - returns string +const response = await base44.integrations.Core.InvokeLLM({ + prompt: "Summarize this text: ..." +}); + +// With internet context (uses Google Search, Maps, News) +const response = await base44.integrations.Core.InvokeLLM({ + prompt: "What's the current weather in NYC?", + add_context_from_internet: true +}); + +// Structured JSON response - returns object +const response = await base44.integrations.Core.InvokeLLM({ + prompt: "Analyze the sentiment of: 'Great product but slow shipping'", + response_json_schema: { + type: "object", + properties: { + sentiment: { type: "string", enum: ["positive", "negative", "mixed"] }, + score: { type: "number", description: "Score from 1-10" }, + key_points: { type: "array", items: { type: "string" } } + } + } +}); +// Returns: { sentiment: "mixed", score: 7, key_points: ["great product", "slow shipping"] } + +// With file attachments (uploaded via UploadFile) +const response = await base44.integrations.Core.InvokeLLM({ + prompt: "Describe what's in this image", + file_urls: ["https://...uploaded_image.png"] +}); +``` + +**Parameters:** +- `prompt` (string, required): The prompt text to send to the model +- `add_context_from_internet` (boolean, optional): If true, uses Google Search/Maps/News for real-time context +- `response_json_schema` (object, optional): JSON schema for structured output +- `file_urls` (string[], optional): URLs of uploaded files for context + +### GenerateImage + +Create AI-generated images from text prompts. + +```javascript +const { url } = await base44.integrations.Core.GenerateImage({ + prompt: "A serene mountain landscape with a lake" +}); +console.log(url); // https://...generated_image.png +``` + +### SendEmail + +Send emails to registered users. Every app gets this integration (no plan upgrade required). + +```javascript +await base44.integrations.Core.SendEmail({ + to: "user@example.com", + subject: "Welcome!", + body: "

Hello

Welcome to our app.

", + from_name: "My App" // optional, defaults to app name +}); +``` + +**Parameters:** +- `to` (string, required): Recipient email address +- `subject` (string, required): Email subject line +- `body` (string, required): Plain text or HTML email body +- `from_name` (string, optional): Sender name displayed to recipient + +**Limitations:** +- 1 credit per email (2 credits with custom domain) + +### UploadFile (Public) + +Upload files to public storage. + +```javascript +const fileInput = document.querySelector('input[type="file"]'); +const { file_url } = await base44.integrations.Core.UploadFile({ + file: fileInput.files[0] +}); +console.log(file_url); // https://...uploaded_file.pdf +``` + +### UploadPrivateFile + +Upload files to private storage that requires a signed URL to access. + +```javascript +const { file_uri } = await base44.integrations.Core.UploadPrivateFile({ + file: fileInput.files[0] +}); +console.log(file_uri); // "private/user123/document.pdf" + +// Create a signed URL to access the file +const { signed_url } = await base44.integrations.Core.CreateFileSignedUrl({ + file_uri, + expires_in: 3600 // URL expires in 1 hour (default: 300 seconds) +}); +console.log(signed_url); // Temporary URL to access the private file +``` + +### CreateFileSignedUrl + +Generate temporary access links for private files. + +```javascript +const { signed_url } = await base44.integrations.Core.CreateFileSignedUrl({ + file_uri: "private/user123/document.pdf", + expires_in: 7200 // 2 hours +}); +``` + +**Parameters:** +- `file_uri` (string, required): URI from UploadPrivateFile +- `expires_in` (number, optional): Expiration time in seconds (default: 300) + +### ExtractDataFromUploadedFile + +Extract structured data from uploaded files using AI. + +```javascript +// First upload the file +const { file_url } = await base44.integrations.Core.UploadFile({ file }); + +// Then extract structured data +const result = await base44.integrations.Core.ExtractDataFromUploadedFile({ + file_url, + json_schema: { + type: "object", + properties: { + invoice_number: { type: "string" }, + total_amount: { type: "number" }, + date: { type: "string" }, + vendor_name: { type: "string" } + } + } +}); +console.log(result); // { invoice_number: "INV-12345", total_amount: 1250.00, ... } +``` + +## Custom Integrations + +Custom integrations allow workspace administrators to connect any external API by importing an OpenAPI specification. Use `base44.integrations.custom.call()` to invoke them. + +### Syntax + +```javascript +const response = await base44.integrations.custom.call( + slug, // Integration identifier (set by admin) + operationId, // Endpoint in "method:path" format + params // Optional: payload, pathParams, queryParams +); +``` + +### Examples + +```javascript +// GET request with query params +const response = await base44.integrations.custom.call( + "my-crm", + "get:/contacts", + { queryParams: { limit: 10, status: "active" } } +); + +// POST request with body +const response = await base44.integrations.custom.call( + "my-crm", + "post:/contacts", + { payload: { name: "John Doe", email: "john@example.com" } } +); + +// Request with path parameters +const response = await base44.integrations.custom.call( + "github", + "post:/repos/{owner}/{repo}/issues", + { + pathParams: { owner: "myorg", repo: "myrepo" }, + payload: { title: "Bug report", body: "Something is broken" } + } +); +``` + +### Response Structure + +```javascript +{ + success: true, // Whether external API returned 2xx + status_code: 200, // HTTP status code + data: { ... } // Response from external API +} +``` + +## Requirements + +- **Core integrations**: Available on all plans +- **Catalog/Custom integrations**: Require Builder plan or higher + +## Type Definitions + +### Core Integration Parameters + +```typescript +/** Parameters for the InvokeLLM function. */ +interface InvokeLLMParams { + /** The prompt text to send to the model. */ + prompt: string; + /** If true, uses Google Search/Maps/News for real-time context. */ + add_context_from_internet?: boolean; + /** JSON schema for structured output. If provided, returns object instead of string. */ + response_json_schema?: object; + /** File URLs (from UploadFile) to provide as context. */ + file_urls?: string[]; +} + +/** Parameters for the GenerateImage function. */ +interface GenerateImageParams { + /** Description of the image to generate. */ + prompt: string; +} + +/** Result from GenerateImage. */ +interface GenerateImageResult { + /** URL of the generated image. */ + url: string; +} + +/** Parameters for the UploadFile function. */ +interface UploadFileParams { + /** The file object to upload. */ + file: File; +} + +/** Result from UploadFile. */ +interface UploadFileResult { + /** URL of the uploaded file. */ + file_url: string; +} + +/** Parameters for the SendEmail function. */ +interface SendEmailParams { + /** Recipient email address. */ + to: string; + /** Email subject line. */ + subject: string; + /** Plain text or HTML email body. */ + body: string; + /** Sender name (defaults to app name). */ + from_name?: string; +} + +/** Parameters for ExtractDataFromUploadedFile. */ +interface ExtractDataFromUploadedFileParams { + /** URL of the uploaded file. */ + file_url: string; + /** JSON schema defining fields to extract. */ + json_schema: object; +} + +/** Parameters for UploadPrivateFile. */ +interface UploadPrivateFileParams { + /** The file object to upload. */ + file: File; +} + +/** Result from UploadPrivateFile. */ +interface UploadPrivateFileResult { + /** URI of the private file (used for signed URLs). */ + file_uri: string; +} + +/** Parameters for CreateFileSignedUrl. */ +interface CreateFileSignedUrlParams { + /** URI from UploadPrivateFile. */ + file_uri: string; + /** Expiration time in seconds (default: 300). */ + expires_in?: number; +} + +/** Result from CreateFileSignedUrl. */ +interface CreateFileSignedUrlResult { + /** Temporary signed URL to access the file. */ + signed_url: string; +} +``` + +### CoreIntegrations + +```typescript +/** Core package containing built-in Base44 integration functions. */ +interface CoreIntegrations { + InvokeLLM(params: InvokeLLMParams): Promise; + GenerateImage(params: GenerateImageParams): Promise; + UploadFile(params: UploadFileParams): Promise; + SendEmail(params: SendEmailParams): Promise; + ExtractDataFromUploadedFile(params: ExtractDataFromUploadedFileParams): Promise; + UploadPrivateFile(params: UploadPrivateFileParams): Promise; + CreateFileSignedUrl(params: CreateFileSignedUrlParams): Promise; +} +``` + +### Custom Integrations + +```typescript +/** Parameters for calling a custom integration endpoint. */ +interface CustomIntegrationCallParams { + /** Request body payload. */ + payload?: Record; + /** Path parameters to substitute in the URL. */ + pathParams?: Record; + /** Query string parameters. */ + queryParams?: Record; + /** Additional headers for this request. */ + headers?: Record; +} + +/** Response from a custom integration call. */ +interface CustomIntegrationCallResponse { + /** Whether the external API returned a 2xx status code. */ + success: boolean; + /** The HTTP status code from the external API. */ + status_code: number; + /** The response data from the external API. */ + data: any; +} + +/** Module for calling custom workspace-level API integrations. */ +interface CustomIntegrationsModule { + /** + * Call a custom integration endpoint. + * @param slug - The integration's unique identifier. + * @param operationId - The operation ID from the OpenAPI spec. + * @param params - Optional payload, pathParams, queryParams, headers. + */ + call(slug: string, operationId: string, params?: CustomIntegrationCallParams): Promise; +} +``` + +### IntegrationsModule + +```typescript +/** Integrations module for calling integration endpoints. */ +type IntegrationsModule = { + /** Core package with built-in integrations. */ + Core: CoreIntegrations; + /** Custom integrations module. */ + custom: CustomIntegrationsModule; + /** Additional integration packages (dynamic). */ + [packageName: string]: any; +}; +``` diff --git a/evals/fixtures/cli-agents/skills/base44-sdk/references/users.md b/evals/fixtures/cli-agents/skills/base44-sdk/references/users.md new file mode 100644 index 0000000..4205b06 --- /dev/null +++ b/evals/fixtures/cli-agents/skills/base44-sdk/references/users.md @@ -0,0 +1,82 @@ +# Users Module + +Invite users to the app via `base44.users`. + +## Contents +- [Methods](#methods) +- [Examples](#examples) (Invite User) +- [Roles](#roles) +- [Notes](#notes) + +## Methods + +| Method | Signature | Description | +|--------|-----------|-------------| +| `inviteUser(user_email, role)` | `Promise` | Invite a user to the app | + +## Examples + +### Invite User + +```javascript +// Invite a user with "user" role +await base44.users.inviteUser("newuser@example.com", "user"); + +// Invite an admin +await base44.users.inviteUser("admin@example.com", "admin"); +``` + +### Invite Multiple Users + +```javascript +const usersToInvite = [ + { email: "user1@example.com", role: "user" }, + { email: "user2@example.com", role: "user" }, + { email: "manager@example.com", role: "admin" } +]; + +for (const user of usersToInvite) { + await base44.users.inviteUser(user.email, user.role); + console.log(`Invited ${user.email} as ${user.role}`); +} +``` + +## Roles + +The `role` parameter must be one of: + +| Role | Description | +|------|-------------| +| `"user"` | Standard user with default permissions | +| `"admin"` | Administrator with elevated permissions | + +**Note:** Only `"user"` and `"admin"` are valid role values. An error will be thrown if you pass any other value. + +## Notes + +- **Email invitation**: The invited user receives an email with a link to join the app +- **Duplicate handling**: Inviting an existing user will re-send the invitation +- **Also available in auth**: `base44.auth.inviteUser()` provides the same functionality +- **Role validation**: Only `"user"` or `"admin"` are accepted + +```javascript +// These are equivalent: +await base44.users.inviteUser("newuser@example.com", "user"); +await base44.auth.inviteUser("newuser@example.com", "user"); +``` + +## Type Definitions + +```typescript +/** Users module for inviting users to the app. */ +interface UsersModule { + /** + * Invite a user to the application. + * @param user_email - User's email address. + * @param role - User's role ('user' or 'admin'). + * @returns Promise resolving when the invitation is sent. + * @throws Error if role is not 'user' or 'admin'. + */ + inviteUser(user_email: string, role: "user" | "admin"): Promise; +} +``` diff --git a/evals/fixtures/cli-entities/eval.json b/evals/fixtures/cli-entities/eval.json new file mode 100644 index 0000000..5b29153 --- /dev/null +++ b/evals/fixtures/cli-entities/eval.json @@ -0,0 +1,263 @@ +{ + "$schema": "../../eval.schema.json", + "name": "cli-entities", + "description": "Test CLI entity schema creation: enums, dates, richtext, complex, multiple, naming", + "prompts": [ + { + "name": "cli-entity-enums", + "description": "Create entity with enum fields", + "prompt": "Create an entity called 'Ticket' with fields: title (string, required), priority (string enum: low/medium/high/critical, default medium), status (string enum: open/in_progress/resolved/closed, default open), and assigned_to (string). Save it at base44/entities/ticket.jsonc.", + "expectedSkills": [ + "base44-cli" + ], + "checks": [ + { + "type": "file-exists", + "filePath": "base44/entities/ticket.jsonc", + "description": "base44/entities/ticket.jsonc exists" + }, + { + "type": "valid-json", + "filePath": "base44/entities/ticket.jsonc", + "description": "base44/entities/ticket.jsonc is valid JSON" + }, + { + "type": "entity-config", + "filePath": "base44/entities/ticket.jsonc", + "target": "file", + "description": "Entity config validates (base44/entities/ticket.jsonc)" + }, + { + "type": "file-content", + "filePath": "base44/entities/ticket.jsonc", + "pattern": "\"name\"\\s*:\\s*\"Ticket\"", + "description": "Has name field" + }, + { + "type": "file-content", + "filePath": "base44/entities/ticket.jsonc", + "pattern": "\"enum\"\\s*:\\s*\\[.*\"low\".*\"medium\".*\"high\".*\"critical\"", + "description": "Has enum field" + }, + { + "type": "file-content", + "filePath": "base44/entities/ticket.jsonc", + "pattern": "\"enum\"\\s*:\\s*\\[.*\"open\".*\"in_progress\"", + "description": "Has enum field" + } + ] + }, + { + "name": "cli-entity-dates", + "description": "Create entity with date format fields", + "prompt": "Create an entity called 'Event' with fields: name (string, required), start_date (string with date format, required), end_date (string with date format), start_time (string with time format), and description (string). Save it at base44/entities/event.jsonc.", + "expectedSkills": [ + "base44-cli" + ], + "checks": [ + { + "type": "file-exists", + "filePath": "base44/entities/event.jsonc", + "description": "base44/entities/event.jsonc exists" + }, + { + "type": "valid-json", + "filePath": "base44/entities/event.jsonc", + "description": "base44/entities/event.jsonc is valid JSON" + }, + { + "type": "entity-config", + "filePath": "base44/entities/event.jsonc", + "target": "file", + "description": "Entity config validates (base44/entities/event.jsonc)" + }, + { + "type": "file-content", + "filePath": "base44/entities/event.jsonc", + "pattern": "\"format\"\\s*:\\s*\"date\"", + "description": "Has format field" + }, + { + "type": "file-content", + "filePath": "base44/entities/event.jsonc", + "pattern": "\"format\"\\s*:\\s*\"time\"", + "description": "Has format field" + } + ] + }, + { + "name": "cli-entity-richtext", + "description": "Create entity with richtext field", + "prompt": "Create an entity called 'Article' with fields: title (string, required), content (string with richtext format, required), author (string), published_date (string with date format), and tags (array). Save it at base44/entities/article.jsonc.", + "expectedSkills": [ + "base44-cli" + ], + "checks": [ + { + "type": "file-exists", + "filePath": "base44/entities/article.jsonc", + "description": "base44/entities/article.jsonc exists" + }, + { + "type": "valid-json", + "filePath": "base44/entities/article.jsonc", + "description": "base44/entities/article.jsonc is valid JSON" + }, + { + "type": "entity-config", + "filePath": "base44/entities/article.jsonc", + "target": "file", + "description": "Entity config validates (base44/entities/article.jsonc)" + }, + { + "type": "file-content", + "filePath": "base44/entities/article.jsonc", + "pattern": "\"format\"\\s*:\\s*\"richtext\"", + "description": "Has format field" + }, + { + "type": "file-content", + "filePath": "base44/entities/article.jsonc", + "pattern": "\"type\"\\s*:\\s*\"array\"", + "description": "Has type field" + } + ] + }, + { + "name": "cli-entity-complex", + "description": "Create entity with complex nested properties", + "prompt": "Create an entity called 'Company' with fields: name (string, required), website (string with uri format), email (string with email format), address (object with properties: street, city, state, zip_code), and employee_count (integer). Save it at base44/entities/company.jsonc.", + "expectedSkills": [ + "base44-cli" + ], + "checks": [ + { + "type": "file-exists", + "filePath": "base44/entities/company.jsonc", + "description": "base44/entities/company.jsonc exists" + }, + { + "type": "valid-json", + "filePath": "base44/entities/company.jsonc", + "description": "base44/entities/company.jsonc is valid JSON" + }, + { + "type": "entity-config", + "filePath": "base44/entities/company.jsonc", + "target": "file", + "description": "Entity config validates (base44/entities/company.jsonc)" + }, + { + "type": "file-content", + "filePath": "base44/entities/company.jsonc", + "pattern": "\"format\"\\s*:\\s*\"uri\"", + "description": "Has format field" + }, + { + "type": "file-content", + "filePath": "base44/entities/company.jsonc", + "pattern": "\"format\"\\s*:\\s*\"email\"", + "description": "Has format field" + }, + { + "type": "file-content", + "filePath": "base44/entities/company.jsonc", + "pattern": "\"type\"\\s*:\\s*\"integer\"", + "description": "Has type field" + } + ] + }, + { + "name": "cli-entity-multiple", + "description": "Create multiple related entities", + "prompt": "Create two related entities: 1) 'Author' with fields name (string, required) and bio (string), saved at base44/entities/author.jsonc. 2) 'Book' with fields title (string, required), isbn (string), price (number), and author_name (string), saved at base44/entities/book.jsonc.", + "expectedSkills": [ + "base44-cli" + ], + "checks": [ + { + "type": "file-exists", + "filePath": "base44/entities/author.jsonc", + "description": "base44/entities/author.jsonc exists" + }, + { + "type": "file-exists", + "filePath": "base44/entities/book.jsonc", + "description": "base44/entities/book.jsonc exists" + }, + { + "type": "valid-json", + "filePath": "base44/entities/author.jsonc", + "description": "base44/entities/author.jsonc is valid JSON" + }, + { + "type": "valid-json", + "filePath": "base44/entities/book.jsonc", + "description": "base44/entities/book.jsonc is valid JSON" + }, + { + "type": "entity-config", + "filePath": "base44/entities/author.jsonc", + "target": "file", + "description": "Entity config validates (base44/entities/author.jsonc)" + }, + { + "type": "entity-config", + "filePath": "base44/entities/book.jsonc", + "target": "file", + "description": "Entity config validates (base44/entities/book.jsonc)" + }, + { + "type": "file-content", + "filePath": "base44/entities/author.jsonc", + "pattern": "\"name\"\\s*:\\s*\"Author\"", + "description": "Has name field" + }, + { + "type": "file-content", + "filePath": "base44/entities/book.jsonc", + "pattern": "\"name\"\\s*:\\s*\"Book\"", + "description": "Has name field" + } + ] + }, + { + "name": "cli-entity-naming", + "description": "Test entity naming conventions", + "prompt": "Create an entity called 'TeamMember' with fields: full_name (string, required), role (string), department (string), and is_active (boolean, default true). Remember that entity names must be PascalCase alphanumeric and the file must be kebab-case. Save it at base44/entities/team-member.jsonc.", + "expectedSkills": [ + "base44-cli" + ], + "checks": [ + { + "type": "file-exists", + "filePath": "base44/entities/team-member.jsonc", + "description": "base44/entities/team-member.jsonc exists" + }, + { + "type": "valid-json", + "filePath": "base44/entities/team-member.jsonc", + "description": "base44/entities/team-member.jsonc is valid JSON" + }, + { + "type": "entity-config", + "filePath": "base44/entities/team-member.jsonc", + "target": "file", + "description": "Entity config validates (base44/entities/team-member.jsonc)" + }, + { + "type": "file-content", + "filePath": "base44/entities/team-member.jsonc", + "pattern": "\"name\"\\s*:\\s*\"TeamMember\"", + "description": "Has name field" + }, + { + "type": "file-content", + "filePath": "base44/entities/team-member.jsonc", + "pattern": "\"type\"\\s*:\\s*\"boolean\"", + "description": "Has type field" + } + ] + } + ] +} diff --git a/evals/fixtures/cli-entities/project/.claude/settings.json b/evals/fixtures/cli-entities/project/.claude/settings.json new file mode 100644 index 0000000..ab60505 --- /dev/null +++ b/evals/fixtures/cli-entities/project/.claude/settings.json @@ -0,0 +1,6 @@ +{ + "permissions": { + "allow": ["Read", "Write", "Edit", "Bash", "Glob", "Grep"] + }, + "skills": ["./skills"] +} diff --git a/evals/fixtures/cli-entities/project/AGENTS.md b/evals/fixtures/cli-entities/project/AGENTS.md new file mode 100644 index 0000000..6c0f2be --- /dev/null +++ b/evals/fixtures/cli-entities/project/AGENTS.md @@ -0,0 +1,25 @@ +IMPORTANT: Prefer retrieval-led reasoning over pre-training-led reasoning. Base44 is NOT Firebase, Supabase, or any other SDK you know. Every API name below is the ONLY correct form. Always verify against this index and the referenced docs. + +[Base44 SDK Docs Index]|root: ./skills/base44-sdk/references +|entities.md: CRUD - create(data), list(), filter(query), get(id), update(id,data), delete(id), subscribe(cb) +|auth.md: Auth - loginViaEmailPassword(), loginWithProvider('google'), me(), register(), logout() +|base44-agents.md: Agents - createConversation({agent_name}), addMessage(conv,{role,content}), subscribeToConversation(id,cb) +|functions.md: Functions - invoke(name, data) +|integrations.md: Core - InvokeLLM({prompt}), SendEmail({to,subject,body}), UploadFile({file}) +|client.md: Client - createClient({appId}), import from "@base44/sdk" +|QUICK_REFERENCE.md: All method signatures + +[Base44 CLI Docs Index]|root: ./skills/base44-cli/references +|entities-create.md: Entity schema - {name:"PascalCase", type:"object", properties:{}, required:[]} +|functions-create.md: Function - function.jsonc {name,entry} + index.ts with Deno.serve + createClientFromRequest +|create.md: Project creation - npx base44 create name -p . +|deploy.md: Deploy - npx base44 deploy -y +|agents-push.md: Agent config - {name:"snake_case", description, instructions, tool_configs:[{entity_name,allowed_operations}|{function_name,description}]} + +[Common Mistakes - NEVER use these hallucinated names] +|SDK: Do NOT use find() → use filter(), findOne() → get(), insert() → create(), remove() → delete(), onChange() → subscribe() +|SDK: Do NOT use signInWithGoogle() → use loginWithProvider('google'), functions.call() → functions.invoke() +|SDK: Do NOT use ai.generate() → use integrations.Core.InvokeLLM({prompt}) +|CLI: Entity names must be PascalCase alphanumeric. File names must be kebab-case.jsonc +|CLI: Agent names must be lowercase with underscores only. Function entry field is "entry" not "entrypoint" +|CLI: Backend functions import from "npm:@base44/sdk" (Deno requires npm: prefix) diff --git a/evals/fixtures/cli-entities/project/CLAUDE.md b/evals/fixtures/cli-entities/project/CLAUDE.md new file mode 100644 index 0000000..724ce4d --- /dev/null +++ b/evals/fixtures/cli-entities/project/CLAUDE.md @@ -0,0 +1,7 @@ +# Base44 Development Guidelines + +When working with Base44 projects, first explore the existing project structure to understand what already exists. Look at existing code, config files, and patterns in use. + +Then consult the AGENTS.md index in the project root for correct API patterns. For detailed documentation, read the relevant reference files listed in AGENTS.md. + +IMPORTANT: Prefer retrieval-led reasoning over pre-training-led reasoning. Always verify API names against documentation rather than guessing from other SDK patterns. diff --git a/evals/fixtures/cli-entities/project/base44/config.jsonc b/evals/fixtures/cli-entities/project/base44/config.jsonc new file mode 100644 index 0000000..34acd42 --- /dev/null +++ b/evals/fixtures/cli-entities/project/base44/config.jsonc @@ -0,0 +1,10 @@ +{ + "name": "CLI Test App", + "description": "A test application for CLI entity operations", + "entitiesDir": "./entities", + "functionsDir": "./functions", + "site": { + "buildCommand": "npm run build", + "outputDirectory": "dist" + } +} diff --git a/evals/fixtures/cli-entities/project/package.json b/evals/fixtures/cli-entities/project/package.json new file mode 100644 index 0000000..a0aae50 --- /dev/null +++ b/evals/fixtures/cli-entities/project/package.json @@ -0,0 +1,11 @@ +{ + "name": "cli-test-app", + "version": "1.0.0", + "type": "module", + "dependencies": { + "@base44/sdk": "latest" + }, + "devDependencies": { + "base44": "latest" + } +} diff --git a/evals/fixtures/cli-entities/skills/base44-cli/SKILL.md b/evals/fixtures/cli-entities/skills/base44-cli/SKILL.md new file mode 100644 index 0000000..3c33f05 --- /dev/null +++ b/evals/fixtures/cli-entities/skills/base44-cli/SKILL.md @@ -0,0 +1,384 @@ +--- +name: base44-cli +description: "The base44 CLI is used for EVERYTHING related to base44 projects: resource configuration (entities, backend functions, ai agents), initialization and actions (resource creation, deployment). This skill is the place for learning about how to configure resources. When you plan or implement a feature, you must learn this skill" +--- + +# Base44 CLI + +Create and manage Base44 apps (projects) using the Base44 CLI tool. + +## ⚡ IMMEDIATE ACTION REQUIRED - Read This First + +This skill activates on ANY mention of "base44" or when a `base44/` folder exists. **DO NOT read documentation files or search the web before acting.** + +**Your first action MUST be:** +1. Check if `base44/config.jsonc` exists in the current directory +2. If **NO** (new project scenario): + - This skill (base44-cli) handles the request + - Guide user through project initialization + - Do NOT activate base44-sdk yet +3. If **YES** (existing project scenario): + - Transfer to base44-sdk skill for implementation + - This skill only handles CLI commands (login, deploy, entities push) + +## Critical: Local Installation Only + +NEVER call `base44` directly. The CLI is installed locally as a dev dependency and must be accessed via a package manager: + +- `npx base44 ` (npm - recommended) +- `yarn base44 ` (yarn) +- `pnpm base44 ` (pnpm) + +WRONG: `base44 login` +RIGHT: `npx base44 login` + +## MANDATORY: Authentication Check at Session Start + +**CRITICAL**: At the very start of every AI session when this skill is activated, you MUST: + +1. **Check authentication status** by running: + ```bash + npx base44 whoami + ``` + +2. **If the user is logged in** (command succeeds and shows an email): + - Continue with the requested task + +3. **If the user is NOT logged in** (command fails or shows an error): + - **STOP immediately** + - **DO NOT proceed** with any CLI operations + - **Ask the user to login manually** by running: + ```bash + npx base44 login + ``` + - Wait for the user to confirm they have logged in before continuing + +**This check is mandatory and must happen before executing any other Base44 CLI commands.** + +## Overview + +The Base44 CLI provides command-line tools for authentication, creating projects, managing entities, and deploying Base44 applications. It is framework-agnostic and works with popular frontend frameworks like Vite, Next.js, and Create React App, Svelte, Vue, and more. + +## When to Use This Skill vs base44-sdk + +**Use base44-cli when:** +- Creating a **NEW** Base44 project from scratch +- Initializing a project in an empty directory +- Directory is missing `base44/config.jsonc` +- User mentions: "create a new project", "initialize project", "setup a project", "start a new Base44 app" +- Deploying, pushing entities, or authenticating via CLI +- Working with CLI commands (`npx base44 ...`) + +**Use base44-sdk when:** +- Building features in an **EXISTING** Base44 project +- `base44/config.jsonc` already exists +- Writing JavaScript/TypeScript code using Base44 SDK +- Implementing functionality, components, or features +- User mentions: "implement", "build a feature", "add functionality", "write code" + +**Skill Dependencies:** +- `base44-cli` is a **prerequisite** for `base44-sdk` in new projects +- If user wants to "create an app" and no Base44 project exists, use `base44-cli` first +- `base44-sdk` assumes a Base44 project is already initialized + +**State Check Logic:** +Before selecting a skill, check: +- IF (user mentions "create/build app" OR "make a project"): + - IF (directory is empty OR no `base44/config.jsonc` exists): + → Use **base44-cli** (project initialization needed) + - ELSE: + → Use **base44-sdk** (project exists, build features) + +## Project Structure + +A Base44 project combines a standard frontend project with a `base44/` configuration folder: + +``` +my-app/ +├── base44/ # Base44 configuration (created by CLI) +│ ├── config.jsonc # Project settings, site config +│ ├── entities/ # Entity schema definitions +│ │ ├── task.jsonc +│ │ └── board.jsonc +│ ├── functions/ # Backend functions (optional) +│ │ └── my-function/ +│ │ ├── function.jsonc +│ │ └── index.ts +│ └── agents/ # Agent configurations (optional) +│ └── support_agent.jsonc +├── src/ # Frontend source code +│ ├── api/ +│ │ └── base44Client.js # Base44 SDK client +│ ├── pages/ +│ ├── components/ +│ └── main.jsx +├── index.html # SPA entry point +├── package.json +└── vite.config.js # Or your framework's config +``` + +**Key files:** +- `base44/config.jsonc` - Project name, description, site build settings +- `base44/entities/*.jsonc` - Data model schemas (see Entity Schema section) +- `base44/agents/*.jsonc` - Agent configurations (optional) +- `src/api/base44Client.js` - Pre-configured SDK client for frontend use + +**config.jsonc example:** +```jsonc +{ + "name": "My App", // Required: project name + "description": "App description", // Optional: project description + "entitiesDir": "./entities", // Optional: default "entities" + "functionsDir": "./functions", // Optional: default "functions" + "agentsDir": "./agents", // Optional: default "agents" + "site": { // Optional: site deployment config + "installCommand": "npm install", // Optional: install dependencies + "buildCommand": "npm run build", // Optional: build command + "serveCommand": "npm run dev", // Optional: local dev server + "outputDirectory": "./dist" // Optional: build output directory + } +} +``` + +**Config properties:** + +| Property | Description | Default | +|----------|-------------|---------| +| `name` | Project name (required) | - | +| `description` | Project description | - | +| `entitiesDir` | Directory for entity schemas | `"entities"` | +| `functionsDir` | Directory for backend functions | `"functions"` | +| `agentsDir` | Directory for agent configs | `"agents"` | +| `site.installCommand` | Command to install dependencies | - | +| `site.buildCommand` | Command to build the project | - | +| `site.serveCommand` | Command to run dev server | - | +| `site.outputDirectory` | Build output directory for deployment | - | + +## Installation + +Install the Base44 CLI as a dev dependency in your project: + +```bash +npm install --save-dev base44 +``` + +**Important:** Never assume or hardcode the `base44` package version. Always install without a version specifier to get the latest version. + +Then run commands using `npx`: + +```bash +npx base44 +``` + +**Note:** All commands in this documentation use `npx base44`. You can also use `yarn base44`, or `pnpm base44` if preferred. + +## Available Commands + +### Authentication + +| Command | Description | Reference | +| --------------- | ----------------------------------------------- | ------------------------------------------- | +| `base44 login` | Authenticate with Base44 using device code flow | [auth-login.md](references/auth-login.md) | +| `base44 logout` | Logout from current device | [auth-logout.md](references/auth-logout.md) | +| `base44 whoami` | Display current authenticated user | [auth-whoami.md](references/auth-whoami.md) | + +### Project Management + +| Command | Description | Reference | +|---------|-------------|-----------| +| `base44 create` | Create a new Base44 project from a template | [create.md](references/create.md) ⚠️ **MUST READ** | +| `base44 link` | Link an existing local project to Base44 | [link.md](references/link.md) | +| `base44 dashboard` | Open the app dashboard in your browser | [dashboard.md](references/dashboard.md) | + +### Deployment + +| Command | Description | Reference | +|---------|-------------|-----------| +| `base44 deploy` | Deploy all resources (entities, functions, site) | [deploy.md](references/deploy.md) | + +### Entity Management + +| Action / Command | Description | Reference | +| ---------------------- | ------------------------------------------- | --------------------------------------------------- | +| Create Entities | Define entities in `base44/entities` folder | [entities-create.md](references/entities-create.md) | +| `base44 entities push` | Push local entities to Base44 | [entities-push.md](references/entities-push.md) | + +#### Entity Schema (Quick Reference) + +ALWAYS follow this exact structure when creating entity files: + +**File naming:** `base44/entities/{kebab-case-name}.jsonc` (e.g., `team-member.jsonc` for `TeamMember`) + +**Schema template:** +```jsonc +{ + "name": "EntityName", + "type": "object", + "properties": { + "field_name": { + "type": "string", + "description": "Field description" + } + }, + "required": ["field_name"] +} +``` + +**Field types:** `string`, `number`, `integer`, `boolean`, `array`, `object`, `binary` +**String formats:** `date`, `date-time`, `time`, `email`, `uri`, `hostname`, `ipv4`, `ipv6`, `uuid`, `file`, `regex`, `richtext` +**For enums:** Add `"enum": ["value1", "value2"]` and optionally `"default": "value1"` +**Entity names:** Must be alphanumeric only (pattern: `/^[a-zA-Z0-9]+$/`) + +For complete documentation, see [entities-create.md](references/entities-create.md). + +### Function Management + +| Action / Command | Description | Reference | +| ------------------------- | --------------------------------------------- | ------------------------------------------------------- | +| Create Functions | Define functions in `base44/functions` folder | [functions-create.md](references/functions-create.md) | +| `base44 functions deploy` | Deploy local functions to Base44 | [functions-deploy.md](references/functions-deploy.md) | + +### Agent Management + +Agents are conversational AI assistants that can interact with users, access your app's entities, and call backend functions. Use these commands to manage agent configurations. + +| Action / Command | Description | Reference | +| ----------------------- | --------------------------------------- | ----------------------------------------------- | +| Create Agents | Define agents in `base44/agents` folder | See Agent Schema below | +| `base44 agents pull` | Pull remote agents to local files | [agents-pull.md](references/agents-pull.md) | +| `base44 agents push` | Push local agents to Base44 | [agents-push.md](references/agents-push.md) | + +**Note:** Agent commands perform full synchronization - pushing replaces all remote agents with local ones, and pulling replaces all local agents with remote ones. + +#### Agent Schema (Quick Reference) + +**File naming:** `base44/agents/{agent_name}.jsonc` (e.g., `support_agent.jsonc`) + +**Schema template:** +```jsonc +{ + "name": "agent_name", + "description": "Brief description of what this agent does", + "instructions": "Detailed instructions for the agent's behavior", + "tool_configs": [ + // Entity tool - gives agent access to entity operations + { "entity_name": "tasks", "allowed_operations": ["read", "create", "update", "delete"] }, + // Backend function tool - gives agent access to a function + { "function_name": "send_email", "description": "Send an email notification" } + ], + "whatsapp_greeting": "Hello! How can I help you today?" +} +``` + +**Naming rules:** +- Agent names must match pattern: `/^[a-z0-9_]+$/` (lowercase alphanumeric with underscores, 1-100 chars) +- Valid: `support_agent`, `order_bot` +- Invalid: `Support-Agent`, `OrderBot` + +**Required fields:** `name`, `description`, `instructions` +**Optional fields:** `tool_configs` (defaults to `[]`), `whatsapp_greeting` + +**Tool config types:** +- **Entity tools**: `entity_name` + `allowed_operations` (array of: `read`, `create`, `update`, `delete`) +- **Backend function tools**: `function_name` + `description` + +### Site Deployment + +| Command | Description | Reference | +| -------------------- | ----------------------------------------- | ------------------------------------------- | +| `base44 site deploy` | Deploy built site files to Base44 hosting | [site-deploy.md](references/site-deploy.md) | + +**SPA only**: Base44 hosting supports Single Page Applications with a single `index.html` entry point. All routes are served from `index.html` (client-side routing). + +## Quick Start + +1. Install the CLI in your project: + ```bash + npm install --save-dev base44 + ``` + +2. Authenticate with Base44: + ```bash + npx base44 login + ``` + +3. Create a new project (ALWAYS provide name and `--path` flag): + ```bash + npx base44 create my-app -p . + ``` + +4. Build and deploy everything: + ```bash + npm run build + npx base44 deploy -y + ``` + +Or deploy individual resources: +- `npx base44 entities push` - Push entities only +- `npx base44 functions deploy` - Deploy functions only +- `npx base44 agents push` - Push agents only +- `npx base44 site deploy -y` - Deploy site only + +## Common Workflows + +### Creating a New Project + +**⚠️ MANDATORY: Before running `base44 create`, you MUST read [create.md](references/create.md) for:** +- **Template selection** - Choose the correct template (`backend-and-client` vs `backend-only`) +- **Correct workflow** - Different templates require different setup steps +- **Common pitfalls** - Avoid folder creation errors that cause failures + +Failure to follow the create.md instructions will result in broken project scaffolding. + +### Linking an Existing Project +```bash +# If you have base44/config.jsonc but no .app.jsonc +npx base44 link --create --name my-app +``` + +### Deploying All Changes +```bash +# Build your project first +npm run build + +# Deploy everything (entities, functions, and site) +npx base44 deploy -y +``` + +### Deploying Individual Resources +```bash +# Push only entities +npx base44 entities push + +# Deploy only functions +npx base44 functions deploy + +# Push only agents +npx base44 agents push + +# Deploy only site +npx base44 site deploy -y +``` + +### Opening the Dashboard +```bash +# Open app dashboard in browser +npx base44 dashboard +``` + +## Authentication + +Most commands require authentication. If you're not logged in, the CLI will automatically prompt you to login. Your session is stored locally and persists across CLI sessions. + +## Troubleshooting + +| Error | Solution | +| --------------------------- | ----------------------------------------------------------------------------------- | +| Not authenticated | Run `npx base44 login` first | +| No entities found | Ensure entities exist in `base44/entities/` directory | +| Entity not recognized | Ensure file uses kebab-case naming (e.g., `team-member.jsonc` not `TeamMember.jsonc`) | +| No functions found | Ensure functions exist in `base44/functions/` with valid `function.jsonc` configs | +| No agents found | Ensure agents exist in `base44/agents/` directory with valid `.jsonc` configs | +| Invalid agent name | Agent names must be lowercase alphanumeric with underscores only | +| No site configuration found | Check that `site.outputDirectory` is configured in project config | +| Site deployment fails | Ensure you ran `npm run build` first and the build succeeded | diff --git a/evals/fixtures/cli-entities/skills/base44-cli/references/agents-pull.md b/evals/fixtures/cli-entities/skills/base44-cli/references/agents-pull.md new file mode 100644 index 0000000..6a700f0 --- /dev/null +++ b/evals/fixtures/cli-entities/skills/base44-cli/references/agents-pull.md @@ -0,0 +1,73 @@ +# base44 agents pull + +Pull AI agent configurations from Base44 to local files. Agents are conversational AI assistants that can interact with users, access your app's entities, and call backend functions. + +## Syntax + +```bash +npx base44 agents pull +``` + +## Authentication + +**Required**: Yes. If not authenticated, you'll be prompted to login first. + +## What It Does + +1. Fetches all agents from Base44 +2. Writes agent files to the `base44/agents/` directory +3. Deletes local agent files that don't exist remotely +4. Reports written and deleted agents + +## Prerequisites + +- Must be run from a Base44 project directory +- Project must be linked to a Base44 app + +## Output + +```bash +$ npx base44 agents pull + +Fetching agents from Base44... +✓ Agents fetched successfully + +Writing agent files... +✓ Agent files written successfully + +Written: support_agent, order_bot +Deleted: old_agent + +Pulled 2 agents to base44/agents +``` + +## Agent Synchronization + +The pull operation synchronizes remote agents to your local files: + +- **Written**: Agent files created or updated from remote +- **Deleted**: Local agent files removed (didn't exist remotely) + +**Warning**: This operation replaces all local agent configurations with remote versions. Any local changes not pushed to Base44 will be overwritten. + +## Error Handling + +If no agents exist on Base44: +```bash +$ npx base44 agents pull +No agents found on Base44 +``` + +## Use Cases + +- Sync agent configurations to a new development machine +- Get the latest agent configurations from your team +- Restore local agent files after accidental deletion +- Start working on an existing project with agents + +## Notes + +- This command syncs agent configurations, not conversation data +- Agent files are stored as `.jsonc` in the `base44/agents/` directory +- The directory location is configurable via `agentsDir` in `config.jsonc` +- Use `base44 agents push` to upload local changes to Base44 diff --git a/evals/fixtures/cli-entities/skills/base44-cli/references/agents-push.md b/evals/fixtures/cli-entities/skills/base44-cli/references/agents-push.md new file mode 100644 index 0000000..fa2795b --- /dev/null +++ b/evals/fixtures/cli-entities/skills/base44-cli/references/agents-push.md @@ -0,0 +1,156 @@ +# base44 agents push + +Push local AI agent configurations to Base44. Agents are conversational AI assistants that can interact with users, access your app's entities, and call backend functions. + +## Syntax + +```bash +npx base44 agents push +``` + +## Authentication + +**Required**: Yes. If not authenticated, you'll be prompted to login first. + +## What It Does + +1. Reads all agent files from the `base44/agents/` directory +2. Validates agent configurations +3. Displays the count of agents to be pushed +4. Uploads agents to the Base44 backend +5. Reports the results: created, updated, and deleted agents + +## Prerequisites + +- Must be run from a Base44 project directory +- Project must have agent definitions in the `base44/agents/` folder + +## Output + +```bash +$ npx base44 agents push + +Found 2 agents to push +Pushing agents to Base44... + +Created: support_agent +Updated: order_bot +Deleted: old_agent + +✓ Agents pushed to Base44 +``` + +## Agent Synchronization + +The push operation synchronizes your local agents with Base44: + +- **Created**: New agents that didn't exist in Base44 +- **Updated**: Existing agents with modified configuration +- **Deleted**: Agents that were removed from your local configuration + +**Warning**: This is a full sync operation. Agents removed locally will be deleted from Base44. + +## Error Handling + +If no agents are found in your project: +```bash +$ npx base44 agents push +No local agents found - this will delete all remote agents +``` + +If an agent has an invalid name: +```bash +$ npx base44 agents push +Error: Agent name must be lowercase alphanumeric with underscores +``` + +## Agent Configuration Schema + +Each agent file should be a `.jsonc` file in `base44/agents/` with this structure: + +```jsonc +{ + "name": "agent_name", // Required: lowercase alphanumeric with underscores, 1-100 chars + "description": "Brief description of what this agent does", // Required: min 1 char + "instructions": "Detailed instructions for the agent's behavior", // Required: min 1 char + "tool_configs": [ // Optional: defaults to [] + // Entity tool - gives agent access to entity operations + { "entity_name": "Task", "allowed_operations": ["read", "create", "update", "delete"] }, + // Backend function tool - gives agent access to a function + { "function_name": "send_email", "description": "Send an email notification" } + ], + "whatsapp_greeting": "Hello! How can I help you today?" // Optional +} +``` + +**Naming rules:** +- **Agent names** must match pattern: `/^[a-z0-9_]+$/` (lowercase alphanumeric with underscores only, 1-100 characters) + - Valid: `support_agent`, `order_bot`, `task_helper` + - Invalid: `Support-Agent`, `OrderBot`, `task helper` +- **Agent file names** must use underscores (matching the agent name) + - Valid: `support_agent.jsonc`, `order_bot.jsonc` + - Invalid: `support-agent.jsonc` (hyphens not allowed) +- **Entity names in `tool_configs`** must use PascalCase (matching the entity's `name` field) + - Valid: `"entity_name": "Task"`, `"entity_name": "TeamMember"` + - Invalid: `"entity_name": "task"`, `"entity_name": "team_member"` + +**Required fields:** +- `name`: Required, must follow naming rules above +- `description`: Required, minimum 1 character +- `instructions`: Required, minimum 1 character +- `tool_configs`: Optional, defaults to empty array +- `whatsapp_greeting`: Optional + +### Common Mistake: Wrong tool_configs Format + +**WRONG** - Do NOT use `tools` with `type` and `entity`: +```jsonc +{ + "name": "my_agent", + "tools": [ // ❌ WRONG + { "type": "entity_query", "entity": "Task" } + ] +} +``` + +**CORRECT** - Use `tool_configs` with `entity_name` and `allowed_operations`: +```jsonc +{ + "name": "my_agent", + "tool_configs": [ // ✅ CORRECT + { "entity_name": "Task", "allowed_operations": ["read"] } + ] +} +``` + +### Best Practices for Agent Instructions + +When giving agents access to entities, be explicit in the instructions about using the tools: + +```jsonc +{ + "name": "support_agent", + "instructions": "You are a helpful support agent.\n\nIMPORTANT: You have access to customer data through entity tools. When users ask about their orders or account:\n1. ALWAYS use the Order entity tool to query their order history\n2. Use the Customer entity tool to look up account details\n3. Analyze the data and provide personalized responses\n\nAlways query the relevant entities first before answering questions about user data.", + "tool_configs": [ + { "entity_name": "Order", "allowed_operations": ["read"] }, + { "entity_name": "Customer", "allowed_operations": ["read"] } + ] +} +``` + +Without explicit instructions to use the entity tools, the agent may not proactively query user data when asked. + +## Use Cases + +- After defining new agents in your project +- When modifying existing agent configurations +- To sync agent changes before testing +- As part of your development workflow when agent behavior changes + +## Notes + +- This command syncs the agent configuration, not conversation data +- Changes are applied to your Base44 project immediately +- Make sure to test agent changes in a development environment first +- Agent definitions are located in the `base44/agents/` directory +- Use `base44 agents pull` to download agents from Base44 diff --git a/evals/fixtures/cli-entities/skills/base44-cli/references/auth-login.md b/evals/fixtures/cli-entities/skills/base44-cli/references/auth-login.md new file mode 100644 index 0000000..adebd27 --- /dev/null +++ b/evals/fixtures/cli-entities/skills/base44-cli/references/auth-login.md @@ -0,0 +1,54 @@ +# base44 login + +Authenticate with Base44 using device code flow. + +## Syntax + +```bash +npx base44 login +``` + +## Authentication + +**Required**: No (this is the login command itself) + +## How It Works + +The login command uses OAuth 2.0 device code flow for authentication: + +1. Generates a device code for authentication +2. Displays a verification code and verification URI +3. Directs you to visit the URI and enter the code +4. Polls for authentication completion (up to device code expiration) +5. Retrieves access and refresh tokens upon successful authentication +6. Fetches and displays your user information +7. Saves authentication data locally with expiration timestamp + +## Interactive Flow + +```bash +$ npx base44 login + +Please visit: https://auth.base44.com/device +Enter code: ABCD-EFGH + +Waiting for authentication... +✓ Successfully authenticated! + +Logged in as: user@example.com +``` + +## Session Management + +- Authentication tokens are stored locally on your device +- Tokens include expiration timestamps +- The session persists across CLI sessions +- Other commands will automatically use your stored credentials +- Use `npx base44 logout` to clear your session +- Use `npx base44 whoami` to check your current authentication status + +## Notes + +- You only need to login once per device +- If your session expires, you'll be prompted to login again when running authenticated commands +- The CLI automatically prompts for login when you run commands that require authentication diff --git a/evals/fixtures/cli-entities/skills/base44-cli/references/auth-logout.md b/evals/fixtures/cli-entities/skills/base44-cli/references/auth-logout.md new file mode 100644 index 0000000..33c2ddf --- /dev/null +++ b/evals/fixtures/cli-entities/skills/base44-cli/references/auth-logout.md @@ -0,0 +1,32 @@ +# base44 logout + +Logout from current device and clear stored authentication data. + +## Syntax + +```bash +npx base44 logout +``` + +## Authentication + +**Required**: No + +## What It Does + +- Deletes stored authentication data from your device +- Clears your local session +- Removes access and refresh tokens + +## Output + +```bash +$ npx base44 logout +Logged out successfully +``` + +## Notes + +- You can logout even if you're not currently logged in (no error) +- After logout, you'll need to run `npx base44 login` again to use authenticated commands +- This only affects the current device; your Base44 account remains active diff --git a/evals/fixtures/cli-entities/skills/base44-cli/references/auth-whoami.md b/evals/fixtures/cli-entities/skills/base44-cli/references/auth-whoami.md new file mode 100644 index 0000000..abe450c --- /dev/null +++ b/evals/fixtures/cli-entities/skills/base44-cli/references/auth-whoami.md @@ -0,0 +1,37 @@ +# base44 whoami + +Display the currently authenticated user. + +## Syntax + +```bash +npx base44 whoami +``` + +## Authentication + +**Required**: Yes. If not authenticated, you'll be prompted to login first. + +## What It Does + +- Reads stored authentication data +- Displays the email of the currently logged-in user + +## Output + +```bash +$ npx base44 whoami +Logged in as: user@example.com +``` + +## Use Cases + +- Verify you're logged in before running other commands +- Check which account you're currently using +- Confirm authentication is working properly +- Useful in scripts or automation to verify credentials + +## Notes + +- If you're not logged in, the command will prompt you to authenticate first +- The email displayed matches your Base44 account email diff --git a/evals/fixtures/cli-entities/skills/base44-cli/references/create.md b/evals/fixtures/cli-entities/skills/base44-cli/references/create.md new file mode 100644 index 0000000..7580645 --- /dev/null +++ b/evals/fixtures/cli-entities/skills/base44-cli/references/create.md @@ -0,0 +1,111 @@ +# base44 create + +Creates a new Base44 project from a template. This command is framework-agnostic and can either scaffold a complete project or add Base44 configuration to an existing project. + +## Critical: Non-Interactive Mode Required + +ALWAYS provide both the project name AND `--path` flag. Without both, the command opens an interactive TUI which agents cannot use properly. + +WRONG: `npx base44 create` +WRONG: `npx base44 create my-app` +RIGHT: `npx base44 create my-app -p ./my-app` + +## Syntax + +```bash +npx base44 create [name] --path [options] +``` + +## Arguments & Options + +| Argument/Option | Description | Required | +|--------|-------------|----------| +| `name` | Project name (positional argument) | Yes* | +| `-p, --path ` | Path where to create the project | Yes* | +| `-t, --template ` | Template ID (see templates below) | No | +| `--deploy` | Build and deploy the site (includes pushing entities) | No | +| `--no-skills` | Skip AI agent skills installation (skills are added by default) | No | + +*Required for non-interactive mode. Both `name` and `--path` must be provided together. + +## Template Selection (CRITICAL - Choose Appropriately) + +**You MUST select the most appropriate template based on user requirements:** + +| Template ID | When to Use | Example Scenarios | +|-------------|-------------|-------------------| +| `backend-and-client` | Creating a NEW full-stack web app from scratch | "Create a task app", "Build me a dashboard", "Make a SaaS app" | +| `backend-only` | Adding Base44 to an EXISTING project OR using a different framework (Next.js, Vue, Svelte, etc.) | "Add Base44 to my project", "I want to use Next.js", "I already have a frontend" | + +**Default Choice:** When the user asks to "create an app" or "build a project" without specifying a particular framework, use `backend-and-client` to provide a complete, production-ready application with Vite + React + Tailwind. + +## The `--path` Flag + +- **For `backend-and-client` template (new projects):** Use a new subfolder path + ```bash + npx base44 create my-app -p ./my-app -t backend-and-client + ``` +- **For `backend-only` template (existing projects):** Use `-p .` in the current directory + ```bash + npx base44 create my-app -p . + ``` + +## Workflow: Using `backend-only` with External Frameworks + +**CRITICAL: The project folder MUST exist BEFORE running `base44 create` with `backend-only`** + +The `backend-only` template only adds Base44 configuration files - it does NOT create a frontend. If you need a frontend with a specific framework: + +```bash +# Step 1: Initialize the frontend project FIRST +npm create vite@latest my-app -- --template react # or vue, svelte, etc. +# OR: npx create-next-app@latest my-app +# OR: any other framework's init command + +# Step 2: Navigate into the created folder +cd my-app + +# Step 3: Install Base44 CLI +npm install --save-dev base44 + +# Step 4: Add Base44 configuration +npx base44 create my-app -p . +``` + +**WARNING:** Do NOT: +- Create an empty folder manually, then try to run `npx create vite` inside it (will fail - folder exists) +- Run `base44 create` with `backend-only` expecting it to create a frontend (it won't) + +**DO:** +- Run the external framework's init command FIRST (it creates its own folder) +- Then run `base44 create` inside that folder with `-p .` + +## Examples + +```bash +# RECOMMENDED: Create full-stack project (for new apps) +npx base44 create my-app -p ./my-app -t backend-and-client + +# Create full-stack and deploy in one step +npx base44 create my-app -p ./my-app -t backend-and-client --deploy + +# Add Base44 to EXISTING project (must be inside the project folder) +npx base44 create my-app -p . + +# Add Base44 to existing project and deploy +npx base44 create my-app -p . --deploy + +# Create without adding AI agent skills +npx base44 create my-app -p . --no-skills +``` + +## What It Does + +1. Applies the selected template to the target path +2. Creates a `base44/` folder with configuration files +3. Registers the project with Base44 backend +4. Creates `base44/.app.jsonc` with the app ID +5. If `--deploy` is used: + - Pushes any entities defined in `base44/entities/` + - Runs install and build commands (for templates with frontend) + - Deploys the site to Base44 hosting diff --git a/evals/fixtures/cli-entities/skills/base44-cli/references/dashboard.md b/evals/fixtures/cli-entities/skills/base44-cli/references/dashboard.md new file mode 100644 index 0000000..95a534a --- /dev/null +++ b/evals/fixtures/cli-entities/skills/base44-cli/references/dashboard.md @@ -0,0 +1,35 @@ +# base44 dashboard + +Opens the Base44 app dashboard in your default web browser. + +## Syntax + +```bash +npx base44 dashboard +``` + +## Options + +This command has no options. + +## What It Does + +1. Reads the project's app ID from `base44/.app.jsonc` +2. Opens the dashboard URL in your default browser + +## Example + +```bash +# Open dashboard for current project +npx base44 dashboard +``` + +## Requirements + +- Must be run from a linked Base44 project directory (contains `base44/.app.jsonc`) +- Must be authenticated (run `npx base44 login` first) + +## Notes + +- The dashboard provides a web interface to manage your app's entities, functions, users, and settings +- If you're not in a project directory, the command will fail with an error diff --git a/evals/fixtures/cli-entities/skills/base44-cli/references/deploy.md b/evals/fixtures/cli-entities/skills/base44-cli/references/deploy.md new file mode 100644 index 0000000..500526f --- /dev/null +++ b/evals/fixtures/cli-entities/skills/base44-cli/references/deploy.md @@ -0,0 +1,86 @@ +# base44 deploy + +Deploys all project resources (entities, functions, and site) to Base44 in a single command. + +## Syntax + +```bash +npx base44 deploy [options] +``` + +## Options + +| Option | Description | +|--------|-------------| +| `-y, --yes` | Skip confirmation prompt | + +## What It Deploys + +The command automatically detects and deploys: + +1. **Entities** - All `.jsonc` files in `base44/entities/` +2. **Functions** - All functions in `base44/functions/` +3. **Agents** - All agent configurations in `base44/agents/` +4. **Site** - Built files from `site.outputDirectory` (if configured) + +## Examples + +```bash +# Interactive mode - shows what will be deployed and asks for confirmation +npx base44 deploy + +# Non-interactive - skip confirmation (for CI/CD or agent use) +npx base44 deploy -y +``` + +## Typical Workflow + +```bash +# 1. Make your changes (entities, functions, frontend code) + +# 2. Build the frontend (if you have one) +npm run build + +# 3. Deploy everything +npx base44 deploy -y +``` + +## What It Does + +1. Reads project configuration from `base44/config.jsonc` +2. Detects available resources (entities, functions, site) +3. Shows a summary of what will be deployed +4. Asks for confirmation (unless `-y` flag is used) +5. Deploys all resources in sequence: + - Pushes entity schemas + - Deploys functions + - Pushes agent configurations + - Uploads site files +6. Displays the dashboard URL and app URL (if site was deployed) + +## Requirements + +- Must be run from a linked Base44 project directory +- Must be authenticated (run `npx base44 login` first) +- For site deployment, must run `npm run build` first + +## Output + +After successful deployment: +- **Dashboard**: Link to your app's management dashboard +- **App URL**: Your deployed site's public URL (if site was included) + +## Notes + +- If no resources are found, the command exits with a message +- Use individual commands (`entities push`, `functions deploy`, `site deploy`) if you only want to deploy specific resources +- The site must be built before deployment - this command does not run `npm run build` for you + +## Related Commands + +| Command | Description | +|---------|-------------| +| `base44 entities push` | Push only entities | +| `base44 functions deploy` | Deploy only functions | +| `base44 agents push` | Push only agents | +| `base44 site deploy` | Deploy only the site | diff --git a/evals/fixtures/cli-entities/skills/base44-cli/references/entities-create.md b/evals/fixtures/cli-entities/skills/base44-cli/references/entities-create.md new file mode 100644 index 0000000..29b40aa --- /dev/null +++ b/evals/fixtures/cli-entities/skills/base44-cli/references/entities-create.md @@ -0,0 +1,552 @@ +# Creating Entities + +Base44 entities are defined locally in your project and then pushed to the Base44 backend. + +## Critical: File Naming + +Entity files MUST use kebab-case naming: `{kebab-case-name}.jsonc` + +| Entity Name | File Name | +|-------------|-----------| +| `Task` | `task.jsonc` | +| `TeamMember` | `team-member.jsonc` | +| `ActivityLog` | `activity-log.jsonc` | + +WRONG: `TeamMember.jsonc`, `teamMember.jsonc` +RIGHT: `team-member.jsonc` + +## Table of Contents + +- [Creating Entities](#creating-entities) + - [Entity Directory](#entity-directory) + - [How to Create an Entity](#how-to-create-an-entity) + - [Entity Schema Structure](#entity-schema-structure) + - [Supported Field Types](#supported-field-types) + - [Field Properties](#field-properties) + - [Complete Example](#complete-example) + - [Naming Conventions](#naming-conventions) + - [Relationships Between Entities](#relationships-between-entities) + - [Row Level Security (RLS)](#row-level-security-rls) + - [Field Level Security (FLS)](#field-level-security-fls) + - [Pushing Entities](#pushing-entities) + +## Entity Directory + +All entity definitions must be placed in the `base44/entities/` folder in your project root. Each entity is defined in its own `.jsonc` file. + +Example structure: +``` +my-app/ + base44/ + entities/ + user.jsonc + product.jsonc + order.jsonc +``` + +## How to Create an Entity + +1. Create a new `.jsonc` file in the `base44/entities/` directory +2. Define your entity schema following the structure below +3. Push the changes to Base44 using the CLI + +## Entity Schema Structure + +Each entity file follows a JSON Schema-like structure: + +```jsonc +{ + "name": "EntityName", // PascalCase entity name + "type": "object", // Always "object" + "properties": { + // Define your fields here + }, + "required": ["field1"] // Array of required field names +} +``` + +### Common Mistake: Nested Schema Property + +**WRONG** - Do NOT wrap properties in a `schema` object: +```jsonc +{ + "name": "Task", + "description": "A task entity", + "schema": { // ❌ WRONG - don't use nested "schema" + "type": "object", + "properties": { ... } + } +} +``` + +**CORRECT** - Put `type` and `properties` at the top level: +```jsonc +{ + "name": "Task", + "description": "A task entity", + "type": "object", // ✅ CORRECT - top level + "properties": { ... } // ✅ CORRECT - top level +} +``` + +This is a common mistake that will cause "Invalid schema: Schema must have a 'type' field" errors when pushing entities. + +## Supported Field Types + +### String + +Basic text field: +```jsonc +{ + "title": { + "type": "string", + "description": "Task title" + } +} +``` + +With format: +```jsonc +{ + "due_date": { + "type": "string", + "format": "date", + "description": "Due date" + } +} +``` + +Available formats: `date`, `date-time`, `time`, `email`, `uri`, `hostname`, `ipv4`, `ipv6`, `uuid`, `file`, `regex`, `richtext` + +### String with Enum + +Constrained to specific values: +```jsonc +{ + "status": { + "type": "string", + "enum": ["todo", "in_progress", "done"], + "default": "todo", + "description": "Current status" + } +} +``` + +### Number + +```jsonc +{ + "position": { + "type": "number", + "description": "Position for ordering" + } +} +``` + +### Integer + +For whole numbers only: +```jsonc +{ + "quantity": { + "type": "integer", + "description": "Item quantity", + "minimum": 0, + "maximum": 1000 + } +} +``` + +### Binary + +For file/blob data: +```jsonc +{ + "attachment": { + "type": "binary", + "description": "File attachment" + } +} +``` + +### Boolean + +```jsonc +{ + "notify_on_change": { + "type": "boolean", + "default": true, + "description": "Enable notifications" + } +} +``` + +### Array of Strings + +```jsonc +{ + "labels": { + "type": "array", + "items": { "type": "string" }, + "description": "Task labels/tags" + } +} +``` + +### Array of Objects + +```jsonc +{ + "attachments": { + "type": "array", + "description": "File attachments", + "items": { + "type": "object", + "properties": { + "name": { "type": "string" }, + "url": { "type": "string" }, + "type": { "type": "string" } + } + } + } +} +``` + +## Field Properties + +| Property | Description | +| ------------- | ---------------------------------------------------------------------------------------- | +| `type` | Data type: `string`, `number`, `integer`, `boolean`, `array`, `object`, `binary` | +| `description` | Human-readable description of the field | +| `enum` | Array of allowed values (for strings) | +| `enumNames` | Human-readable labels for enum values (same order as `enum`) | +| `default` | Default value when not provided | +| `format` | Format hint: `date`, `date-time`, `time`, `email`, `uri`, `hostname`, `ipv4`, `ipv6`, `uuid`, `file`, `regex`, `richtext` | +| `items` | Schema for array items | +| `properties` | Nested properties for object types | +| `$ref` | Reference to another schema definition | +| `minLength` | Minimum string length | +| `maxLength` | Maximum string length | +| `pattern` | Regex pattern for string validation | +| `minimum` | Minimum value for numbers | +| `maximum` | Maximum value for numbers | +| `rls` | Field-level security rules (see Field Level Security section) | + +## Complete Example + +Here's a complete entity definition for a Task: + +```jsonc +{ + "name": "Task", + "type": "object", + "properties": { + "title": { + "type": "string", + "description": "Task title" + }, + "description": { + "type": "string", + "description": "Task description" + }, + "status": { + "type": "string", + "enum": ["todo", "in_progress", "done"], + "default": "todo", + "description": "Current status of the task" + }, + "board_id": { + "type": "string", + "description": "Board this task belongs to" + }, + "assignee_email": { + "type": "string", + "description": "Email of assigned user" + }, + "priority": { + "type": "string", + "enum": ["low", "medium", "high"], + "default": "medium", + "description": "Task priority" + }, + "due_date": { + "type": "string", + "format": "date", + "description": "Due date" + }, + "labels": { + "type": "array", + "items": { "type": "string" }, + "description": "Task labels/tags" + } + }, + "required": ["title"] +} +``` + +## Naming Conventions + +- **Entity name**: Use PascalCase with alphanumeric characters only (e.g., `Task`, `TeamMember`, `ActivityLog`) + - Must match pattern: `/^[a-zA-Z0-9]+$/` + - Valid: `Task`, `TeamMember`, `Order123` + - Invalid: `Team_Member`, `Team-Member`, `Team Member` +- **File name**: Use kebab-case matching the entity (e.g., `task.jsonc`, `team-member.jsonc`, `activity-log.jsonc`) +- **Field names**: Use snake_case (e.g., `board_id`, `user_email`, `due_date`) + +## Relationships Between Entities + +To create relationships between entities, use ID reference fields: + +```jsonc +{ + "board_id": { + "type": "string", + "description": "Board this task belongs to" + }, + "team_id": { + "type": "string", + "description": "Associated team ID" + } +} +``` + +## Row Level Security (RLS) + +Row Level Security (RLS) controls which records users can access based on their identity and attributes. RLS rules are defined per entity inside the `rls` field of the schema. + +**Important:** If no RLS is defined, all records are accessible to all users. + +### RLS Operations + +RLS supports five operations: + +| Operation | Description | +|-----------|-------------| +| `create` | Control who can add new records | +| `read` | Control who can view records | +| `update` | Control who can modify records | +| `delete` | Control who can remove records | +| `write` | Shorthand for `create`, `update`, and `delete` combined | + +### Permission Values + +Each operation accepts one of the following values: + +1. **`true`** - Allow all users (including anonymous/unauthenticated) +2. **`false`** - Block all users +3. **Condition object** - Allow users matching the condition + +### Template Variables + +Use template variables to reference the current user's attributes: + +| Template | Description | +|----------|-------------| +| `{{user.id}}` | The user's ID | +| `{{user.email}}` | The user's email | +| `{{user.role}}` | The user's role | +| `{{user.data.field_name}}` | Custom field from the user's `data` object | + +### Built-in Entity Attributes + +Every entity record has these built-in attributes available for RLS rules: + +| Attribute | Description | +|-----------|-------------| +| `id` | Unique record identifier | +| `created_date` | Timestamp when record was created | +| `updated_date` | Timestamp when record was last updated | +| `created_by` | Email of the user who created the record | + +### Rule Types + +There are two condition types you can use: + +**1. Entity-to-user comparison** - Compare record fields to the current user's values: +```jsonc +{ + "created_by": "{{user.email}}" +} +``` + +**2. User condition check** - Check user properties directly using `user_condition`: +```jsonc +{ + "user_condition": { "role": "admin" } +} +``` + +**Important limitations:** +- `user_condition` only supports **simple equality** (e.g., `{ "role": "admin" }`) +- For `data.*` field comparisons, you can use operators: `$in`, `$nin`, `$ne`, `$all` +- Logical operators `$or`, `$and`, `$nor` are available for combining conditions +- You cannot filter by entity field values directly (e.g., `{"status": "published"}`) +- Only user-related conditions are allowed + +### RLS Examples + +**Owner-only access:** +```jsonc +{ + "created_by": "{{user.email}}" +} +``` + +**Department-based access:** +```jsonc +{ + "data.department": "{{user.data.department}}" +} +``` + +**Admin-only access:** +```jsonc +{ + "user_condition": { "role": "admin" } +} +``` + +**Complete RLS configuration:** +```jsonc +{ + "name": "Task", + "type": "object", + "properties": { + "title": { + "type": "string", + "description": "Task title" + }, + "status": { + "type": "string", + "enum": ["todo", "in_progress", "done"], + "default": "todo" + } + }, + "required": ["title"], + "rls": { + "create": true, + "read": { "created_by": "{{user.email}}" }, + "update": { "created_by": "{{user.email}}" }, + "delete": { "created_by": "{{user.email}}" } + } +} +``` + +### Common RLS Patterns + +**Public create, admin-only management (e.g., contact forms, waitlists):** +```jsonc +{ + "rls": { + "create": true, + "read": { "user_condition": { "role": "admin" } }, + "update": { "user_condition": { "role": "admin" } }, + "delete": { "user_condition": { "role": "admin" } } + } +} +``` + +**Owner-only access:** +```jsonc +{ + "rls": { + "create": true, + "read": { "created_by": "{{user.email}}" }, + "update": { "created_by": "{{user.email}}" }, + "delete": { "created_by": "{{user.email}}" } + } +} +``` + +**Logged-in users only:** +```jsonc +{ + "rls": { + "create": { "user_condition": { "id": "{{user.id}}" } }, + "read": true, + "update": { "created_by": "{{user.email}}" }, + "delete": { "created_by": "{{user.email}}" } + } +} +``` + +### Limitations + +- **user_condition is equality only:** `user_condition` only supports exact match (e.g., `{ "role": "admin" }`) - no operators +- **No comparison operators on user_condition:** `$gt`, `$lt`, `$regex`, `$expr`, `$where` are NOT supported for user conditions +- **No entity field filtering:** Cannot filter by entity field values (e.g., `{"status": "published"}`) +- **No deeply nested templates:** Templates like `{{user.data.profile.department}}` may not work + +**Supported operators:** +- **Logical operators:** `$or`, `$and`, `$nor` for combining multiple conditions +- **Field operators (for `data.*` fields only):** `$in`, `$nin`, `$ne`, `$all` + +### Complex Access Patterns + +For complex access patterns that require multiple conditions (e.g., "owner OR admin"), you have two options: + +1. **Use the Base44 Dashboard UI** - The dashboard allows adding multiple rules per operation with OR logic +2. **Use separate entities** - Split data into multiple entities with different access rules +3. **Use backend functions** - Implement custom access logic in backend functions + +## Field Level Security (FLS) + +Field Level Security allows you to control access to individual fields within an entity. FLS rules are defined within each field's schema using the `rls` property. + +### FLS Operations + +FLS supports the same operations as entity-level RLS: + +| Operation | Description | +|-----------|-------------| +| `create` | Control who can set this field when creating records | +| `read` | Control who can view this field | +| `update` | Control who can modify this field | +| `delete` | Control who can clear this field | +| `write` | Shorthand for `create`, `update`, and `delete` combined | + +### FLS Example + +```jsonc +{ + "name": "Employee", + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "Employee name" + }, + "salary": { + "type": "number", + "description": "Employee salary", + "rls": { + "read": { "user_condition": { "role": "hr" } }, + "update": { "user_condition": { "role": "hr" } } + } + }, + "department": { + "type": "string", + "description": "Department name" + } + }, + "required": ["name"] +} +``` + +In this example, only users with the `hr` role can read or update the `salary` field. All users with access to the entity can read/update other fields. + +### FLS Notes + +- If no field-level RLS is defined, the field inherits the entity-level RLS rules +- FLS rules follow the same condition format as entity-level RLS +- Use FLS for sensitive fields like salary, SSN, or internal notes + +## Pushing Entities + +The `entities push` command will push all entities that exist in the `base44/entities` folder. + +```bash +npx base44 entities push +``` + +For more details on the push command, see [entities-push.md](entities-push.md). diff --git a/evals/fixtures/cli-entities/skills/base44-cli/references/entities-push.md b/evals/fixtures/cli-entities/skills/base44-cli/references/entities-push.md new file mode 100644 index 0000000..63b92d7 --- /dev/null +++ b/evals/fixtures/cli-entities/skills/base44-cli/references/entities-push.md @@ -0,0 +1,71 @@ +# base44 entities push + +Push local entity definitions to Base44. + +## Syntax + +```bash +npx base44 entities push +``` + +## Authentication + +**Required**: Yes. If not authenticated, you'll be prompted to login first. + +## What It Does + +1. Pushes all entities that exist in the `base44/entities` folder +2. Validates that entities exist in the folder +3. Displays the count of entities to be pushed +4. Uploads entities to the Base44 backend +5. Reports the results: created, updated, and deleted entities + +## Prerequisites + +- Must be run from a Base44 project directory +- Project must have entity definitions in the `base44/entities` folder + +## Output + +```bash +$ npx base44 entities push + +Found 3 entities to push +Pushing entities to Base44... + +Created: User, Post +Updated: Comment +Deleted: OldEntity + +✓ Entities pushed successfully +``` + +## Entity Synchronization + +The push operation synchronizes your local entity schema with Base44: + +- **Created**: New entities that didn't exist in Base44 +- **Updated**: Existing entities with modified schema or configuration +- **Deleted**: Entities that were removed from your local configuration + +## Error Handling + +If no entities are found in your project: +```bash +$ npx base44 entities push +No entities found in project +``` + +## Use Cases + +- After defining new entities in your project +- When modifying existing entity schemas +- To sync entity changes before deploying +- As part of your development workflow when data models change + +## Notes + +- This command syncs the entity schema/structure, not the actual data +- Changes are applied to your Base44 project immediately +- Make sure to test entity changes in a development environment first +- Entity definitions are located in the `base44/entities/` directory diff --git a/evals/fixtures/cli-entities/skills/base44-cli/references/functions-create.md b/evals/fixtures/cli-entities/skills/base44-cli/references/functions-create.md new file mode 100644 index 0000000..c523cdc --- /dev/null +++ b/evals/fixtures/cli-entities/skills/base44-cli/references/functions-create.md @@ -0,0 +1,229 @@ +# Creating Functions + +Base44 functions are serverless backend functions that run on Deno. They are defined locally in your project and deployed to the Base44 backend. + +## Function Directory + +All function definitions must be placed in the `base44/functions/` folder in your project. Each function lives in its own subdirectory with a configuration file and entry point. + +Example structure: +``` +my-app/ + base44/ + functions/ + process-order/ + function.jsonc + index.ts + send-notification/ + function.jsonc + index.ts +``` + +## How to Create a Function + +1. Create a new directory in `base44/functions/` with your function name (use kebab-case) +2. Create a `function.jsonc` configuration file in the directory +3. Create the entry point file (e.g., `index.ts`) +4. Deploy the function using the CLI + +## Function Configuration + +Each function requires a `function.jsonc` configuration file: + +```jsonc +{ + "name": "my-function", + "entry": "index.ts" +} +``` + +### Configuration Properties + +| Property | Description | Required | +|----------|-------------|----------| +| `name` | Function name (must match `/^[^.]+$/` - no dots allowed) | Yes | +| `entry` | Entry point file path relative to the function directory (min 1 char) | Yes | + +## Entry Point File + +Functions run on Deno and must export using `Deno.serve()`. Use `npm:` prefix for npm packages. + +```typescript +import { createClientFromRequest } from "npm:@base44/sdk"; + +Deno.serve(async (req) => { + // Get authenticated client from request + const base44 = createClientFromRequest(req); + + // Parse input + const { orderId, action } = await req.json(); + + // Your logic here + const order = await base44.entities.Orders.get(orderId); + + // Return response + return Response.json({ + success: true, + order: order + }); +}); +``` + +### Request Object + +The function receives a standard Deno `Request` object: +- `req.json()` - Parse JSON body +- `req.text()` - Get raw text body +- `req.headers` - Access request headers +- `req.method` - HTTP method + +### Response Object + +Return using `Response.json()` for JSON responses: + +```typescript +// Success response +return Response.json({ data: result }); + +// Error response with status code +return Response.json({ error: "Something went wrong" }, { status: 400 }); + +// Not found +return Response.json({ error: "Order not found" }, { status: 404 }); +``` + +## Complete Example + +### Directory Structure +``` +base44/ + functions/ + process-order/ + function.jsonc + index.ts +``` + +### function.jsonc +```jsonc +{ + "name": "process-order", + "entry": "index.ts" +} +``` + +### index.ts +```typescript +import { createClientFromRequest } from "npm:@base44/sdk"; + +Deno.serve(async (req) => { + try { + const base44 = createClientFromRequest(req); + const { orderId } = await req.json(); + + // Validate input + if (!orderId) { + return Response.json( + { error: "Order ID is required" }, + { status: 400 } + ); + } + + // Fetch and process the order + const order = await base44.entities.Orders.get(orderId); + if (!order) { + return Response.json( + { error: "Order not found" }, + { status: 404 } + ); + } + + return Response.json({ + success: true, + orderId: order.id, + processedAt: new Date().toISOString() + }); + + } catch (error) { + return Response.json( + { error: error.message }, + { status: 500 } + ); + } +}); +``` + +## Using Service Role Access + +For admin-level operations, use `asServiceRole`: + +```typescript +import { createClientFromRequest } from "npm:@base44/sdk"; + +Deno.serve(async (req) => { + const base44 = createClientFromRequest(req); + + // Check user is authenticated + const user = await base44.auth.me(); + if (!user) { + return Response.json({ error: "Unauthorized" }, { status: 401 }); + } + + // Use service role for admin operations + const allOrders = await base44.asServiceRole.entities.Orders.list(); + + return Response.json({ orders: allOrders }); +}); +``` + +## Using Secrets + +Access environment variables configured in the app dashboard: + +```typescript +Deno.serve(async (req) => { + // Access environment variables (configured in app settings) + const apiKey = Deno.env.get("STRIPE_API_KEY"); + + const response = await fetch("https://api.stripe.com/v1/charges", { + headers: { + "Authorization": `Bearer ${apiKey}` + } + }); + + return Response.json(await response.json()); +}); +``` + +## Naming Conventions + +- **Directory name**: Use kebab-case (e.g., `process-order`, `send-notification`) +- **Function name**: Match the directory name, must match pattern `/^[^.]+$/` (no dots allowed) + - Valid: `process-order`, `send_notification`, `myFunction` + - Invalid: `process.order`, `send.notification.v2` +- **Entry file**: Typically `index.ts` or `index.js` + +## Deploying Functions + +After creating your function, deploy it to Base44: + +```bash +npx base44 functions deploy +``` + +For more details on deploying, see [functions-deploy.md](functions-deploy.md). + +## Notes + +- Functions run on Deno runtime, not Node.js +- Use `npm:` prefix for npm packages (e.g., `npm:@base44/sdk`) +- Use `createClientFromRequest(req)` to get a client that inherits the caller's auth context +- Configure secrets via app dashboard for API keys +- Make sure to handle errors gracefully and return appropriate HTTP status codes + +## Common Mistakes + +| Wrong | Correct | Why | +|-------|---------|-----| +| `functions/myFunction.js` (single file) | `functions/my-function/index.ts` + `function.jsonc` | Functions require subdirectory with config | +| `import { ... } from "@base44/sdk"` | `import { ... } from "npm:@base44/sdk"` | Deno requires `npm:` prefix for npm packages | +| `MyFunction` or `myFunction` directory | `my-function` directory | Use kebab-case for directory names | diff --git a/evals/fixtures/cli-entities/skills/base44-cli/references/functions-deploy.md b/evals/fixtures/cli-entities/skills/base44-cli/references/functions-deploy.md new file mode 100644 index 0000000..f89d796 --- /dev/null +++ b/evals/fixtures/cli-entities/skills/base44-cli/references/functions-deploy.md @@ -0,0 +1,78 @@ +# base44 functions deploy + +Deploy local function definitions to Base44. + +## Syntax + +```bash +npx base44 functions deploy +``` + +## Authentication + +**Required**: Yes. If not authenticated, you'll be prompted to login first. + +## What It Does + +1. Scans the `base44/functions/` directory for function definitions +2. Validates that functions exist and have valid configurations +3. Displays the count of functions to be deployed +4. Uploads function code and configuration to Base44 +5. Reports the results: deployed and deleted functions + +## Prerequisites + +- Must be run from a Base44 project directory +- Project must have function definitions in the `base44/functions/` folder +- Each function must have a valid `function.jsonc` config file + +## Output + +```bash +$ npx base44 functions deploy + +Found 2 functions to deploy +Deploying functions to Base44... + +Deployed: process-order, send-notification +Deleted: old-function + +✓ Functions deployed successfully +``` + +## Function Synchronization + +The deploy operation synchronizes your local functions with Base44: + +- **Deployed**: Functions that were created or updated +- **Deleted**: Functions that were removed from your local configuration + +## Error Handling + +If no functions are found in your project: +```bash +$ npx base44 functions deploy +No functions found. Create functions in the 'functions' directory. +``` + +If a function has configuration errors: +```bash +$ npx base44 functions deploy +Function deployment errors: +'my-function' function: Entry point cannot be empty +``` + +## Use Cases + +- After creating new functions in your project +- When modifying existing function code or configuration +- To sync function changes before testing +- As part of your development workflow when backend logic changes + +## Notes + +- This command deploys the function code and configuration +- Changes are applied to your Base44 project immediately +- Make sure to test functions in a development environment first +- Function definitions are located in the `base44/functions/` directory +- For how to create functions, see [functions-create.md](functions-create.md) diff --git a/evals/fixtures/cli-entities/skills/base44-cli/references/link.md b/evals/fixtures/cli-entities/skills/base44-cli/references/link.md new file mode 100644 index 0000000..0b009ce --- /dev/null +++ b/evals/fixtures/cli-entities/skills/base44-cli/references/link.md @@ -0,0 +1,81 @@ +# base44 link + +Links an existing local Base44 project to a Base44 app in the cloud. Use this when you have a `base44/config.jsonc` but haven't connected it to a Base44 app yet. + +## Critical: When to Use Link vs Create + +| Scenario | Command | +|----------|---------| +| Starting fresh, no `base44/` folder | `npx base44 create` | +| Have `base44/config.jsonc` but no `.app.jsonc` | `npx base44 link` | +| Project already linked (has `.app.jsonc`) | Already done, use `deploy` | + +## Syntax + +```bash +npx base44 link [options] +``` + +## Options + +| Option | Description | Required | +|--------|-------------|----------| +| `-c, --create` | Create a new project (skip selection prompt) | No | +| `-n, --name ` | Project name (required when `--create` is used) | With `--create` | +| `-d, --description ` | Project description | No | +| `-p, --projectId ` | Project ID to link to an existing project (skip selection prompt) | No | + +## Non-Interactive Mode + +For CI/CD or agent use: + +**Create a new project:** +```bash +npx base44 link --create --name my-app +``` + +**Link to an existing project:** +```bash +npx base44 link --projectId +``` + +WRONG: `npx base44 link --create` (missing --name) +WRONG: `npx base44 link --create --projectId ` (cannot use both) +RIGHT: `npx base44 link --create --name my-app` +RIGHT: `npx base44 link --projectId ` + +## Examples + +```bash +# Interactive mode - prompts for project details +npx base44 link + +# Non-interactive - create and link in one step +npx base44 link --create --name my-app + +# With description +npx base44 link --create --name my-app --description "My awesome app" + +# Link to a specific existing project by ID +npx base44 link --projectId abc123 +``` + +## What It Does + +1. Finds the `base44/config.jsonc` in the current directory (or parent directories) +2. Verifies no `.app.jsonc` exists (project not already linked) +3. Either: + - Creates a new Base44 app in the cloud (with `--create`), OR + - Links to an existing project (with `--projectId` or interactive selection) +4. Writes the app ID to `base44/.app.jsonc` + +## Requirements + +- Must have `base44/config.jsonc` in the project +- Must NOT have `base44/.app.jsonc` (use `deploy` if already linked) +- Must be authenticated (run `npx base44 login` first) + +## Notes + +- After linking, you can deploy resources with `npx base44 deploy` +- The `.app.jsonc` file should be git-ignored (contains your app ID) diff --git a/evals/fixtures/cli-entities/skills/base44-cli/references/rls-examples.md b/evals/fixtures/cli-entities/skills/base44-cli/references/rls-examples.md new file mode 100644 index 0000000..7d3ef42 --- /dev/null +++ b/evals/fixtures/cli-entities/skills/base44-cli/references/rls-examples.md @@ -0,0 +1,463 @@ +# RLS Examples + +Practical Row-Level Security patterns for common application types. + +**Important:** Base44 RLS supports: +- **Logical operators:** `$or`, `$and`, `$nor` for combining conditions +- **Field operators (for `data.*` fields):** `$in`, `$nin`, `$ne`, `$all` +- **user_condition:** Equality only (no operators) + +## Contents +- [Simple Patterns (JSON Schema)](#simple-patterns-json-schema) +- [Using Operators](#using-operators) +- [Field-Level Security Examples](#field-level-security-examples) +- [Complex Patterns (Dashboard UI or Backend)](#complex-patterns-dashboard-ui-or-backend) +- [Best Practices](#best-practices) + +--- + +## Simple Patterns (JSON Schema) + +These patterns work with the JSON schema RLS format. + +### Todo App - Owner-only access + +Users see and manage only their own tasks. + +```jsonc +{ + "name": "Task", + "type": "object", + "properties": { + "title": { "type": "string" }, + "description": { "type": "string" }, + "completed": { "type": "boolean" }, + "priority": { "type": "string", "enum": ["low", "medium", "high"] }, + "due_date": { "type": "string", "format": "date" } + }, + "rls": { + "create": true, + "read": { "created_by": "{{user.email}}" }, + "update": { "created_by": "{{user.email}}" }, + "delete": { "created_by": "{{user.email}}" } + } +} +``` + +### Contact Form - Public create, admin-only read + +Anyone can submit, only admins can view submissions. + +```jsonc +{ + "name": "ContactSubmission", + "type": "object", + "properties": { + "name": { "type": "string" }, + "email": { "type": "string", "format": "email" }, + "message": { "type": "string" } + }, + "rls": { + "create": true, + "read": { "user_condition": { "role": "admin" } }, + "update": { "user_condition": { "role": "admin" } }, + "delete": { "user_condition": { "role": "admin" } } + } +} +``` + +### User Profile - Self-management + +Users can only access their own profile. + +```jsonc +{ + "name": "UserProfile", + "type": "object", + "properties": { + "name": { "type": "string" }, + "avatar_url": { "type": "string" }, + "bio": { "type": "string" }, + "preferences": { "type": "object" } + }, + "rls": { + "create": true, + "read": { "created_by": "{{user.email}}" }, + "update": { "created_by": "{{user.email}}" }, + "delete": { "created_by": "{{user.email}}" } + } +} +``` + +### Department Data - Same department access + +Users can only see records from their department. + +```jsonc +{ + "name": "DepartmentAnnouncement", + "type": "object", + "properties": { + "title": { "type": "string" }, + "content": { "type": "string" }, + "department": { "type": "string" } + }, + "rls": { + "create": { "user_condition": { "role": "manager" } }, + "read": { "data.department": "{{user.data.department}}" }, + "update": { "user_condition": { "role": "manager" } }, + "delete": { "user_condition": { "role": "admin" } } + } +} +``` + +### Subscription - Admin-managed, user-readable via email field + +```jsonc +{ + "name": "Subscription", + "type": "object", + "properties": { + "user_email": { "type": "string" }, + "tier": { "type": "string", "enum": ["free", "basic", "pro", "enterprise"] }, + "credits": { "type": "number" }, + "renewal_date": { "type": "string", "format": "date" } + }, + "rls": { + "create": { "user_condition": { "role": "admin" } }, + "read": { "data.user_email": "{{user.email}}" }, + "update": { "user_condition": { "role": "admin" } }, + "delete": { "user_condition": { "role": "admin" } } + } +} +``` + +**Note:** This pattern only allows users to read their own subscription. Admins need to use the Dashboard UI to configure additional read access for themselves. + +### Private Data - Owner-only + +```jsonc +{ + "name": "PrivateNotes", + "type": "object", + "properties": { + "title": { "type": "string" }, + "content": { "type": "string" }, + "tags": { "type": "array", "items": { "type": "string" } } + }, + "rls": { + "create": true, + "read": { "created_by": "{{user.email}}" }, + "update": { "created_by": "{{user.email}}" }, + "delete": { "created_by": "{{user.email}}" } + } +} +``` + +### Public Read, Authenticated Write + +Anyone can read, only logged-in users can create/edit their own records. + +```jsonc +{ + "name": "BlogPost", + "type": "object", + "properties": { + "title": { "type": "string" }, + "content": { "type": "string" }, + "author_email": { "type": "string" } + }, + "rls": { + "create": true, + "read": true, + "update": { "created_by": "{{user.email}}" }, + "delete": { "created_by": "{{user.email}}" } + } +} +``` + +--- + +## Using Operators + +### Logical Operators + +Combine multiple conditions using `$or`, `$and`, or `$nor`: + +**Owner OR Admin access:** +```jsonc +{ + "name": "Document", + "type": "object", + "properties": { + "title": { "type": "string" }, + "content": { "type": "string" } + }, + "rls": { + "create": true, + "read": { + "$or": [ + { "created_by": "{{user.email}}" }, + { "user_condition": { "role": "admin" } } + ] + }, + "update": { + "$or": [ + { "created_by": "{{user.email}}" }, + { "user_condition": { "role": "admin" } } + ] + }, + "delete": { "user_condition": { "role": "admin" } } + } +} +``` + +**Multiple roles with $or:** +```jsonc +{ + "rls": { + "read": { + "$or": [ + { "user_condition": { "role": "admin" } }, + { "user_condition": { "role": "manager" } }, + { "user_condition": { "role": "hr" } } + ] + } + } +} +``` + +### Field Operators for data.* Fields + +Use `$in`, `$nin`, `$ne`, `$all` for comparing entity data fields: + +**Access based on tags ($in):** +```jsonc +{ + "rls": { + "read": { + "data.category": { "$in": ["public", "shared"] } + } + } +} +``` + +**Exclude specific statuses ($nin):** +```jsonc +{ + "rls": { + "read": { + "data.status": { "$nin": ["archived", "deleted"] } + } + } +} +``` + +**Not equal ($ne):** +```jsonc +{ + "rls": { + "read": { + "data.visibility": { "$ne": "private" } + } + } +} +``` + +**All tags must match ($all):** +```jsonc +{ + "rls": { + "read": { + "data.required_tags": { "$all": ["approved", "reviewed"] } + } + } +} +``` + +### Combining Logical and Field Operators + +```jsonc +{ + "rls": { + "read": { + "$and": [ + { "data.status": { "$ne": "draft" } }, + { + "$or": [ + { "created_by": "{{user.email}}" }, + { "data.visibility": "public" } + ] + } + ] + } + } +} +``` + +--- + +## Field-Level Security Examples + +Control access to specific fields within an entity. + +### Sensitive Salary Field + +```jsonc +{ + "name": "Employee", + "type": "object", + "properties": { + "name": { "type": "string" }, + "email": { "type": "string", "format": "email" }, + "salary": { + "type": "number", + "description": "Annual salary", + "rls": { + "read": { "user_condition": { "role": "hr" } }, + "write": { "user_condition": { "role": "hr" } } + } + }, + "performance_notes": { + "type": "string", + "description": "Manager notes", + "rls": { + "read": { + "$or": [ + { "user_condition": { "role": "manager" } }, + { "user_condition": { "role": "hr" } } + ] + }, + "write": { "user_condition": { "role": "manager" } } + } + } + } +} +``` + +### Admin-Only Internal Fields + +```jsonc +{ + "name": "Order", + "type": "object", + "properties": { + "order_number": { "type": "string" }, + "total": { "type": "number" }, + "internal_notes": { + "type": "string", + "description": "Internal processing notes", + "rls": { + "read": { "user_condition": { "role": "admin" } }, + "write": { "user_condition": { "role": "admin" } } + } + }, + "profit_margin": { + "type": "number", + "description": "Profit margin percentage", + "rls": { + "read": { "user_condition": { "role": "admin" } }, + "write": false + } + } + } +} +``` + +--- + +## Complex Patterns (Dashboard UI or Backend) + +Some patterns may still require the Dashboard UI or backend functions. + +### Bidirectional Relationships (e.g., Friendships, Matches) + +**Requirement:** Either party in a relationship should have access. + +**Now possible with $or:** +```jsonc +{ + "rls": { + "read": { + "$or": [ + { "data.user_a_email": "{{user.email}}" }, + { "data.user_b_email": "{{user.email}}" } + ] + } + } +} +``` + +**Alternative solutions:** +1. **Entity redesign:** Store two records per relationship (one for each party) +2. **Backend function:** Query with custom logic + +### Complex Business Logic + +**Requirement:** Access depends on multiple entity fields with complex conditions. + +**JSON Schema limitation:** While operators help, very complex business logic may still be hard to express. + +**Solution options:** +1. **Backend function:** Implement custom access logic +2. **Combine simpler rules:** Break complex rules into simpler entity-level and field-level rules + +--- + +## Best Practices + +### Security Strategy + +Use a combination of entity-level RLS and field-level security: + +| Data Type | Approach | Example | +|-----------|----------|---------| +| User-editable | Entity RLS: Owner-only | UserProfile with `created_by` check | +| Sensitive fields | Field-level RLS | Salary field with HR role check | +| Multi-role access | `$or` with user_condition | Admin OR Manager access | +| Conditional access | Field operators | `$in`, `$ne` on data fields | +| Public content | Entity RLS: `read: true` | PublicPost | +| Private content | Entity RLS: Owner-only | PrivateNote | + +### When to Use Each Approach + +| Requirement | Approach | +|-------------|----------| +| Single condition (owner, admin, department) | JSON Schema RLS | +| Multiple OR/AND conditions | JSON Schema RLS with `$or`/`$and` | +| Field value checks with `$in`/`$ne`/etc. | JSON Schema RLS for `data.*` fields | +| Field-level access control | JSON Schema FLS (field-level `rls`) | +| Complex comparison operators (`$gt`, `$lt`) | Backend functions | +| Very complex business logic | Backend functions | + +### Common Role Patterns + +| Role | Typical Access | +|------|----------------| +| `admin` | Full access to all records | +| `moderator` | Read/update access, limited delete | +| `manager` | Department-scoped access | +| `user` | Own records only | + +### Supported Operators Summary + +| Operator | Supported | Notes | +|----------|-----------|-------| +| `$or` | Yes | Combine multiple conditions | +| `$and` | Yes | All conditions must match | +| `$nor` | Yes | None of the conditions match | +| `$in` | Yes | For `data.*` fields only | +| `$nin` | Yes | For `data.*` fields only | +| `$ne` | Yes | For `data.*` fields only | +| `$all` | Yes | For `data.*` fields only | +| `$gt`, `$lt`, `$gte`, `$lte` | No | Use backend functions | +| `$regex` | No | Use backend functions | + +### Limitations Summary + +| Not Supported | Alternative | +|---------------|-------------| +| Operators on `user_condition` | Use equality only for user checks | +| Comparison operators (`$gt`, `$lt`) | Backend functions | +| Regex matching (`$regex`) | Backend functions | +| Cross-entity relationships | Backend functions | diff --git a/evals/fixtures/cli-entities/skills/base44-cli/references/site-deploy.md b/evals/fixtures/cli-entities/skills/base44-cli/references/site-deploy.md new file mode 100644 index 0000000..929d302 --- /dev/null +++ b/evals/fixtures/cli-entities/skills/base44-cli/references/site-deploy.md @@ -0,0 +1,118 @@ +# base44 site deploy + +Deploy built site files to Base44 hosting. + +## Table of Contents + +- [Syntax](#syntax) +- [Authentication](#authentication) +- [Prerequisites](#prerequisites) +- [How It Works](#how-it-works) +- [Interactive Flow](#interactive-flow) +- [Typical Workflow](#typical-workflow) +- [Configuration](#configuration) +- [Error Handling](#error-handling) +- [Use Cases](#use-cases) +- [Notes](#notes) + +## Syntax + +```bash +npx base44 site deploy [options] +``` + +## Options + +| Option | Description | +| ------------ | ------------------------- | +| `-y, --yes` | Skip confirmation prompt | + +Use `-y` flag for non-interactive/automated deployments: + +```bash +npx base44 site deploy -y +``` + +## Authentication + +**Required**: Yes. If not authenticated, you'll be prompted to login first. + +## Prerequisites + +- Must be run from a Base44 project directory +- Project must have `site.outputDirectory` configured in project config +- Site must be built before deploying (run your build command first) +- **SPA only**: Base44 hosting supports Single Page Applications with a single `index.html` entry point. All routes are served from `index.html` (client-side routing). + +## How It Works + +1. Reads project configuration +2. Validates that site configuration exists +3. Prompts for deployment confirmation showing the output directory +4. Creates an archive of site files from the output directory +5. Deploys to Base44 hosting +6. Returns the app URL + +## Interactive Flow + +```bash +$ npx base44 site deploy + +Deploy site from ./dist? (yes/no) yes + +Creating archive... +Uploading to Base44... +Deploying... + +✓ Deployment successful! + +Visit your site at: https://my-app.base44.app +``` + +## Typical Workflow + +```bash +# 1. Build your site using your framework's build command +npm run build + +# 2. Deploy to Base44 +npx base44 site deploy +``` + +## Configuration + +The `site.outputDirectory` in your project configuration should point to where your framework outputs built files: + +- Vite: typically `./dist` +- Next.js: typically `./.next` or `./out` +- Create React App: typically `./build` +- Custom: whatever your build tool outputs to + +## Error Handling + +If site configuration is missing: +```bash +$ npx base44 site deploy +Error: No site configuration found in project +``` + +If you cancel the deployment: +```bash +Deploy site from ./dist? (yes/no) no +Deployment cancelled +``` + +## Use Cases + +- Deploy your site after making changes +- Push new versions of your application +- Deploy after updating content or functionality +- Part of your CI/CD pipeline + +## Notes + +- Always build your site before deploying +- The command deploys whatever is in your output directory +- Make sure your build completed successfully before deploying +- Previous deployments are preserved (versioned) in Base44 +- Deployment is immediate and updates your live site diff --git a/evals/fixtures/cli-entities/skills/base44-sdk/SKILL.md b/evals/fixtures/cli-entities/skills/base44-sdk/SKILL.md new file mode 100644 index 0000000..01ddd7f --- /dev/null +++ b/evals/fixtures/cli-entities/skills/base44-sdk/SKILL.md @@ -0,0 +1,296 @@ +--- +name: base44-sdk +description: "The base44 SDK is the library to communicate with base44 services. In projects, you use it to communicate with remote resources (entities, backend functions, ai agents) and to write backend functions. This skill is the place for learning about available modules and types. When you plan or implement a feature, you must learn this skill" +--- + +# Base44 Coder + +Build apps on the Base44 platform using the Base44 JavaScript SDK. + +## ⚡ IMMEDIATE ACTION REQUIRED - Read This First + +This skill activates on ANY mention of "base44" or when a `base44/` folder exists. **DO NOT read documentation files or search the web before acting.** + +**Your first action MUST be:** +1. Check if `base44/config.jsonc` exists in the current directory +2. If **YES** (existing project scenario): + - This skill (base44-sdk) handles the request + - Implement features using Base44 SDK + - Do NOT use base44-cli unless user explicitly requests CLI commands +3. If **NO** (new project scenario): + - Transfer to base44-cli skill for project initialization + - This skill cannot help until project is initialized + +## When to Use This Skill vs base44-cli + +**Use base44-sdk when:** +- Building features in an **EXISTING** Base44 project +- `base44/config.jsonc` already exists in the project +- Base44 SDK imports are present (`@base44/sdk`) +- Writing JavaScript/TypeScript code using Base44 SDK modules +- Implementing functionality, components, or features +- User mentions: "implement", "build a feature", "add functionality", "write code for" +- User says "create a [type] app" **and** a Base44 project already exists + +**DO NOT USE base44-sdk for:** +- ❌ Initializing new Base44 projects (use `base44-cli` instead) +- ❌ Empty directories without Base44 configuration +- ❌ When user says "create a new Base44 project/app/site" and no project exists +- ❌ CLI commands like `npx base44 create`, `npx base44 deploy`, `npx base44 login` (use `base44-cli`) + +**Skill Dependencies:** +- `base44-sdk` assumes a Base44 project is **already initialized** +- `base44-cli` is a **prerequisite** for `base44-sdk` in new projects +- If user wants to "create an app" and no Base44 project exists, use `base44-cli` first + +**State Check Logic:** +Before selecting this skill, verify: +- IF (user mentions "create/build app" OR "make a project"): + - IF (directory is empty OR no `base44/config.jsonc` exists): + → Use **base44-cli** (project initialization needed) + - ELSE: + → Use **base44-sdk** (project exists, build features) + +## Quick Start + +```javascript +// In Base44-generated apps, base44 client is pre-configured and available + +// CRUD operations +const task = await base44.entities.Task.create({ title: "New task", status: "pending" }); +const tasks = await base44.entities.Task.list(); +await base44.entities.Task.update(task.id, { status: "done" }); + +// Get current user +const user = await base44.auth.me(); +``` + +```javascript +// External apps +import { createClient } from "@base44/sdk"; + +// IMPORTANT: Use 'appId' (NOT 'clientId' or 'id') +const base44 = createClient({ appId: "your-app-id" }); +await base44.auth.loginViaEmailPassword("user@example.com", "password"); +``` + +## ⚠️ CRITICAL: Do Not Hallucinate APIs + +**Before writing ANY Base44 code, verify method names against this table or [QUICK_REFERENCE.md](references/QUICK_REFERENCE.md).** + +Base44 SDK has unique method names. Do NOT assume patterns from Firebase, Supabase, or other SDKs. + +### Authentication - WRONG vs CORRECT + +| ❌ WRONG (hallucinated) | ✅ CORRECT | +|------------------------|-----------| +| `signInWithGoogle()` | `loginWithProvider('google')` | +| `signInWithProvider('google')` | `loginWithProvider('google')` | +| `auth.google()` | `loginWithProvider('google')` | +| `signInWithEmailAndPassword(email, pw)` | `loginViaEmailPassword(email, pw)` | +| `signIn(email, pw)` | `loginViaEmailPassword(email, pw)` | +| `createUser()` / `signUp()` | `register({email, password})` | +| `onAuthStateChanged()` | `me()` (no listener, call when needed) | +| `currentUser` | `await auth.me()` | + +### Functions - WRONG vs CORRECT + +| ❌ WRONG (hallucinated) | ✅ CORRECT | +|------------------------|-----------| +| `functions.call('name', data)` | `functions.invoke('name', data)` | +| `functions.run('name', data)` | `functions.invoke('name', data)` | +| `callFunction('name', data)` | `functions.invoke('name', data)` | +| `httpsCallable('name')(data)` | `functions.invoke('name', data)` | + +### Integrations - WRONG vs CORRECT + +| ❌ WRONG (hallucinated) | ✅ CORRECT | +|------------------------|-----------| +| `ai.generate(prompt)` | `integrations.Core.InvokeLLM({prompt})` | +| `openai.chat(prompt)` | `integrations.Core.InvokeLLM({prompt})` | +| `llm(prompt)` | `integrations.Core.InvokeLLM({prompt})` | +| `sendEmail(to, subject, body)` | `integrations.Core.SendEmail({to, subject, body})` | +| `email.send()` | `integrations.Core.SendEmail({to, subject, body})` | +| `uploadFile(file)` | `integrations.Core.UploadFile({file})` | +| `storage.upload(file)` | `integrations.Core.UploadFile({file})` | + +### Entities - WRONG vs CORRECT + +| ❌ WRONG (hallucinated) | ✅ CORRECT | +|------------------------|-----------| +| `entities.Task.find({...})` | `entities.Task.filter({...})` | +| `entities.Task.findOne(id)` | `entities.Task.get(id)` | +| `entities.Task.insert(data)` | `entities.Task.create(data)` | +| `entities.Task.remove(id)` | `entities.Task.delete(id)` | +| `entities.Task.onChange(cb)` | `entities.Task.subscribe(cb)` | + +## SDK Modules + +| Module | Purpose | Reference | +|--------|---------|-----------| +| `entities` | CRUD operations on data models | [entities.md](references/entities.md) | +| `auth` | Login, register, user management | [auth.md](references/auth.md) | +| `agents` | AI conversations and messages | [base44-agents.md](references/base44-agents.md) | +| `functions` | Backend function invocation | [functions.md](references/functions.md) | +| `integrations` | AI, email, file uploads, custom APIs | [integrations.md](references/integrations.md) | +| `connectors` | OAuth tokens (service role only) | [connectors.md](references/connectors.md) | +| `analytics` | Track custom events and user activity | [analytics.md](references/analytics.md) | +| `appLogs` | Log user activity in app | [app-logs.md](references/app-logs.md) | +| `users` | Invite users to the app | [users.md](references/users.md) | + +For client setup and authentication modes, see [client.md](references/client.md). + +**TypeScript Support:** Each reference file includes a "Type Definitions" section with TypeScript interfaces and types for the module's methods, parameters, and return values. + +## Installation + +Install the Base44 SDK: + +```bash +npm install @base44/sdk +``` + +**Important:** Never assume or hardcode the `@base44/sdk` package version. Always install without a version specifier to get the latest version. + +## Creating a Client (External Apps) + +When creating a client in external apps, **ALWAYS use `appId` as the parameter name**: + +```javascript +import { createClient } from "@base44/sdk"; + +// ✅ CORRECT +const base44 = createClient({ appId: "your-app-id" }); + +// ❌ WRONG - Do NOT use these: +// const base44 = createClient({ clientId: "your-app-id" }); // WRONG +// const base44 = createClient({ id: "your-app-id" }); // WRONG +``` + +**Required parameter:** `appId` (string) - Your Base44 application ID + +**Optional parameters:** +- `token` (string) - Pre-authenticated user token +- `options` (object) - Configuration options + - `options.onError` (function) - Global error handler + +**Example with error handler:** +```javascript +const base44 = createClient({ + appId: "your-app-id", + options: { + onError: (error) => { + console.error("Base44 error:", error); + } + } +}); +``` + +## Module Selection + +**Working with app data?** +- Create/read/update/delete records → `entities` +- Import data from file → `entities.importEntities()` +- Realtime updates → `entities.EntityName.subscribe()` + +**User management?** +- Login/register/logout → `auth` +- Get current user → `auth.me()` +- Update user profile → `auth.updateMe()` +- Invite users → `users.inviteUser()` + +**AI features?** +- Chat with AI agents → `agents` (requires logged-in user) +- Create new conversation → `agents.createConversation()` +- Manage conversations → `agents.getConversations()` +- Generate text/JSON with AI → `integrations.Core.InvokeLLM()` +- Generate images → `integrations.Core.GenerateImage()` + +**Custom backend logic?** +- Run server-side code → `functions.invoke()` +- Need admin access → `base44.asServiceRole.functions.invoke()` + +**External services?** +- Send emails → `integrations.Core.SendEmail()` +- Upload files → `integrations.Core.UploadFile()` +- Custom APIs → `integrations.custom.call()` +- OAuth tokens (Google, Slack) → `connectors` (backend only) + +**Tracking and analytics?** +- Track custom events → `analytics.track()` +- Log page views/activity → `appLogs.logUserInApp()` + +## Common Patterns + +### Filter and Sort Data + +```javascript +const pendingTasks = await base44.entities.Task.filter( + { status: "pending", assignedTo: userId }, // query + "-created_date", // sort (descending) + 10, // limit + 0 // skip +); +``` + +### Protected Routes (check auth) + +```javascript +const user = await base44.auth.me(); +if (!user) { + // Navigate to your custom login page + navigate('/login', { state: { returnTo: window.location.pathname } }); + return; +} +``` + +### Backend Function Call + +```javascript +// Frontend +const result = await base44.functions.invoke("processOrder", { + orderId: "123", + action: "ship" +}); + +// Backend function (Deno) +import { createClientFromRequest } from "npm:@base44/sdk"; + +Deno.serve(async (req) => { + const base44 = createClientFromRequest(req); + const { orderId, action } = await req.json(); + // Process with service role for admin access + const order = await base44.asServiceRole.entities.Orders.get(orderId); + return Response.json({ success: true }); +}); +``` + +### Service Role Access + +Use `asServiceRole` in backend functions for admin-level operations: + +```javascript +// User mode - respects permissions +const myTasks = await base44.entities.Task.list(); + +// Service role - full access (backend only) +const allTasks = await base44.asServiceRole.entities.Task.list(); +const token = await base44.asServiceRole.connectors.getAccessToken("slack"); +``` + +## Frontend vs Backend + +| Capability | Frontend | Backend | +|------------|----------|---------| +| `entities` (user's data) | Yes | Yes | +| `auth` | Yes | Yes | +| `agents` | Yes | Yes | +| `functions.invoke()` | Yes | Yes | +| `integrations` | Yes | Yes | +| `analytics` | Yes | Yes | +| `appLogs` | Yes | Yes | +| `users` | Yes | Yes | +| `asServiceRole.*` | No | Yes | +| `connectors` | No | Yes | + +Backend functions use `Deno.serve()` and `createClientFromRequest(req)` to get a properly authenticated client. diff --git a/evals/fixtures/cli-entities/skills/base44-sdk/references/QUICK_REFERENCE.md b/evals/fixtures/cli-entities/skills/base44-sdk/references/QUICK_REFERENCE.md new file mode 100644 index 0000000..d357384 --- /dev/null +++ b/evals/fixtures/cli-entities/skills/base44-sdk/references/QUICK_REFERENCE.md @@ -0,0 +1,155 @@ +# Base44 SDK Quick Reference + +Compact method signatures for all SDK modules. **Verify against this before writing code.** + +--- + +## Auth (`base44.auth.*`) + +``` +loginViaEmailPassword(email, password, turnstileToken?) → Promise<{access_token, user}> +loginWithProvider('google' | 'microsoft' | 'facebook', fromUrl?) → void +me() → Promise +updateMe(data) → Promise +isAuthenticated() → Promise +logout(redirectUrl?) → void +redirectToLogin(nextUrl) → void # ⚠️ Avoid - prefer custom login UI +register({email, password, turnstile_token?, referral_code?}) → Promise +verifyOtp({email, otpCode}) → Promise +resendOtp(email) → Promise +inviteUser(userEmail, role) → Promise +resetPasswordRequest(email) → Promise +resetPassword({resetToken, newPassword}) → Promise +changePassword({userId, currentPassword, newPassword}) → Promise +setToken(token, saveToStorage?) → void +``` + +--- + +## Entities (`base44.entities.EntityName.*`) + +``` +create(data) → Promise +bulkCreate(dataArray) → Promise +list(sort?, limit?, skip?, fields?) → Promise +filter(query, sort?, limit?, skip?, fields?) → Promise +get(id) → Promise +update(id, data) → Promise +delete(id) → Promise +deleteMany(query) → Promise +importEntities(file) → Promise // frontend only +subscribe(callback) → () => void // returns unsubscribe fn +``` + +**Sort:** Use `-fieldName` for descending (e.g., `-created_date`) + +--- + +## Functions (`base44.functions.*`) + +``` +invoke(functionName, data) → Promise +``` + +**Backend:** Use `base44.asServiceRole.functions.invoke()` for admin access. + +--- + +## Integrations (`base44.integrations.Core.*`) + +``` +InvokeLLM({prompt, add_context_from_internet?, response_json_schema?, file_urls?}) → Promise +GenerateImage({prompt}) → Promise<{url}> +SendEmail({to, subject, body, from_name?}) → Promise +UploadFile({file}) → Promise<{file_url}> +UploadPrivateFile({file}) → Promise<{file_uri}> +CreateFileSignedUrl({file_uri, expires_in?}) → Promise<{signed_url}> +ExtractDataFromUploadedFile({file_url, json_schema}) → Promise +``` + +### Custom Integrations (`base44.integrations.custom.*`) + +``` +call(slug, operationId, {payload?, pathParams?, queryParams?, headers?}?) → Promise<{success, status_code, data}> +``` + +**operationId format:** `"method:/path"` (e.g., `"get:/contacts"`, `"post:/users/{id}"`) + +--- + +## Analytics (`base44.analytics.*`) + +``` +track({eventName, properties?}) → void +``` + +--- + +## App Logs (`base44.appLogs.*`) + +``` +logUserInApp(pageName) → Promise +``` + +--- + +## Users (`base44.users.*`) + +``` +inviteUser(userEmail, role) → Promise // role: 'user' | 'admin' +``` + +--- + +## Connectors (`base44.asServiceRole.connectors.*`) + +**Backend only, service role required.** + +``` +getAccessToken(integrationType) → Promise +``` + +**Types:** `'googlecalendar'`, `'googledrive'`, `'slack'`, `'notion'`, `'salesforce'`, `'hubspot'`, `'linkedin'`, `'tiktok'`, `'github'` + +--- + +## Service Role Access + +**Backend functions only.** Prefix any module with `asServiceRole` for admin access: + +```javascript +base44.asServiceRole.entities.Task.list() +base44.asServiceRole.functions.invoke('name', data) +base44.asServiceRole.connectors.getAccessToken('slack') +``` + +--- + +## Backend Function Template + +```javascript +import { createClientFromRequest } from "@base44/sdk"; + +Deno.serve(async (req) => { + const base44 = createClientFromRequest(req); + const data = await req.json(); + + // User context + const user = await base44.auth.me(); + + // Service role for admin operations + const allRecords = await base44.asServiceRole.entities.Task.list(); + + return Response.json({ success: true }); +}); +``` + +--- + +## Client Initialization (External Apps) + +```javascript +import { createClient } from "@base44/sdk"; + +const base44 = createClient({ appId: "your-app-id" }); // MUST use 'appId' +``` diff --git a/evals/fixtures/cli-entities/skills/base44-sdk/references/analytics.md b/evals/fixtures/cli-entities/skills/base44-sdk/references/analytics.md new file mode 100644 index 0000000..d1b2c69 --- /dev/null +++ b/evals/fixtures/cli-entities/skills/base44-sdk/references/analytics.md @@ -0,0 +1,113 @@ +# Analytics Module + +Track custom events and user activity via `base44.analytics`. + +## Contents +- [Methods](#methods) +- [Examples](#examples) +- [Automatic Tracking](#automatic-tracking) +- [Best Practices](#best-practices) + +## Methods + +| Method | Signature | Description | +|--------|-----------|-------------| +| `track(params)` | `void` | Track a custom event | + +## Examples + +### Track Custom Event + +```javascript +// Track a simple event +base44.analytics.track({ + eventName: "button_clicked" +}); + +// Track event with properties +base44.analytics.track({ + eventName: "purchase_completed", + properties: { + product_id: "prod-123", + amount: 99.99, + currency: "USD" + } +}); +``` + +### Track User Actions + +```javascript +// Track page view +base44.analytics.track({ + eventName: "page_view", + properties: { + page: "/dashboard", + referrer: document.referrer + } +}); + +// Track feature usage +base44.analytics.track({ + eventName: "feature_used", + properties: { + feature: "export_data", + format: "csv" + } +}); +``` + +## Automatic Tracking + +The analytics module automatically tracks: +- **Initialization events**: When the app loads +- **Heartbeat events**: Periodic activity signals +- **Session duration**: Time spent in the app + +These internal events help measure user engagement without manual instrumentation. + +## Best Practices + +1. **Use descriptive event names**: `order_completed` instead of `click` +2. **Include relevant properties**: Add context that helps analyze the event +3. **Be consistent**: Use the same event names and property keys across your app +4. **Don't track sensitive data**: Avoid PII in event properties + +```javascript +// Good: Descriptive with relevant properties +base44.analytics.track({ + eventName: "subscription_started", + properties: { + plan: "pro", + billing_cycle: "annual" + } +}); + +// Avoid: Vague event name, no context +base44.analytics.track({ + eventName: "click" +}); +``` + +## Type Definitions + +```typescript +/** Properties that can be attached to a tracked event. */ +type TrackEventProperties = { + [key: string]: string | number | boolean | null | undefined; +}; + +/** Parameters for the track() method. */ +interface TrackEventParams { + /** The name of the event to track. */ + eventName: string; + /** Optional properties to attach to the event. */ + properties?: TrackEventProperties; +} + +/** The analytics module interface. */ +interface AnalyticsModule { + /** Track a custom event with optional properties. */ + track(params: TrackEventParams): void; +} +``` diff --git a/evals/fixtures/cli-entities/skills/base44-sdk/references/app-logs.md b/evals/fixtures/cli-entities/skills/base44-sdk/references/app-logs.md new file mode 100644 index 0000000..0439805 --- /dev/null +++ b/evals/fixtures/cli-entities/skills/base44-sdk/references/app-logs.md @@ -0,0 +1,78 @@ +# App Logs Module + +Log user activity in your app via `base44.appLogs`. + +## Contents +- [Methods](#methods) +- [Examples](#examples) +- [Use Cases](#use-cases) + +## Methods + +| Method | Signature | Description | +|--------|-----------|-------------| +| `logUserInApp(pageName)` | `Promise` | Log user activity on a page | + +## Examples + +### Log User Activity + +```javascript +// Log when user visits a page +await base44.appLogs.logUserInApp("dashboard"); + +// Log specific page visits +await base44.appLogs.logUserInApp("settings"); +await base44.appLogs.logUserInApp("profile"); + +// Log feature usage +await base44.appLogs.logUserInApp("export-button-click"); +``` + +The page name doesn't have to be an actual page - it can be any string you want to track. + +## Use Cases + +### Track Page Views in React + +```javascript +// Log page views on route change +useEffect(() => { + base44.appLogs.logUserInApp(window.location.pathname); +}, [location.pathname]); +``` + +### Track Feature Usage + +```javascript +// Log when user uses specific features +function handleExport() { + base44.appLogs.logUserInApp("export-data"); + // ... export logic +} + +function handleSettingsChange() { + base44.appLogs.logUserInApp("settings-updated"); + // ... save settings +} +``` + +## Notes + +- Logs appear in the Analytics page of your app dashboard +- App logs track page-level and feature-level activity +- Use `analytics.track()` for custom events with properties, `appLogs.logUserInApp()` for simple page/feature tracking + +## Type Definitions + +```typescript +/** App Logs module for tracking and analyzing app usage. */ +interface AppLogsModule { + /** + * Log user activity in the app. + * @param pageName - Name of the page or section being visited. + * @returns Promise that resolves when the log is recorded. + */ + logUserInApp(pageName: string): Promise; +} +``` diff --git a/evals/fixtures/cli-entities/skills/base44-sdk/references/auth.md b/evals/fixtures/cli-entities/skills/base44-sdk/references/auth.md new file mode 100644 index 0000000..5195b3a --- /dev/null +++ b/evals/fixtures/cli-entities/skills/base44-sdk/references/auth.md @@ -0,0 +1,761 @@ +# Auth Module + +User authentication, registration, and session management via `base44.auth`. + +## Contents +- [TypeScript Types](#typescript-types) +- [Methods](#methods) +- [Examples](#examples) +- [Error Handling](#error-handling) +- [Auth Providers](#auth-providers) +- [Environment Availability](#environment-availability) +- [App Visibility](#app-visibility) +- [Limitations](#limitations) + +--- + +## TypeScript Types + +### User Interface +```typescript +interface User { + id: string; + created_date: string; + updated_date: string; + email: string; + full_name: string | null; + disabled: boolean | null; + is_verified: boolean; + app_id: string; + is_service: boolean; + role: string; + [key: string]: any; // Custom schema fields +} +``` + +### LoginResponse Interface +```typescript +interface LoginResponse { + access_token: string; // JWT token + user: User; // Complete user object +} +``` + +### Parameter Interfaces + +#### RegisterParams +```typescript +interface RegisterParams { + email: string; // Required + password: string; // Required + turnstile_token?: string | null; // Optional: Cloudflare Turnstile for bot protection + referral_code?: string | null; // Optional: Referral code +} +``` + +#### VerifyOtpParams +```typescript +interface VerifyOtpParams { + email: string; // User's email + otpCode: string; // OTP code from email +} +``` + +#### ResetPasswordParams +```typescript +interface ResetPasswordParams { + resetToken: string; // Token from password reset email + newPassword: string; // New password to set +} +``` + +#### ChangePasswordParams +```typescript +interface ChangePasswordParams { + userId: string; // User ID + currentPassword: string; // Current password for verification + newPassword: string; // New password to set +} +``` + +### Provider Type +```typescript +type Provider = 'google' | 'microsoft' | 'facebook'; +``` + +--- + +## Methods + +### Module Interface +```typescript +interface AuthModule { + // User Info + me(): Promise; + updateMe(data: Partial>): Promise; + isAuthenticated(): Promise; + + // Login/Logout + loginViaEmailPassword(email: string, password: string, turnstileToken?: string): Promise; + loginWithProvider(provider: Provider, fromUrl?: string): void; + logout(redirectUrl?: string): void; + redirectToLogin(nextUrl: string): void; + + // Token Management + setToken(token: string, saveToStorage?: boolean): void; + + // Registration + register(params: RegisterParams): Promise; + verifyOtp(params: VerifyOtpParams): Promise; + resendOtp(email: string): Promise; + + // User Management + inviteUser(userEmail: string, role: string): Promise; + + // Password Management + resetPasswordRequest(email: string): Promise; + resetPassword(params: ResetPasswordParams): Promise; + changePassword(params: ChangePasswordParams): Promise; +} +``` + +### Method Reference Table + +| Method | Parameters | Return Type | Description | +|--------|-----------|-------------|-------------| +| `register()` | `params: RegisterParams` | `Promise` | Create new user account | +| `loginViaEmailPassword()` | `email: string, password: string, turnstileToken?: string` | `Promise` | Authenticate with email/password | +| `loginWithProvider()` | `provider: Provider, fromUrl?: string` | `void` | Initiate OAuth login flow | +| `me()` | None | `Promise` | Get current authenticated user | +| `updateMe()` | `data: Partial` | `Promise` | Update current user's profile | +| `logout()` | `redirectUrl?: string` | `void` | Clear session, optionally redirect | +| `redirectToLogin()` | `nextUrl: string` | `void` | ⚠️ **Avoid** - Prefer custom login UI with `loginViaEmailPassword()` or `loginWithProvider()` | +| `isAuthenticated()` | None | `Promise` | Check if user is logged in | +| `setToken()` | `token: string, saveToStorage?: boolean` | `void` | Manually set auth token | +| `inviteUser()` | `userEmail: string, role: string` | `Promise` | Send invitation email | +| `verifyOtp()` | `params: VerifyOtpParams` | `Promise` | Verify OTP code | +| `resendOtp()` | `email: string` | `Promise` | Resend OTP code | +| `resetPasswordRequest()` | `email: string` | `Promise` | Request password reset | +| `resetPassword()` | `params: ResetPasswordParams` | `Promise` | Reset password with token | +| `changePassword()` | `params: ChangePasswordParams` | `Promise` | Change user password | + +--- + +## Examples + +### Register New User (Complete Flow) + +Registration requires email verification before login. Complete flow: + +1. **Register** - Create the user account +2. **Verification email sent** - User receives an OTP code +3. **Verify OTP** - User enters code to verify email +4. **Login** - User can now log in + +```javascript +try { + // Step 1: Register the user + await base44.auth.register({ + email: "user@example.com", + password: "securePassword123", + referral_code: "OPTIONAL_CODE", // optional + turnstile_token: "CAPTCHA_TOKEN" // optional, for bot protection + }); + console.log('Registration successful. Check email for OTP code.'); + + // Step 2: User receives email with OTP code (e.g., "123456") + + // Step 3: Verify the OTP code + await base44.auth.verifyOtp({ + email: "user@example.com", + otpCode: "123456" // code from verification email + }); + console.log('Email verified successfully.'); + + // Step 4: Now the user can log in + const loginResponse = await base44.auth.loginViaEmailPassword( + "user@example.com", + "securePassword123" + ); + console.log('Login successful:', loginResponse.user); + +} catch (error) { + console.error('Registration flow failed:', error.message); + // Handle specific errors (see Error Handling section) +} +``` + +> **Important**: Users cannot log in until they complete OTP verification. Attempting to call `loginViaEmailPassword` before verification will fail. + +### Login with Email/Password + +```javascript +try { + const response = await base44.auth.loginViaEmailPassword( + "user@example.com", + "password123", + turnstileToken // optional: for bot protection + ); + + console.log('Login successful'); + console.log('User:', response.user); + console.log('Token:', response.access_token); + + // JWT is automatically stored for subsequent requests + +} catch (error) { + console.error('Login failed:', error.message); + if (error.status === 401) { + console.error('Invalid credentials'); + } else if (error.status === 403) { + console.error('Email not verified. Please check your email for OTP.'); + } +} +``` + +### Login with OAuth Provider + +```javascript +// Redirect to Google OAuth +base44.auth.loginWithProvider('google'); + +// Redirect to Google OAuth and return to current page after +base44.auth.loginWithProvider('google', window.location.href); + +// Supported providers: 'google', 'microsoft', 'facebook' +base44.auth.loginWithProvider('microsoft'); +``` + +### Get Current User + +```javascript +try { + const user = await base44.auth.me(); + + if (user) { + console.log('User ID:', user.id); + console.log('Email:', user.email); + console.log('Name:', user.full_name); + console.log('Role:', user.role); + console.log('Verified:', user.is_verified); + } else { + console.log('User not authenticated'); + } + +} catch (error) { + console.error('Failed to fetch user:', error.message); + if (error.status === 401) { + // Token expired or invalid - navigate to your custom login page + navigate('/login'); + } +} +``` + +### Update User Profile + +```javascript +try { + const updatedUser = await base44.auth.updateMe({ + full_name: "John Doe", + // Custom schema fields can be updated here + phone: "+1234567890", + preferences: { theme: "dark" } + }); + + console.log('Profile updated:', updatedUser); + +} catch (error) { + console.error('Profile update failed:', error.message); + if (error.status === 400) { + console.error('Invalid data provided'); + } else if (error.status === 401) { + console.error('Authentication required'); + navigate('/login'); + } +} +``` + +### Check Authentication Status + +```javascript +try { + const isLoggedIn = await base44.auth.isAuthenticated(); + + if (isLoggedIn) { + // Show authenticated UI + console.log('User is authenticated'); + } else { + // Show login button + console.log('User is not authenticated'); + } + +} catch (error) { + console.error('Failed to check authentication:', error.message); + // On error, treat as not authenticated +} +``` + +### Logout + +```javascript +// Simple logout +base44.auth.logout(); + +// Logout and redirect to goodbye page +base44.auth.logout("/goodbye"); + +// Logout and redirect to homepage +base44.auth.logout("/"); +``` + +### Protected Route Pattern + +```javascript +// Using a navigation function (e.g., React Router's useNavigate, Next.js router) +async function requireAuth(navigate) { + try { + const user = await base44.auth.me(); + + if (!user) { + // Navigate to your custom login page + navigate('/login', { state: { returnTo: window.location.pathname } }); + return null; + } + + return user; + + } catch (error) { + console.error('Authentication check failed:', error.message); + navigate('/login', { state: { returnTo: window.location.pathname } }); + return null; + } +} + +// Usage in your app +async function loadProtectedPage(navigate) { + const user = await requireAuth(navigate); + if (!user) { + // Will navigate to login + return; + } + + // Continue with authenticated logic + console.log('Welcome,', user.full_name); +} +``` + +### Set Authentication Token + +```javascript +// SECURITY WARNING: Never hardcode tokens or expose them in client code +// Tokens should only be received from secure authentication flows + +// Set token and save to localStorage (default) +base44.auth.setToken(receivedToken); + +// Set token without saving to localStorage (temporary session) +base44.auth.setToken(receivedToken, false); + +// Verify token was set +try { + const isAuthenticated = await base44.auth.isAuthenticated(); + if (!isAuthenticated) { + console.error('Token validation failed'); + } +} catch (error) { + console.error('Failed to set token:', error.message); +} +``` + +### Invite User to Application + +```javascript +try { + // Note: Typically requires admin privileges + const response = await base44.auth.inviteUser( + "newuser@example.com", + "user" // or "admin" + ); + + console.log('Invitation sent successfully'); + +} catch (error) { + console.error('Failed to invite user:', error.message); + if (error.status === 403) { + console.error('Insufficient permissions to invite users'); + } else if (error.status === 400) { + console.error('Invalid email or role'); + } +} +``` + +### OTP Verification + +```javascript +try { + // Verify OTP code sent to user's email + await base44.auth.verifyOtp({ + email: "user@example.com", + otpCode: "123456" + }); + + console.log('OTP verified successfully'); + +} catch (error) { + console.error('OTP verification failed:', error.message); + if (error.status === 400) { + console.error('Invalid or expired OTP code'); + } else if (error.status === 429) { + console.error('Too many attempts. Please try again later.'); + } +} + +// Resend OTP if needed +try { + await base44.auth.resendOtp("user@example.com"); + console.log('OTP resent successfully'); + +} catch (error) { + console.error('Failed to resend OTP:', error.message); + if (error.status === 429) { + console.error('Too many requests. Please wait before trying again.'); + } +} +``` + +### Password Reset Flow + +```javascript +// Step 1: Request password reset +try { + await base44.auth.resetPasswordRequest("user@example.com"); + console.log('Password reset email sent. Check your inbox.'); + +} catch (error) { + console.error('Password reset request failed:', error.message); + if (error.status === 429) { + console.error('Too many requests. Please try again later.'); + } + // Note: For security, don't reveal if email exists +} + +// Step 2: Reset password with token from email +try { + await base44.auth.resetPassword({ + resetToken: "token-from-email", + newPassword: "newSecurePassword123" + }); + + console.log('Password reset successfully. You can now log in.'); + +} catch (error) { + console.error('Password reset failed:', error.message); + if (error.status === 400) { + console.error('Invalid or expired reset token'); + } else if (error.status === 422) { + console.error('Password does not meet requirements'); + } +} +``` + +### Change Password + +```javascript +try { + const currentUser = await base44.auth.me(); + + if (!currentUser) { + throw new Error('User must be authenticated to change password'); + } + + await base44.auth.changePassword({ + userId: currentUser.id, + currentPassword: "oldPassword123", + newPassword: "newSecurePassword456" + }); + + console.log('Password changed successfully'); + +} catch (error) { + console.error('Password change failed:', error.message); + if (error.status === 401) { + console.error('Current password is incorrect'); + } else if (error.status === 422) { + console.error('New password does not meet requirements'); + } else if (error.status === 403) { + console.error('Not authorized to change this password'); + } +} +``` + +--- + +## Error Handling + +### Common Error Scenarios + +The auth module can throw various errors. Here are common scenarios and how to handle them: + +#### Authentication Errors (401/403) +```javascript +try { + const user = await base44.auth.me(); +} catch (error) { + if (error.status === 401) { + // Token expired or invalid - navigate to your custom login page + navigate('/login'); + } else if (error.status === 403) { + // Email not verified or insufficient permissions + console.error('Access denied:', error.message); + } +} +``` + +#### Validation Errors (400/422) +```javascript +try { + await base44.auth.register({ + email: "invalid-email", + password: "weak" + }); +} catch (error) { + if (error.status === 400) { + console.error('Invalid input:', error.message); + // Handle validation errors + } else if (error.status === 422) { + console.error('Data validation failed:', error.details); + } +} +``` + +#### Rate Limiting (429) +```javascript +try { + await base44.auth.resendOtp("user@example.com"); +} catch (error) { + if (error.status === 429) { + console.error('Too many requests. Please wait before trying again.'); + // Show countdown or disable button temporarily + } +} +``` + +#### Generic Error Handler +```javascript +function handleAuthError(error) { + switch (error.status) { + case 400: + return 'Invalid input. Please check your data.'; + case 401: + return 'Authentication required. Redirecting to login...'; + case 403: + return 'Access denied. You may need to verify your email.'; + case 404: + return 'Resource not found.'; + case 422: + return 'Validation failed. Please check your input.'; + case 429: + return 'Too many requests. Please try again later.'; + case 500: + return 'Server error. Please try again.'; + default: + return 'An unexpected error occurred.'; + } +} + +// Usage +try { + await base44.auth.loginViaEmailPassword(email, password); +} catch (error) { + console.error(handleAuthError(error)); +} +``` + +--- + +## Auth Providers + +Configure authentication providers in your app dashboard: + +### Available Providers + +**Built-in (All Plans):** +- **Email/Password** - Default, always enabled +- **Google** - OAuth authentication +- **Microsoft** - OAuth authentication +- **Facebook** - OAuth authentication + +**SSO Providers (Elite Plan):** +- **Okta** +- **Azure AD** +- **GitHub** + +### Using OAuth Providers + +```javascript +// Initiate OAuth login flow +base44.auth.loginWithProvider('google'); + +// Return to specific page after authentication +base44.auth.loginWithProvider('microsoft', '/dashboard'); + +// Supported values: 'google', 'microsoft', 'facebook' +``` + +--- + +## Environment Availability + +| Environment | Availability | Notes | +|------------|--------------|-------| +| **Frontend** | ✅ Yes | All methods available | +| **Backend Functions** | ✅ Yes | Use `createClientFromRequest(req)` for authenticated client | +| **Service Role** | ❌ No | Auth methods not available in service role context | + +### Frontend Usage +```javascript +// Standard browser usage +const user = await base44.auth.me(); +``` + +### Backend Functions Usage +```javascript +import { createClientFromRequest } from "@base44/sdk"; + +Deno.serve(async (req) => { + const base44 = createClientFromRequest(req); + + // Get user from request context + const user = await base44.auth.me(); + + // User operations here... + return Response.json({ user }); +}); +``` + +--- + +## App Visibility + +Control who can access your app in the app settings: + +### Public Apps +- No login required for basic access +- Users can view public content without authentication +- Authenticated users get additional features/data + +### Private Apps +- Login required to access any content +- Unauthenticated users are automatically redirected to login +- All content is protected by default + +--- + +## Limitations + +### Authentication UI Options +- **Recommended:** Build custom login/signup UI using `loginViaEmailPassword()` and `loginWithProvider()` for full control over user experience and branding +- **Alternative:** `redirectToLogin()` uses Base44's hosted authentication pages with limited customization + +### Hosted Login (via redirectToLogin) +- `redirectToLogin()` shows both login and signup options on the same page +- No separate `redirectToSignup()` method +- Users can switch between login/signup on the hosted page +- ⚠️ **Note:** Prefer building custom login UI for better user experience + +### Password Requirements +- Minimum length and complexity requirements enforced +- Requirements not exposed via API +- Validation errors returned when requirements not met + +### Rate Limiting +- OTP requests are rate-limited to prevent abuse +- Password reset requests are rate-limited +- Login attempts may be rate-limited with Turnstile protection + +### Token Management +- JWTs are automatically stored in localStorage by default +- Token expiration and refresh not exposed in API +- Call `me()` or `isAuthenticated()` to verify token validity + +--- + +## Best Practices + +### 1. Always Handle Errors +```javascript +try { + await base44.auth.loginViaEmailPassword(email, password); +} catch (error) { + // Always handle authentication errors + console.error('Login failed:', error.message); +} +``` + +### 2. Verify Authentication Before Protected Actions +```javascript +const user = await requireAuth(); +if (!user) return; // Will redirect to login +// Proceed with authenticated action +``` + +### 3. Use Type Safety with TypeScript +```typescript +import type { User, LoginResponse } from '@base44/sdk'; + +const user: User = await base44.auth.me(); +const response: LoginResponse = await base44.auth.loginViaEmailPassword(email, password); +``` + +### 4. Don't Hardcode Credentials +```javascript +// ❌ BAD +base44.auth.setToken("hardcoded-token-here"); + +// ✅ GOOD +const token = await secureAuthFlow(); +base44.auth.setToken(token); +``` + +### 5. Provide User Feedback +```javascript +try { + await base44.auth.register(params); + showSuccessMessage('Registration successful! Check your email for verification code.'); +} catch (error) { + showErrorMessage('Registration failed: ' + error.message); +} +``` + +### 6. Handle Token Expiration Gracefully +```javascript +try { + const user = await base44.auth.me(); +} catch (error) { + if (error.status === 401) { + // Clear local state and navigate to login + localStorage.clear(); + navigate('/login'); + } +} +``` + +### 7. Build Custom Login UI (Recommended) +```javascript +// ✅ RECOMMENDED - Custom login form with direct methods +const handleLogin = async (email, password) => { + try { + const { user } = await base44.auth.loginViaEmailPassword(email, password); + navigate('/dashboard'); + } catch (error) { + setError(error.message); + } +}; + +const handleGoogleLogin = () => { + base44.auth.loginWithProvider('google', '/dashboard'); +}; + +// ❌ AVOID - Redirecting to hosted login page +// base44.auth.redirectToLogin(window.location.href); +``` diff --git a/evals/fixtures/cli-entities/skills/base44-sdk/references/base44-agents.md b/evals/fixtures/cli-entities/skills/base44-sdk/references/base44-agents.md new file mode 100644 index 0000000..ee55dee --- /dev/null +++ b/evals/fixtures/cli-entities/skills/base44-sdk/references/base44-agents.md @@ -0,0 +1,364 @@ +# Agents Module + +AI agent conversations and messages via `base44.agents`. + +> **Note:** This module requires a logged-in user. All agent methods work in the context of the authenticated user. + +## Contents +- [Concepts](#concepts) +- [Methods](#methods) +- [Examples](#examples) (Create, Get Conversations, List, Subscribe, Send Message, WhatsApp) +- [Message Structure](#message-structure) +- [Conversation Structure](#conversation-structure) +- [Common Patterns](#common-patterns) + +## Concepts + +- **Conversation**: A dialogue between user and an AI agent. Has unique ID, agent name, user reference, and metadata. +- **Message**: Single message in a conversation. Has role (`user`, `assistant`, `system`), content, timestamps, and optional metadata. + +## Methods + +| Method | Signature | Description | +|--------|-----------|-------------| +| `createConversation(params)` | `Promise` | Create a new conversation with an agent | +| `getConversations()` | `Promise` | Get all user's conversations | +| `getConversation(id)` | `Promise` | Get conversation with messages | +| `listConversations(filterParams)` | `Promise` | Filter/sort/paginate conversations | +| `subscribeToConversation(id, onUpdate?)` | `() => void` | Real-time updates via WebSocket (returns unsubscribe function) | +| `addMessage(conversation, message)` | `Promise` | Send a message | +| `getWhatsAppConnectURL(agentName)` | `string` | Get WhatsApp connection URL for agent | + +## Examples + +### Create Conversation + +```javascript +const conversation = await base44.agents.createConversation({ + agent_name: "support-agent", + metadata: { + order_id: "ORD-123", + category: "billing" + } +}); + +console.log(conversation.id); +``` + +### Get All Conversations + +```javascript +const conversations = await base44.agents.getConversations(); + +conversations.forEach(conv => { + console.log(conv.id, conv.agent_name, conv.created_date); +}); +``` + +### Get Single Conversation (with messages) + +```javascript +const conversation = await base44.agents.getConversation("conv-id-123"); + +console.log(conversation.messages); +``` + +### List with Filters + +```javascript +// Using filterParams object with q, sort, limit, skip, fields +const recent = await base44.agents.listConversations({ + q: { agent_name: "support-agent" }, + sort: "-created_date", + limit: 10, + skip: 0 +}); + +// Filter by metadata +const highPriority = await base44.agents.listConversations({ + q: { + agent_name: "support-agent", + "metadata.priority": "high" + }, + sort: "-updated_date", + limit: 20 +}); +``` + +### Subscribe to Updates (Real-time) + +```javascript +const unsubscribe = base44.agents.subscribeToConversation( + "conv-id-123", + (updatedConversation) => { + // Called when new messages arrive + console.log("New messages:", updatedConversation.messages); + } +); + +// Later: unsubscribe +unsubscribe(); +``` + +### Send a Message + +```javascript +const conversation = await base44.agents.getConversation("conv-id-123"); + +await base44.agents.addMessage(conversation, { + role: "user", + content: "What's the weather like today?" +}); +``` + +### Get WhatsApp Connection URL + +```javascript +const whatsappUrl = base44.agents.getWhatsAppConnectURL("support-agent"); +// Returns URL for users to connect with agent via WhatsApp +console.log(whatsappUrl); +``` + +## Message Structure + +```javascript +{ + role: "user" | "assistant" | "system", + content: "Message text or structured object", + created_date: "2024-01-15T10:30:00Z", + updated_date: "2024-01-15T10:30:00Z", + + // Optional fields + reasoning: { + content: "Agent's reasoning process", + timing: 1500 + }, + tool_calls: [{ + name: "search", + arguments: { query: "weather" }, + result: { ... }, + status: "success" + }], + file_urls: ["https://..."], + usage: { + prompt_tokens: 150, + completion_tokens: 50 + }, + metadata: { ... }, + custom_context: { ... } +} +``` + +## Conversation Structure + +```javascript +{ + id: "conv-id-123", + app_id: "app-id", + agent_name: "support-agent", + created_by_id: "user-id", + created_date: "2024-01-15T10:00:00Z", + updated_date: "2024-01-15T10:30:00Z", + messages: [ ... ], + metadata: { ... } +} +``` + +## Common Patterns + +### Chat Interface + +```javascript +// Load conversation +const conv = await base44.agents.getConversation(conversationId); +setMessages(conv.messages); + +// Subscribe to updates +const unsubscribe = base44.agents.subscribeToConversation(conversationId, (updated) => { + setMessages(updated.messages); +}); + +// Send message +async function sendMessage(text) { + await base44.agents.addMessage(conv, { role: "user", content: text }); +} + +// Cleanup on unmount +return () => unsubscribe(); +``` + +## Type Definitions + +### AgentConversation + +```typescript +/** An agent conversation containing messages exchanged with an AI agent. */ +interface AgentConversation { + /** Unique identifier for the conversation. */ + id: string; + /** Application ID. */ + app_id: string; + /** Name of the agent in this conversation. */ + agent_name: string; + /** ID of the user who created the conversation. */ + created_by_id: string; + /** When the conversation was created. */ + created_date: string; + /** When the conversation was last updated. */ + updated_date: string; + /** Array of messages in the conversation. */ + messages: AgentMessage[]; + /** Optional metadata associated with the conversation. */ + metadata?: Record; +} +``` + +### AgentMessage + +```typescript +/** A message in an agent conversation. */ +interface AgentMessage { + /** Unique identifier for the message. */ + id: string; + /** Role of the message sender. */ + role: "user" | "assistant" | "system"; + /** When the message was created. */ + created_date: string; + /** When the message was last updated. */ + updated_date: string; + /** Message content. */ + content?: string | Record; + /** Optional reasoning information for the message. */ + reasoning?: AgentMessageReasoning | null; + /** URLs to files attached to the message. */ + file_urls?: string[]; + /** Tool calls made by the agent. */ + tool_calls?: AgentMessageToolCall[]; + /** Token usage statistics. */ + usage?: AgentMessageUsage; + /** Whether the message is hidden from the user. */ + hidden?: boolean; + /** Custom context provided with the message. */ + custom_context?: AgentMessageCustomContext[]; + /** Model used to generate the message. */ + model?: string; + /** Checkpoint ID for the message. */ + checkpoint_id?: string; + /** Metadata about when and by whom the message was created. */ + metadata?: AgentMessageMetadata; +} +``` + +### Supporting Types + +```typescript +/** Reasoning information for an agent message. */ +interface AgentMessageReasoning { + /** When reasoning started. */ + start_date: string; + /** When reasoning ended. */ + end_date?: string; + /** Reasoning content. */ + content: string; +} + +/** A tool call made by the agent. */ +interface AgentMessageToolCall { + /** Tool call ID. */ + id: string; + /** Name of the tool called. */ + name: string; + /** Arguments passed to the tool as JSON string. */ + arguments_string: string; + /** Status of the tool call. */ + status: "running" | "success" | "error" | "stopped"; + /** Results from the tool call. */ + results?: string; +} + +/** Token usage statistics for an agent message. */ +interface AgentMessageUsage { + /** Number of tokens in the prompt. */ + prompt_tokens?: number; + /** Number of tokens in the completion. */ + completion_tokens?: number; +} + +/** Custom context provided with an agent message. */ +interface AgentMessageCustomContext { + /** Context message. */ + message: string; + /** Associated data for the context. */ + data: Record; + /** Type of context. */ + type: string; +} + +/** Metadata about when and by whom a message was created. */ +interface AgentMessageMetadata { + /** When the message was created. */ + created_date: string; + /** Email of the user who created the message. */ + created_by_email: string; + /** Full name of the user who created the message. */ + created_by_full_name: string; +} +``` + +### CreateConversationParams + +```typescript +/** Parameters for creating a new conversation. */ +interface CreateConversationParams { + /** The name of the agent to create a conversation with. */ + agent_name: string; + /** Optional metadata to attach to the conversation. */ + metadata?: Record; +} +``` + +### ModelFilterParams + +```typescript +/** Parameters for filtering, sorting, and paginating conversations. */ +interface ModelFilterParams { + /** Query object with field-value pairs for filtering. */ + q?: Record; + /** Sort parameter (e.g., "-created_date" for descending). */ + sort?: string | null; + /** Maximum number of results to return. */ + limit?: number | null; + /** Number of results to skip for pagination. */ + skip?: number | null; + /** Array of field names to include in the response. */ + fields?: string[] | null; +} +``` + +### AgentsModule + +```typescript +/** Agents module for managing AI agent conversations. */ +interface AgentsModule { + /** Gets all conversations from all agents in the app. */ + getConversations(): Promise; + + /** Gets a specific conversation by ID. */ + getConversation(conversationId: string): Promise; + + /** Lists conversations with filtering, sorting, and pagination. */ + listConversations(filterParams: ModelFilterParams): Promise; + + /** Creates a new conversation with an agent. */ + createConversation(conversation: CreateConversationParams): Promise; + + /** Adds a message to a conversation. */ + addMessage(conversation: AgentConversation, message: Partial): Promise; + + /** Subscribes to real-time updates for a conversation. Returns unsubscribe function. */ + subscribeToConversation(conversationId: string, onUpdate?: (conversation: AgentConversation) => void): () => void; + + /** Gets WhatsApp connection URL for an agent. */ + getWhatsAppConnectURL(agentName: string): string; +} +``` diff --git a/evals/fixtures/cli-entities/skills/base44-sdk/references/client.md b/evals/fixtures/cli-entities/skills/base44-sdk/references/client.md new file mode 100644 index 0000000..554bf2b --- /dev/null +++ b/evals/fixtures/cli-entities/skills/base44-sdk/references/client.md @@ -0,0 +1,257 @@ +# Client Setup + +How to create and configure the Base44 client. + +## Contents +- [In Base44-Generated Apps](#in-base44-generated-apps) +- [In External Apps](#in-external-apps) +- [In Backend Functions](#in-backend-functions) +- [Authentication Modes](#authentication-modes) (Anonymous, User, Service Role) +- [Available Modules](#available-modules) +- [Client Methods](#client-methods) +- [Client Configuration Options](#client-configuration-options) + +## In Base44-Generated Apps + +The client is pre-configured and available as `base44`. Just use it: + +```javascript +const tasks = await base44.entities.Task.list(); +``` + +## In External Apps + +Install the SDK and create a client: + +```bash +npm install @base44/sdk +``` + +```javascript +import { createClient } from "@base44/sdk"; + +// IMPORTANT: The parameter name is 'appId' (NOT 'clientId', NOT 'id') +// IMPORTANT: onError must be nested inside 'options' object +const base44 = createClient({ + appId: "your-app-id", // Required: Use 'appId' parameter + token: "optional-user-token", // Optional: for pre-authenticated requests + options: { // Optional: configuration options + onError: (error) => { // Optional: error handler (must be in options) + console.error("Base44 error:", error); + } + } +}); +``` + +**Common Mistakes:** +- ❌ `createClient({ clientId: "..." })` - WRONG parameter name +- ❌ `createClient({ id: "..." })` - WRONG parameter name +- ❌ `createClient({ appId: "...", onError: ... })` - WRONG: onError must be in options +- ✅ `createClient({ appId: "..." })` - CORRECT parameter name +- ✅ `createClient({ appId: "...", options: { onError: ... } })` - CORRECT: onError in options + +## In Backend Functions + +Use `createClientFromRequest` to get a client with the caller's auth context: + +```javascript +import { createClientFromRequest } from "@base44/sdk"; + +Deno.serve(async (req) => { + const base44 = createClientFromRequest(req); + + // Client inherits authentication from the request + const user = await base44.auth.me(); + + return Response.json({ user }); +}); +``` + +## Authentication Modes + +| Mode | How to Get | Permissions | +|------|-----------|-------------| +| **Anonymous** | `createClient({ appId })` without token | Public data only | +| **User** | After `loginViaEmailPassword()` or via `createClientFromRequest` | User's own data | +| **Service Role** | `base44.asServiceRole.*` in backend | Full admin access | + +## Anonymous Mode + +No authentication. Can only access public resources. + +```javascript +const base44 = createClient({ appId: "your-app-id" }); + +// Only works if Task entity allows anonymous read +const publicTasks = await base44.entities.Task.list(); +``` + +## User Mode + +After user logs in, the client automatically includes their token. + +```javascript +const base44 = createClient({ appId: "your-app-id" }); + +// Login sets the token +await base44.auth.loginViaEmailPassword("user@example.com", "password"); + +// Subsequent requests are authenticated +const user = await base44.auth.me(); +const myTasks = await base44.entities.Task.list(); // filtered by permissions +``` + +## Service Role Mode + +Admin-level access. **Backend only.** + +```javascript +// Inside a backend function +Deno.serve(async (req) => { + const base44 = createClientFromRequest(req); + + // User mode - respects permissions + const myTasks = await base44.entities.Task.list(); + + // Service role - bypasses permissions + const allTasks = await base44.asServiceRole.entities.Task.list(); + const allUsers = await base44.asServiceRole.entities.User.list(); + const oauthToken = await base44.asServiceRole.connectors.getAccessToken("slack"); + + return Response.json({ myTasks, allTasks }); +}); +``` + +## Available Modules + +The client exposes these modules: + +```javascript +base44.entities // CRUD operations +base44.auth // Authentication +base44.agents // AI conversations +base44.functions // Backend function invocation +base44.integrations // Third-party services +base44.analytics // Event tracking +base44.appLogs // App usage logging +base44.users // User invitations + +// Service role only (backend) +base44.asServiceRole.entities +base44.asServiceRole.agents +base44.asServiceRole.functions +base44.asServiceRole.integrations +base44.asServiceRole.appLogs +base44.asServiceRole.connectors +``` + +## Client Methods + +The client provides these methods: + +```javascript +// Set authentication token for all subsequent requests +base44.setToken(newToken); + +// Cleanup WebSocket connections (call when done with client) +base44.cleanup(); +``` + +### setToken + +Updates the authentication token for all subsequent API requests and WebSocket connections. + +```javascript +// After receiving a token (e.g., from external auth) +base44.setToken("new-jwt-token"); +``` + +### cleanup + +Disconnects WebSocket connections. Call when you're done with the client or when the component unmounts. + +```javascript +// Cleanup on component unmount (React example) +useEffect(() => { + return () => base44.cleanup(); +}, []); +``` + +## Client Configuration Options + +```javascript +createClient({ + appId: "your-app-id", // Required: MUST use 'appId' (not 'clientId' or 'id') + token: "jwt-token", // Optional: pre-set auth token + options: { // Optional: configuration options + onError: (error) => {} // Optional: global error handler (must be in options) + } +}); +``` + +**⚠️ Critical:** +- The parameter name is `appId`, not `clientId` or `id`. Using the wrong parameter name will cause errors. +- The `onError` handler must be nested inside the `options` object, not at the top level. + +## Type Definitions + +### CreateClientConfig + +```typescript +/** Configuration for creating a Base44 client. */ +interface CreateClientConfig { + /** The Base44 app ID (required). */ + appId: string; + /** User authentication token. Used to authenticate as a specific user. */ + token?: string; + /** Service role authentication token (backend only). */ + serviceToken?: string; + /** Additional client options. */ + options?: CreateClientOptions; +} + +/** Options for creating a Base44 client. */ +interface CreateClientOptions { + /** Optional error handler called whenever an API error occurs. */ + onError?: (error: Error) => void; +} +``` + +### Base44Client + +```typescript +/** The Base44 client instance. */ +interface Base44Client { + /** Entities module for CRUD operations on your data models. */ + entities: EntitiesModule; + /** Integrations module for calling pre-built integration endpoints. */ + integrations: IntegrationsModule; + /** Auth module for user authentication and management. */ + auth: AuthModule; + /** Functions module for invoking custom backend functions. */ + functions: FunctionsModule; + /** Agents module for managing AI agent conversations. */ + agents: AgentsModule; + /** App logs module for tracking app usage. */ + appLogs: AppLogsModule; + /** Analytics module for tracking custom events. */ + analytics: AnalyticsModule; + + /** Cleanup function to disconnect WebSocket connections. */ + cleanup(): void; + + /** Sets a new authentication token for all subsequent requests. */ + setToken(newToken: string): void; + + /** Provides access to modules with elevated service role permissions (backend only). */ + readonly asServiceRole: { + entities: EntitiesModule; + integrations: IntegrationsModule; + connectors: ConnectorsModule; + functions: FunctionsModule; + agents: AgentsModule; + appLogs: AppLogsModule; + cleanup(): void; + }; +} +``` diff --git a/evals/fixtures/cli-entities/skills/base44-sdk/references/connectors.md b/evals/fixtures/cli-entities/skills/base44-sdk/references/connectors.md new file mode 100644 index 0000000..f3fbcb4 --- /dev/null +++ b/evals/fixtures/cli-entities/skills/base44-sdk/references/connectors.md @@ -0,0 +1,113 @@ +# Connectors Module + +OAuth token management for external services. **Service role only** (backend functions). + +## Method + +```javascript +base44.asServiceRole.connectors.getAccessToken(integrationType): Promise +``` + +Returns a raw OAuth access token for the specified service. + +## Supported Services + +| Integration Type | Service | +|-----------------|---------| +| `"googlecalendar"` | Google Calendar | +| `"googledrive"` | Google Drive | +| `"slack"` | Slack | +| `"notion"` | Notion | +| `"salesforce"` | Salesforce | +| `"hubspot"` | HubSpot | +| `"linkedin"` | LinkedIn | +| `"tiktok"` | TikTok | +| `"github"` | GitHub | + +## Example Usage + +```javascript +// Backend function only +Deno.serve(async (req) => { + const base44 = createClientFromRequest(req); + + // Get OAuth token for Slack + const slackToken = await base44.asServiceRole.connectors.getAccessToken("slack"); + + // Use token directly with Slack API + const response = await fetch("https://slack.com/api/chat.postMessage", { + method: "POST", + headers: { + "Authorization": `Bearer ${slackToken}`, + "Content-Type": "application/json" + }, + body: JSON.stringify({ + channel: "#general", + text: "Hello from Base44!" + }) + }); + + return Response.json(await response.json()); +}); +``` + +## Google Calendar Example + +```javascript +Deno.serve(async (req) => { + const base44 = createClientFromRequest(req); + + const token = await base44.asServiceRole.connectors.getAccessToken("googlecalendar"); + + // List upcoming events + const response = await fetch( + "https://www.googleapis.com/calendar/v3/calendars/primary/events?" + + new URLSearchParams({ + maxResults: "10", + orderBy: "startTime", + singleEvents: "true", + timeMin: new Date().toISOString() + }), + { + headers: { "Authorization": `Bearer ${token}` } + } + ); + + const events = await response.json(); + return Response.json(events); +}); +``` + +## Setup Requirements + +1. **Builder plan** or higher +2. **Backend functions** enabled +3. **Connector configured** in Base44 dashboard (OAuth flow completed) + +## Important Notes + +- **One account per connector per app**: All users share the same connected account +- **Backend only**: `connectors` module not available in frontend code +- **Service role required**: Must use `base44.asServiceRole.connectors` +- **You handle the API calls**: Base44 only provides the token; you make the actual API requests +- **Token refresh**: Base44 handles token refresh automatically + +## Type Definitions + +```typescript +/** + * The type of external integration/connector. + * Examples: 'googlecalendar', 'slack', 'github', 'notion', etc. + */ +type ConnectorIntegrationType = string; + +/** Connectors module for managing OAuth tokens for external services. */ +interface ConnectorsModule { + /** + * Retrieves an OAuth access token for a specific external integration type. + * @param integrationType - The type of integration (e.g., 'googlecalendar', 'slack'). + * @returns Promise resolving to the access token string. + */ + getAccessToken(integrationType: ConnectorIntegrationType): Promise; +} +``` diff --git a/evals/fixtures/cli-entities/skills/base44-sdk/references/entities.md b/evals/fixtures/cli-entities/skills/base44-sdk/references/entities.md new file mode 100644 index 0000000..a7bdf35 --- /dev/null +++ b/evals/fixtures/cli-entities/skills/base44-sdk/references/entities.md @@ -0,0 +1,253 @@ +# Entities Module + +CRUD operations on data models. Access via `base44.entities.EntityName.method()`. + +## Contents +- [Methods](#methods) +- [Examples](#examples) (Create, Bulk Create, List, Filter, Get, Update, Delete, Subscribe) +- [User Entity](#user-entity) +- [Service Role Access](#service-role-access) +- [Permissions](#permissions) + +## Methods + +| Method | Signature | Description | +|--------|-----------|-------------| +| `create(data)` | `Promise` | Create one record | +| `bulkCreate(dataArray)` | `Promise` | Create multiple records | +| `list(sort?, limit?, skip?, fields?)` | `Promise` | Get all records (paginated) | +| `filter(query, sort?, limit?, skip?, fields?)` | `Promise` | Get records matching conditions | +| `get(id)` | `Promise` | Get single record by ID | +| `update(id, data)` | `Promise` | Update record (partial update) | +| `delete(id)` | `Promise` | Delete record by ID | +| `deleteMany(query)` | `Promise` | Delete all matching records | +| `importEntities(file)` | `Promise` | Import from CSV (frontend only) | +| `subscribe(callback)` | `() => void` | Subscribe to realtime updates (returns unsubscribe function) | + +## Examples + +### Create + +```javascript +const task = await base44.entities.Task.create({ + title: "Complete documentation", + status: "pending", + dueDate: "2024-12-31" +}); +``` + +### Bulk Create + +```javascript +const tasks = await base44.entities.Task.bulkCreate([ + { title: "Task 1", status: "pending" }, + { title: "Task 2", status: "pending" } +]); +``` + +### List with Pagination + +```javascript +// Get first 10 records, sorted by created_date descending +const tasks = await base44.entities.Task.list( + "-created_date", // sort (string: prefix with - for descending) + 10, // limit + 0 // skip +); + +// Get next page +const page2 = await base44.entities.Task.list("-created_date", 10, 10); +``` + +### Filter + +```javascript +// Simple filter +const pending = await base44.entities.Task.filter({ status: "pending" }); + +// Multiple conditions +const myPending = await base44.entities.Task.filter({ + status: "pending", + assignedTo: userId +}); + +// With sort, limit, skip +const recent = await base44.entities.Task.filter( + { status: "pending" }, + "-created_date", // sort (string: prefix with - for descending) + 5, + 0 +); + +// Select specific fields +const titles = await base44.entities.Task.filter( + { status: "pending" }, + null, + null, + null, + ["id", "title"] +); +``` + +### Get by ID + +```javascript +const task = await base44.entities.Task.get("task-id-123"); +``` + +### Update + +```javascript +// Partial update - only specified fields change +await base44.entities.Task.update("task-id-123", { + status: "completed", + completedAt: new Date().toISOString() +}); +``` + +### Delete + +```javascript +// Single record +await base44.entities.Task.delete("task-id-123"); + +// Multiple records matching query +await base44.entities.Task.deleteMany({ status: "archived" }); +``` + +### Subscribe to Realtime Updates + +```javascript +// Subscribe to all changes on Task entity +const unsubscribe = base44.entities.Task.subscribe((event) => { + console.log(`Task ${event.id} was ${event.type}:`, event.data); + // event.type is "create", "update", or "delete" +}); + +// Later: unsubscribe to stop receiving updates +unsubscribe(); +``` + +**Event structure:** +```javascript +{ + type: "create" | "update" | "delete", + data: { ... }, // the entity data + id: "entity-id", // the affected entity's ID + timestamp: "2024-01-15T10:30:00Z" +} +``` + +## User Entity + +Every app has a built-in `User` entity with special rules: + +- Regular users can only read/update **their own** record +- Cannot create users via `entities.create()` - use `auth.register()` instead +- Service role has full access to all user records + +```javascript +// Get current user's record +const me = await base44.entities.User.get(currentUserId); + +// Service role: get any user +const anyUser = await base44.asServiceRole.entities.User.get(userId); +``` + +## Service Role Access + +For admin-level operations (bypass user permissions): + +```javascript +// Backend only +const allTasks = await base44.asServiceRole.entities.Task.list(); +const allUsers = await base44.asServiceRole.entities.User.list(); +``` + +## Permissions (RLS & FLS) + +Data access is controlled by **Row Level Security (RLS)** and **Field Level Security (FLS)** rules defined in entity schemas. + +1. **Authentication level**: anonymous, authenticated, or service role +2. **RLS rules**: Control which records (rows) users can create/read/update/delete +3. **FLS rules**: Control which fields users can read/write within accessible records + +Operations succeed or fail based on these rules - no partial results. + +RLS and FLS are configured in entity schema files (`base44/entities/*.jsonc`). See [entities-create.md](../../base44-cli/references/entities-create.md#row-level-security-rls) for configuration details. + +**Note:** `asServiceRole` sets the user's role to `"admin"` but does NOT bypass RLS. Your RLS rules must include admin access (e.g., `{ "user_condition": { "role": "admin" } }`) for service role operations to succeed. + +## Type Definitions + +### RealtimeEvent + +```typescript +/** Event types for realtime entity updates. */ +type RealtimeEventType = "create" | "update" | "delete"; + +/** Payload received when a realtime event occurs. */ +interface RealtimeEvent { + /** The type of change that occurred. */ + type: RealtimeEventType; + /** The entity data. */ + data: any; + /** The unique identifier of the affected entity. */ + id: string; + /** ISO 8601 timestamp of when the event occurred. */ + timestamp: string; +} + +/** Callback function invoked when a realtime event occurs. */ +type RealtimeCallback = (event: RealtimeEvent) => void; + +/** Function returned from subscribe, call it to unsubscribe. */ +type Subscription = () => void; +``` + +### EntityHandler + +```typescript +/** Entity handler providing CRUD operations for a specific entity type. */ +interface EntityHandler { + /** Lists records with optional pagination and sorting. */ + list(sort?: string, limit?: number, skip?: number, fields?: string[]): Promise; + + /** Filters records based on a query. */ + filter(query: Record, sort?: string, limit?: number, skip?: number, fields?: string[]): Promise; + + /** Gets a single record by ID. */ + get(id: string): Promise; + + /** Creates a new record. */ + create(data: Record): Promise; + + /** Updates an existing record. */ + update(id: string, data: Record): Promise; + + /** Deletes a single record by ID. */ + delete(id: string): Promise; + + /** Deletes multiple records matching a query. */ + deleteMany(query: Record): Promise; + + /** Creates multiple records in a single request. */ + bulkCreate(data: Record[]): Promise; + + /** Imports records from a file (frontend only). */ + importEntities(file: File): Promise; + + /** Subscribes to realtime updates. Returns unsubscribe function. */ + subscribe(callback: RealtimeCallback): Subscription; +} +``` + +### EntitiesModule + +```typescript +/** Entities module for managing app data. */ +interface EntitiesModule { + /** Access any entity by name dynamically. */ + [entityName: string]: EntityHandler; +} +``` diff --git a/evals/fixtures/cli-entities/skills/base44-sdk/references/functions.md b/evals/fixtures/cli-entities/skills/base44-sdk/references/functions.md new file mode 100644 index 0000000..8072465 --- /dev/null +++ b/evals/fixtures/cli-entities/skills/base44-sdk/references/functions.md @@ -0,0 +1,216 @@ +# Functions Module + +Invoke custom backend functions via `base44.functions`. + +## Contents +- [Method](#method) +- [Invoking Functions](#invoking-functions) (Frontend, File Upload, Service Role, REST API) +- [Writing Backend Functions](#writing-backend-functions) (Basic, Service Role, Secrets, Errors) +- [Setup Requirements](#setup-requirements) +- [Authentication Modes](#authentication-modes) + +## Method + +```javascript +base44.functions.invoke(functionName, data): Promise +``` + +- `functionName`: Name of the backend function +- `data`: Object of parameters (sent as JSON, or multipart if contains File objects) +- Returns: Whatever the function returns + +## Invoking Functions + +### From Frontend + +```javascript +const result = await base44.functions.invoke("processOrder", { + orderId: "order-123", + action: "ship" +}); + +console.log(result); +``` + +### With File Upload + +```javascript +const fileInput = document.querySelector('input[type="file"]'); +const file = fileInput.files[0]; + +// Automatically uses multipart/form-data when File objects present +const result = await base44.functions.invoke("uploadDocument", { + file: file, + category: "invoices" +}); +``` + +### With Service Role (Backend) + +```javascript +// Inside another backend function +const result = await base44.asServiceRole.functions.invoke("adminTask", { + userId: "user-123" +}); +``` + +### Via REST API (curl) + +Functions can be called via HTTP POST to your app domain: + +```bash +curl -X POST "https:///functions/" \ + -H "Content-Type: application/json" \ + -d '{"key": "value"}' +``` + +## Writing Backend Functions + +Backend functions run on Deno. Must export using `Deno.serve()`. + +### Required Directory Structure + +Each function must be in its own subdirectory under `base44/functions/` with a configuration file: + +``` +base44/ + functions/ + process-order/ # kebab-case directory name + function.jsonc # required configuration + index.ts # entry point +``` + +**function.jsonc:** +```jsonc +{ + "name": "process-order", + "entry": "index.ts" +} +``` + +For complete setup and deployment instructions, see [functions-create.md](../../base44-cli/references/functions-create.md) in base44-cli. + +### Basic Structure + +```javascript +// base44/functions/process-order/index.ts +import { createClientFromRequest } from "npm:@base44/sdk"; + +Deno.serve(async (req) => { + // Get authenticated client from request + const base44 = createClientFromRequest(req); + + // Parse input + const { orderId, action } = await req.json(); + + // Your logic here + const order = await base44.entities.Orders.get(orderId); + + // Return response + return Response.json({ + success: true, + order: order + }); +}); +``` + +### With Service Role Access + +```javascript +import { createClientFromRequest } from "npm:@base44/sdk"; + +Deno.serve(async (req) => { + const base44 = createClientFromRequest(req); + + // Check user is authenticated + const user = await base44.auth.me(); + if (!user) { + return Response.json({ error: "Unauthorized" }, { status: 401 }); + } + + // Use service role for admin operations + const allOrders = await base44.asServiceRole.entities.Orders.list(); + + return Response.json({ orders: allOrders }); +}); +``` + +### Using Secrets + +```javascript +Deno.serve(async (req) => { + // Access environment variables (configured in app settings) + const apiKey = Deno.env.get("STRIPE_API_KEY"); + + const response = await fetch("https://api.stripe.com/v1/charges", { + headers: { + "Authorization": `Bearer ${apiKey}` + } + }); + + return Response.json(await response.json()); +}); +``` + +### Error Handling + +```javascript +import { createClientFromRequest } from "npm:@base44/sdk"; + +Deno.serve(async (req) => { + try { + const base44 = createClientFromRequest(req); + const { orderId } = await req.json(); + + const order = await base44.entities.Orders.get(orderId); + if (!order) { + return Response.json( + { error: "Order not found" }, + { status: 404 } + ); + } + + return Response.json({ order }); + + } catch (error) { + return Response.json( + { error: error.message }, + { status: 500 } + ); + } +}); +``` + +## Setup Requirements + +1. Enable Backend Functions in app settings (requires appropriate plan) +2. Create function files in `/functions` folder +3. Configure secrets via app dashboard for API keys + +## Authentication Modes + +| Mode | Context | Permissions | +|------|---------|-------------| +| User | `base44.functions.invoke()` | Runs under calling user's permissions | +| Service Role | `base44.asServiceRole.functions.invoke()` | Admin-level access | + +Inside the function, use `createClientFromRequest(req)` to get a client that inherits the caller's auth context. + +## Type Definitions + +```typescript +/** Functions module for invoking custom backend functions. */ +interface FunctionsModule { + /** + * Invokes a custom backend function by name. + * + * If any parameter is a File object, the request will automatically be + * sent as multipart/form-data. Otherwise, it will be sent as JSON. + * + * @param functionName - The name of the function to invoke. + * @param data - An object containing named parameters for the function. + * @returns Promise resolving to the function's response. + */ + invoke(functionName: string, data: Record): Promise; +} +``` diff --git a/evals/fixtures/cli-entities/skills/base44-sdk/references/integrations.md b/evals/fixtures/cli-entities/skills/base44-sdk/references/integrations.md new file mode 100644 index 0000000..f356a18 --- /dev/null +++ b/evals/fixtures/cli-entities/skills/base44-sdk/references/integrations.md @@ -0,0 +1,377 @@ +# Integrations Module + +Access third-party services via `base44.integrations`. + +## Types of Integrations + +1. **Core/Built-in**: AI, email, file uploads (available by default) +2. **Catalog integrations**: Pre-built connectors from Base44 catalog +3. **Custom integrations**: Your own OpenAPI-based integrations + +## Accessing Integrations + +```javascript +// Core integrations +base44.integrations.Core.FunctionName(params) + +// Custom integrations +base44.integrations.custom.call(slug, operationId, params) +``` + +## Core Integrations + +### InvokeLLM (AI Text Generation) + +Generate text or structured JSON data using AI models. + +```javascript +// Basic prompt - returns string +const response = await base44.integrations.Core.InvokeLLM({ + prompt: "Summarize this text: ..." +}); + +// With internet context (uses Google Search, Maps, News) +const response = await base44.integrations.Core.InvokeLLM({ + prompt: "What's the current weather in NYC?", + add_context_from_internet: true +}); + +// Structured JSON response - returns object +const response = await base44.integrations.Core.InvokeLLM({ + prompt: "Analyze the sentiment of: 'Great product but slow shipping'", + response_json_schema: { + type: "object", + properties: { + sentiment: { type: "string", enum: ["positive", "negative", "mixed"] }, + score: { type: "number", description: "Score from 1-10" }, + key_points: { type: "array", items: { type: "string" } } + } + } +}); +// Returns: { sentiment: "mixed", score: 7, key_points: ["great product", "slow shipping"] } + +// With file attachments (uploaded via UploadFile) +const response = await base44.integrations.Core.InvokeLLM({ + prompt: "Describe what's in this image", + file_urls: ["https://...uploaded_image.png"] +}); +``` + +**Parameters:** +- `prompt` (string, required): The prompt text to send to the model +- `add_context_from_internet` (boolean, optional): If true, uses Google Search/Maps/News for real-time context +- `response_json_schema` (object, optional): JSON schema for structured output +- `file_urls` (string[], optional): URLs of uploaded files for context + +### GenerateImage + +Create AI-generated images from text prompts. + +```javascript +const { url } = await base44.integrations.Core.GenerateImage({ + prompt: "A serene mountain landscape with a lake" +}); +console.log(url); // https://...generated_image.png +``` + +### SendEmail + +Send emails to registered users. Every app gets this integration (no plan upgrade required). + +```javascript +await base44.integrations.Core.SendEmail({ + to: "user@example.com", + subject: "Welcome!", + body: "

Hello

Welcome to our app.

", + from_name: "My App" // optional, defaults to app name +}); +``` + +**Parameters:** +- `to` (string, required): Recipient email address +- `subject` (string, required): Email subject line +- `body` (string, required): Plain text or HTML email body +- `from_name` (string, optional): Sender name displayed to recipient + +**Limitations:** +- 1 credit per email (2 credits with custom domain) + +### UploadFile (Public) + +Upload files to public storage. + +```javascript +const fileInput = document.querySelector('input[type="file"]'); +const { file_url } = await base44.integrations.Core.UploadFile({ + file: fileInput.files[0] +}); +console.log(file_url); // https://...uploaded_file.pdf +``` + +### UploadPrivateFile + +Upload files to private storage that requires a signed URL to access. + +```javascript +const { file_uri } = await base44.integrations.Core.UploadPrivateFile({ + file: fileInput.files[0] +}); +console.log(file_uri); // "private/user123/document.pdf" + +// Create a signed URL to access the file +const { signed_url } = await base44.integrations.Core.CreateFileSignedUrl({ + file_uri, + expires_in: 3600 // URL expires in 1 hour (default: 300 seconds) +}); +console.log(signed_url); // Temporary URL to access the private file +``` + +### CreateFileSignedUrl + +Generate temporary access links for private files. + +```javascript +const { signed_url } = await base44.integrations.Core.CreateFileSignedUrl({ + file_uri: "private/user123/document.pdf", + expires_in: 7200 // 2 hours +}); +``` + +**Parameters:** +- `file_uri` (string, required): URI from UploadPrivateFile +- `expires_in` (number, optional): Expiration time in seconds (default: 300) + +### ExtractDataFromUploadedFile + +Extract structured data from uploaded files using AI. + +```javascript +// First upload the file +const { file_url } = await base44.integrations.Core.UploadFile({ file }); + +// Then extract structured data +const result = await base44.integrations.Core.ExtractDataFromUploadedFile({ + file_url, + json_schema: { + type: "object", + properties: { + invoice_number: { type: "string" }, + total_amount: { type: "number" }, + date: { type: "string" }, + vendor_name: { type: "string" } + } + } +}); +console.log(result); // { invoice_number: "INV-12345", total_amount: 1250.00, ... } +``` + +## Custom Integrations + +Custom integrations allow workspace administrators to connect any external API by importing an OpenAPI specification. Use `base44.integrations.custom.call()` to invoke them. + +### Syntax + +```javascript +const response = await base44.integrations.custom.call( + slug, // Integration identifier (set by admin) + operationId, // Endpoint in "method:path" format + params // Optional: payload, pathParams, queryParams +); +``` + +### Examples + +```javascript +// GET request with query params +const response = await base44.integrations.custom.call( + "my-crm", + "get:/contacts", + { queryParams: { limit: 10, status: "active" } } +); + +// POST request with body +const response = await base44.integrations.custom.call( + "my-crm", + "post:/contacts", + { payload: { name: "John Doe", email: "john@example.com" } } +); + +// Request with path parameters +const response = await base44.integrations.custom.call( + "github", + "post:/repos/{owner}/{repo}/issues", + { + pathParams: { owner: "myorg", repo: "myrepo" }, + payload: { title: "Bug report", body: "Something is broken" } + } +); +``` + +### Response Structure + +```javascript +{ + success: true, // Whether external API returned 2xx + status_code: 200, // HTTP status code + data: { ... } // Response from external API +} +``` + +## Requirements + +- **Core integrations**: Available on all plans +- **Catalog/Custom integrations**: Require Builder plan or higher + +## Type Definitions + +### Core Integration Parameters + +```typescript +/** Parameters for the InvokeLLM function. */ +interface InvokeLLMParams { + /** The prompt text to send to the model. */ + prompt: string; + /** If true, uses Google Search/Maps/News for real-time context. */ + add_context_from_internet?: boolean; + /** JSON schema for structured output. If provided, returns object instead of string. */ + response_json_schema?: object; + /** File URLs (from UploadFile) to provide as context. */ + file_urls?: string[]; +} + +/** Parameters for the GenerateImage function. */ +interface GenerateImageParams { + /** Description of the image to generate. */ + prompt: string; +} + +/** Result from GenerateImage. */ +interface GenerateImageResult { + /** URL of the generated image. */ + url: string; +} + +/** Parameters for the UploadFile function. */ +interface UploadFileParams { + /** The file object to upload. */ + file: File; +} + +/** Result from UploadFile. */ +interface UploadFileResult { + /** URL of the uploaded file. */ + file_url: string; +} + +/** Parameters for the SendEmail function. */ +interface SendEmailParams { + /** Recipient email address. */ + to: string; + /** Email subject line. */ + subject: string; + /** Plain text or HTML email body. */ + body: string; + /** Sender name (defaults to app name). */ + from_name?: string; +} + +/** Parameters for ExtractDataFromUploadedFile. */ +interface ExtractDataFromUploadedFileParams { + /** URL of the uploaded file. */ + file_url: string; + /** JSON schema defining fields to extract. */ + json_schema: object; +} + +/** Parameters for UploadPrivateFile. */ +interface UploadPrivateFileParams { + /** The file object to upload. */ + file: File; +} + +/** Result from UploadPrivateFile. */ +interface UploadPrivateFileResult { + /** URI of the private file (used for signed URLs). */ + file_uri: string; +} + +/** Parameters for CreateFileSignedUrl. */ +interface CreateFileSignedUrlParams { + /** URI from UploadPrivateFile. */ + file_uri: string; + /** Expiration time in seconds (default: 300). */ + expires_in?: number; +} + +/** Result from CreateFileSignedUrl. */ +interface CreateFileSignedUrlResult { + /** Temporary signed URL to access the file. */ + signed_url: string; +} +``` + +### CoreIntegrations + +```typescript +/** Core package containing built-in Base44 integration functions. */ +interface CoreIntegrations { + InvokeLLM(params: InvokeLLMParams): Promise; + GenerateImage(params: GenerateImageParams): Promise; + UploadFile(params: UploadFileParams): Promise; + SendEmail(params: SendEmailParams): Promise; + ExtractDataFromUploadedFile(params: ExtractDataFromUploadedFileParams): Promise; + UploadPrivateFile(params: UploadPrivateFileParams): Promise; + CreateFileSignedUrl(params: CreateFileSignedUrlParams): Promise; +} +``` + +### Custom Integrations + +```typescript +/** Parameters for calling a custom integration endpoint. */ +interface CustomIntegrationCallParams { + /** Request body payload. */ + payload?: Record; + /** Path parameters to substitute in the URL. */ + pathParams?: Record; + /** Query string parameters. */ + queryParams?: Record; + /** Additional headers for this request. */ + headers?: Record; +} + +/** Response from a custom integration call. */ +interface CustomIntegrationCallResponse { + /** Whether the external API returned a 2xx status code. */ + success: boolean; + /** The HTTP status code from the external API. */ + status_code: number; + /** The response data from the external API. */ + data: any; +} + +/** Module for calling custom workspace-level API integrations. */ +interface CustomIntegrationsModule { + /** + * Call a custom integration endpoint. + * @param slug - The integration's unique identifier. + * @param operationId - The operation ID from the OpenAPI spec. + * @param params - Optional payload, pathParams, queryParams, headers. + */ + call(slug: string, operationId: string, params?: CustomIntegrationCallParams): Promise; +} +``` + +### IntegrationsModule + +```typescript +/** Integrations module for calling integration endpoints. */ +type IntegrationsModule = { + /** Core package with built-in integrations. */ + Core: CoreIntegrations; + /** Custom integrations module. */ + custom: CustomIntegrationsModule; + /** Additional integration packages (dynamic). */ + [packageName: string]: any; +}; +``` diff --git a/evals/fixtures/cli-entities/skills/base44-sdk/references/users.md b/evals/fixtures/cli-entities/skills/base44-sdk/references/users.md new file mode 100644 index 0000000..4205b06 --- /dev/null +++ b/evals/fixtures/cli-entities/skills/base44-sdk/references/users.md @@ -0,0 +1,82 @@ +# Users Module + +Invite users to the app via `base44.users`. + +## Contents +- [Methods](#methods) +- [Examples](#examples) (Invite User) +- [Roles](#roles) +- [Notes](#notes) + +## Methods + +| Method | Signature | Description | +|--------|-----------|-------------| +| `inviteUser(user_email, role)` | `Promise` | Invite a user to the app | + +## Examples + +### Invite User + +```javascript +// Invite a user with "user" role +await base44.users.inviteUser("newuser@example.com", "user"); + +// Invite an admin +await base44.users.inviteUser("admin@example.com", "admin"); +``` + +### Invite Multiple Users + +```javascript +const usersToInvite = [ + { email: "user1@example.com", role: "user" }, + { email: "user2@example.com", role: "user" }, + { email: "manager@example.com", role: "admin" } +]; + +for (const user of usersToInvite) { + await base44.users.inviteUser(user.email, user.role); + console.log(`Invited ${user.email} as ${user.role}`); +} +``` + +## Roles + +The `role` parameter must be one of: + +| Role | Description | +|------|-------------| +| `"user"` | Standard user with default permissions | +| `"admin"` | Administrator with elevated permissions | + +**Note:** Only `"user"` and `"admin"` are valid role values. An error will be thrown if you pass any other value. + +## Notes + +- **Email invitation**: The invited user receives an email with a link to join the app +- **Duplicate handling**: Inviting an existing user will re-send the invitation +- **Also available in auth**: `base44.auth.inviteUser()` provides the same functionality +- **Role validation**: Only `"user"` or `"admin"` are accepted + +```javascript +// These are equivalent: +await base44.users.inviteUser("newuser@example.com", "user"); +await base44.auth.inviteUser("newuser@example.com", "user"); +``` + +## Type Definitions + +```typescript +/** Users module for inviting users to the app. */ +interface UsersModule { + /** + * Invite a user to the application. + * @param user_email - User's email address. + * @param role - User's role ('user' or 'admin'). + * @returns Promise resolving when the invitation is sent. + * @throws Error if role is not 'user' or 'admin'. + */ + inviteUser(user_email: string, role: "user" | "admin"): Promise; +} +``` diff --git a/evals/fixtures/cli-functions/eval.json b/evals/fixtures/cli-functions/eval.json new file mode 100644 index 0000000..01b3f2e --- /dev/null +++ b/evals/fixtures/cli-functions/eval.json @@ -0,0 +1,221 @@ +{ + "$schema": "../../eval.schema.json", + "name": "cli-functions", + "description": "Test CLI backend function creation: env vars, LLM, service role, response patterns", + "prompts": [ + { + "name": "cli-function-env", + "description": "Create function using environment variables", + "prompt": "Create a backend function called 'send-webhook' that reads a WEBHOOK_URL from Deno environment variables and sends a POST request to it with data from the request body. Create both base44/functions/send-webhook/function.jsonc and base44/functions/send-webhook/index.ts.", + "expectedSkills": [ + "base44-cli" + ], + "checks": [ + { + "type": "file-exists", + "filePath": "base44/functions/send-webhook/function.jsonc", + "description": "base44/functions/send-webhook/function.jsonc exists" + }, + { + "type": "file-exists", + "filePath": "base44/functions/send-webhook/index.ts", + "description": "base44/functions/send-webhook/index.ts exists" + }, + { + "type": "valid-json", + "filePath": "base44/functions/send-webhook/function.jsonc", + "description": "base44/functions/send-webhook/function.jsonc is valid JSON" + }, + { + "type": "function-def", + "filePath": "base44/functions/send-webhook/function.jsonc", + "target": "file", + "description": "Function config validates (base44/functions/send-webhook/function.jsonc)" + }, + { + "type": "file-content", + "filePath": "base44/functions/send-webhook/index.ts", + "pattern": "Deno\\.serve", + "description": "Uses Deno.serve()" + }, + { + "type": "file-content", + "filePath": "base44/functions/send-webhook/index.ts", + "pattern": "Deno\\.env\\.get", + "description": "Uses Deno environment variables" + }, + { + "type": "file-content", + "filePath": "base44/functions/send-webhook/index.ts", + "pattern": "Response\\.json\\(", + "description": "Returns Response.json()" + } + ] + }, + { + "name": "cli-function-llm", + "description": "Create function using LLM integration", + "prompt": "Create a backend function called 'summarize-text' that accepts text in the request body, uses the Base44 SDK's integrations.Core.InvokeLLM to generate a summary, and returns it. Create both base44/functions/summarize-text/function.jsonc and base44/functions/summarize-text/index.ts.", + "expectedSkills": [ + "base44-cli" + ], + "checks": [ + { + "type": "file-exists", + "filePath": "base44/functions/summarize-text/function.jsonc", + "description": "base44/functions/summarize-text/function.jsonc exists" + }, + { + "type": "file-exists", + "filePath": "base44/functions/summarize-text/index.ts", + "description": "base44/functions/summarize-text/index.ts exists" + }, + { + "type": "valid-json", + "filePath": "base44/functions/summarize-text/function.jsonc", + "description": "base44/functions/summarize-text/function.jsonc is valid JSON" + }, + { + "type": "function-def", + "filePath": "base44/functions/summarize-text/function.jsonc", + "target": "file", + "description": "Function config validates (base44/functions/summarize-text/function.jsonc)" + }, + { + "type": "file-content", + "filePath": "base44/functions/summarize-text/index.ts", + "pattern": "Deno\\.serve", + "description": "Uses Deno.serve()" + }, + { + "type": "file-content", + "filePath": "base44/functions/summarize-text/index.ts", + "pattern": "createClientFromRequest", + "description": "Imports base44 client" + }, + { + "type": "file-content", + "filePath": "base44/functions/summarize-text/index.ts", + "pattern": "integrations\\.Core\\.InvokeLLM|InvokeLLM", + "description": "Matches expected pattern in index.ts" + }, + { + "type": "file-content", + "filePath": "base44/functions/summarize-text/index.ts", + "pattern": "npm:@base44/sdk|from.*@base44/sdk", + "description": "Imports base44 client" + } + ] + }, + { + "name": "cli-function-service-role", + "description": "Create function using service role access", + "prompt": "Create a backend function called 'admin-stats' that uses service role access to count all orders and return statistics. It should use base44.asServiceRole.entities.Order.list() to get all orders regardless of user permissions. Create both base44/functions/admin-stats/function.jsonc and base44/functions/admin-stats/index.ts.", + "expectedSkills": [ + "base44-cli" + ], + "checks": [ + { + "type": "file-exists", + "filePath": "base44/functions/admin-stats/function.jsonc", + "description": "base44/functions/admin-stats/function.jsonc exists" + }, + { + "type": "file-exists", + "filePath": "base44/functions/admin-stats/index.ts", + "description": "base44/functions/admin-stats/index.ts exists" + }, + { + "type": "valid-json", + "filePath": "base44/functions/admin-stats/function.jsonc", + "description": "base44/functions/admin-stats/function.jsonc is valid JSON" + }, + { + "type": "function-def", + "filePath": "base44/functions/admin-stats/function.jsonc", + "target": "file", + "description": "Function config validates (base44/functions/admin-stats/function.jsonc)" + }, + { + "type": "file-content", + "filePath": "base44/functions/admin-stats/index.ts", + "pattern": "Deno\\.serve", + "description": "Uses Deno.serve()" + }, + { + "type": "file-content", + "filePath": "base44/functions/admin-stats/index.ts", + "pattern": "asServiceRole", + "description": "Uses asServiceRole for admin access" + }, + { + "type": "file-content", + "filePath": "base44/functions/admin-stats/index.ts", + "pattern": "createClientFromRequest", + "description": "Imports base44 client" + }, + { + "type": "file-content", + "filePath": "base44/functions/admin-stats/index.ts", + "pattern": "Response\\.json\\(", + "description": "Returns Response.json()" + } + ] + }, + { + "name": "cli-function-response", + "description": "Create function with proper error handling and response patterns", + "prompt": "Create a backend function called 'validate-coupon' that accepts a coupon_code in the request body, validates it against known coupons, and returns either the discount amount or an error. Use proper try/catch error handling and return appropriate HTTP responses. Create both base44/functions/validate-coupon/function.jsonc and base44/functions/validate-coupon/index.ts.", + "expectedSkills": [ + "base44-cli" + ], + "checks": [ + { + "type": "file-exists", + "filePath": "base44/functions/validate-coupon/function.jsonc", + "description": "base44/functions/validate-coupon/function.jsonc exists" + }, + { + "type": "file-exists", + "filePath": "base44/functions/validate-coupon/index.ts", + "description": "base44/functions/validate-coupon/index.ts exists" + }, + { + "type": "valid-json", + "filePath": "base44/functions/validate-coupon/function.jsonc", + "description": "base44/functions/validate-coupon/function.jsonc is valid JSON" + }, + { + "type": "function-def", + "filePath": "base44/functions/validate-coupon/function.jsonc", + "target": "file", + "description": "Function config validates (base44/functions/validate-coupon/function.jsonc)" + }, + { + "type": "file-content", + "filePath": "base44/functions/validate-coupon/index.ts", + "pattern": "Deno\\.serve", + "description": "Uses Deno.serve()" + }, + { + "type": "file-content", + "filePath": "base44/functions/validate-coupon/index.ts", + "pattern": "try\\s*\\{", + "description": "Has try/catch error handling" + }, + { + "type": "file-content", + "filePath": "base44/functions/validate-coupon/index.ts", + "pattern": "catch", + "description": "Has catch block" + }, + { + "type": "file-content", + "filePath": "base44/functions/validate-coupon/index.ts", + "pattern": "Response\\.json\\(", + "description": "Returns Response.json()" + } + ] + } + ] +} diff --git a/evals/fixtures/cli-functions/project/.claude/settings.json b/evals/fixtures/cli-functions/project/.claude/settings.json new file mode 100644 index 0000000..ab60505 --- /dev/null +++ b/evals/fixtures/cli-functions/project/.claude/settings.json @@ -0,0 +1,6 @@ +{ + "permissions": { + "allow": ["Read", "Write", "Edit", "Bash", "Glob", "Grep"] + }, + "skills": ["./skills"] +} diff --git a/evals/fixtures/cli-functions/project/AGENTS.md b/evals/fixtures/cli-functions/project/AGENTS.md new file mode 100644 index 0000000..6c0f2be --- /dev/null +++ b/evals/fixtures/cli-functions/project/AGENTS.md @@ -0,0 +1,25 @@ +IMPORTANT: Prefer retrieval-led reasoning over pre-training-led reasoning. Base44 is NOT Firebase, Supabase, or any other SDK you know. Every API name below is the ONLY correct form. Always verify against this index and the referenced docs. + +[Base44 SDK Docs Index]|root: ./skills/base44-sdk/references +|entities.md: CRUD - create(data), list(), filter(query), get(id), update(id,data), delete(id), subscribe(cb) +|auth.md: Auth - loginViaEmailPassword(), loginWithProvider('google'), me(), register(), logout() +|base44-agents.md: Agents - createConversation({agent_name}), addMessage(conv,{role,content}), subscribeToConversation(id,cb) +|functions.md: Functions - invoke(name, data) +|integrations.md: Core - InvokeLLM({prompt}), SendEmail({to,subject,body}), UploadFile({file}) +|client.md: Client - createClient({appId}), import from "@base44/sdk" +|QUICK_REFERENCE.md: All method signatures + +[Base44 CLI Docs Index]|root: ./skills/base44-cli/references +|entities-create.md: Entity schema - {name:"PascalCase", type:"object", properties:{}, required:[]} +|functions-create.md: Function - function.jsonc {name,entry} + index.ts with Deno.serve + createClientFromRequest +|create.md: Project creation - npx base44 create name -p . +|deploy.md: Deploy - npx base44 deploy -y +|agents-push.md: Agent config - {name:"snake_case", description, instructions, tool_configs:[{entity_name,allowed_operations}|{function_name,description}]} + +[Common Mistakes - NEVER use these hallucinated names] +|SDK: Do NOT use find() → use filter(), findOne() → get(), insert() → create(), remove() → delete(), onChange() → subscribe() +|SDK: Do NOT use signInWithGoogle() → use loginWithProvider('google'), functions.call() → functions.invoke() +|SDK: Do NOT use ai.generate() → use integrations.Core.InvokeLLM({prompt}) +|CLI: Entity names must be PascalCase alphanumeric. File names must be kebab-case.jsonc +|CLI: Agent names must be lowercase with underscores only. Function entry field is "entry" not "entrypoint" +|CLI: Backend functions import from "npm:@base44/sdk" (Deno requires npm: prefix) diff --git a/evals/fixtures/cli-functions/project/CLAUDE.md b/evals/fixtures/cli-functions/project/CLAUDE.md new file mode 100644 index 0000000..724ce4d --- /dev/null +++ b/evals/fixtures/cli-functions/project/CLAUDE.md @@ -0,0 +1,7 @@ +# Base44 Development Guidelines + +When working with Base44 projects, first explore the existing project structure to understand what already exists. Look at existing code, config files, and patterns in use. + +Then consult the AGENTS.md index in the project root for correct API patterns. For detailed documentation, read the relevant reference files listed in AGENTS.md. + +IMPORTANT: Prefer retrieval-led reasoning over pre-training-led reasoning. Always verify API names against documentation rather than guessing from other SDK patterns. diff --git a/evals/fixtures/cli-functions/project/base44/config.jsonc b/evals/fixtures/cli-functions/project/base44/config.jsonc new file mode 100644 index 0000000..0f5cfc9 --- /dev/null +++ b/evals/fixtures/cli-functions/project/base44/config.jsonc @@ -0,0 +1,10 @@ +{ + "name": "CLI Functions App", + "description": "A test application for CLI function operations", + "entitiesDir": "./entities", + "functionsDir": "./functions", + "site": { + "buildCommand": "npm run build", + "outputDirectory": "dist" + } +} diff --git a/evals/fixtures/cli-functions/project/package.json b/evals/fixtures/cli-functions/project/package.json new file mode 100644 index 0000000..fc7d6eb --- /dev/null +++ b/evals/fixtures/cli-functions/project/package.json @@ -0,0 +1,11 @@ +{ + "name": "cli-functions-app", + "version": "1.0.0", + "type": "module", + "dependencies": { + "@base44/sdk": "latest" + }, + "devDependencies": { + "base44": "latest" + } +} diff --git a/evals/fixtures/cli-functions/skills/base44-cli/SKILL.md b/evals/fixtures/cli-functions/skills/base44-cli/SKILL.md new file mode 100644 index 0000000..3c33f05 --- /dev/null +++ b/evals/fixtures/cli-functions/skills/base44-cli/SKILL.md @@ -0,0 +1,384 @@ +--- +name: base44-cli +description: "The base44 CLI is used for EVERYTHING related to base44 projects: resource configuration (entities, backend functions, ai agents), initialization and actions (resource creation, deployment). This skill is the place for learning about how to configure resources. When you plan or implement a feature, you must learn this skill" +--- + +# Base44 CLI + +Create and manage Base44 apps (projects) using the Base44 CLI tool. + +## ⚡ IMMEDIATE ACTION REQUIRED - Read This First + +This skill activates on ANY mention of "base44" or when a `base44/` folder exists. **DO NOT read documentation files or search the web before acting.** + +**Your first action MUST be:** +1. Check if `base44/config.jsonc` exists in the current directory +2. If **NO** (new project scenario): + - This skill (base44-cli) handles the request + - Guide user through project initialization + - Do NOT activate base44-sdk yet +3. If **YES** (existing project scenario): + - Transfer to base44-sdk skill for implementation + - This skill only handles CLI commands (login, deploy, entities push) + +## Critical: Local Installation Only + +NEVER call `base44` directly. The CLI is installed locally as a dev dependency and must be accessed via a package manager: + +- `npx base44 ` (npm - recommended) +- `yarn base44 ` (yarn) +- `pnpm base44 ` (pnpm) + +WRONG: `base44 login` +RIGHT: `npx base44 login` + +## MANDATORY: Authentication Check at Session Start + +**CRITICAL**: At the very start of every AI session when this skill is activated, you MUST: + +1. **Check authentication status** by running: + ```bash + npx base44 whoami + ``` + +2. **If the user is logged in** (command succeeds and shows an email): + - Continue with the requested task + +3. **If the user is NOT logged in** (command fails or shows an error): + - **STOP immediately** + - **DO NOT proceed** with any CLI operations + - **Ask the user to login manually** by running: + ```bash + npx base44 login + ``` + - Wait for the user to confirm they have logged in before continuing + +**This check is mandatory and must happen before executing any other Base44 CLI commands.** + +## Overview + +The Base44 CLI provides command-line tools for authentication, creating projects, managing entities, and deploying Base44 applications. It is framework-agnostic and works with popular frontend frameworks like Vite, Next.js, and Create React App, Svelte, Vue, and more. + +## When to Use This Skill vs base44-sdk + +**Use base44-cli when:** +- Creating a **NEW** Base44 project from scratch +- Initializing a project in an empty directory +- Directory is missing `base44/config.jsonc` +- User mentions: "create a new project", "initialize project", "setup a project", "start a new Base44 app" +- Deploying, pushing entities, or authenticating via CLI +- Working with CLI commands (`npx base44 ...`) + +**Use base44-sdk when:** +- Building features in an **EXISTING** Base44 project +- `base44/config.jsonc` already exists +- Writing JavaScript/TypeScript code using Base44 SDK +- Implementing functionality, components, or features +- User mentions: "implement", "build a feature", "add functionality", "write code" + +**Skill Dependencies:** +- `base44-cli` is a **prerequisite** for `base44-sdk` in new projects +- If user wants to "create an app" and no Base44 project exists, use `base44-cli` first +- `base44-sdk` assumes a Base44 project is already initialized + +**State Check Logic:** +Before selecting a skill, check: +- IF (user mentions "create/build app" OR "make a project"): + - IF (directory is empty OR no `base44/config.jsonc` exists): + → Use **base44-cli** (project initialization needed) + - ELSE: + → Use **base44-sdk** (project exists, build features) + +## Project Structure + +A Base44 project combines a standard frontend project with a `base44/` configuration folder: + +``` +my-app/ +├── base44/ # Base44 configuration (created by CLI) +│ ├── config.jsonc # Project settings, site config +│ ├── entities/ # Entity schema definitions +│ │ ├── task.jsonc +│ │ └── board.jsonc +│ ├── functions/ # Backend functions (optional) +│ │ └── my-function/ +│ │ ├── function.jsonc +│ │ └── index.ts +│ └── agents/ # Agent configurations (optional) +│ └── support_agent.jsonc +├── src/ # Frontend source code +│ ├── api/ +│ │ └── base44Client.js # Base44 SDK client +│ ├── pages/ +│ ├── components/ +│ └── main.jsx +├── index.html # SPA entry point +├── package.json +└── vite.config.js # Or your framework's config +``` + +**Key files:** +- `base44/config.jsonc` - Project name, description, site build settings +- `base44/entities/*.jsonc` - Data model schemas (see Entity Schema section) +- `base44/agents/*.jsonc` - Agent configurations (optional) +- `src/api/base44Client.js` - Pre-configured SDK client for frontend use + +**config.jsonc example:** +```jsonc +{ + "name": "My App", // Required: project name + "description": "App description", // Optional: project description + "entitiesDir": "./entities", // Optional: default "entities" + "functionsDir": "./functions", // Optional: default "functions" + "agentsDir": "./agents", // Optional: default "agents" + "site": { // Optional: site deployment config + "installCommand": "npm install", // Optional: install dependencies + "buildCommand": "npm run build", // Optional: build command + "serveCommand": "npm run dev", // Optional: local dev server + "outputDirectory": "./dist" // Optional: build output directory + } +} +``` + +**Config properties:** + +| Property | Description | Default | +|----------|-------------|---------| +| `name` | Project name (required) | - | +| `description` | Project description | - | +| `entitiesDir` | Directory for entity schemas | `"entities"` | +| `functionsDir` | Directory for backend functions | `"functions"` | +| `agentsDir` | Directory for agent configs | `"agents"` | +| `site.installCommand` | Command to install dependencies | - | +| `site.buildCommand` | Command to build the project | - | +| `site.serveCommand` | Command to run dev server | - | +| `site.outputDirectory` | Build output directory for deployment | - | + +## Installation + +Install the Base44 CLI as a dev dependency in your project: + +```bash +npm install --save-dev base44 +``` + +**Important:** Never assume or hardcode the `base44` package version. Always install without a version specifier to get the latest version. + +Then run commands using `npx`: + +```bash +npx base44 +``` + +**Note:** All commands in this documentation use `npx base44`. You can also use `yarn base44`, or `pnpm base44` if preferred. + +## Available Commands + +### Authentication + +| Command | Description | Reference | +| --------------- | ----------------------------------------------- | ------------------------------------------- | +| `base44 login` | Authenticate with Base44 using device code flow | [auth-login.md](references/auth-login.md) | +| `base44 logout` | Logout from current device | [auth-logout.md](references/auth-logout.md) | +| `base44 whoami` | Display current authenticated user | [auth-whoami.md](references/auth-whoami.md) | + +### Project Management + +| Command | Description | Reference | +|---------|-------------|-----------| +| `base44 create` | Create a new Base44 project from a template | [create.md](references/create.md) ⚠️ **MUST READ** | +| `base44 link` | Link an existing local project to Base44 | [link.md](references/link.md) | +| `base44 dashboard` | Open the app dashboard in your browser | [dashboard.md](references/dashboard.md) | + +### Deployment + +| Command | Description | Reference | +|---------|-------------|-----------| +| `base44 deploy` | Deploy all resources (entities, functions, site) | [deploy.md](references/deploy.md) | + +### Entity Management + +| Action / Command | Description | Reference | +| ---------------------- | ------------------------------------------- | --------------------------------------------------- | +| Create Entities | Define entities in `base44/entities` folder | [entities-create.md](references/entities-create.md) | +| `base44 entities push` | Push local entities to Base44 | [entities-push.md](references/entities-push.md) | + +#### Entity Schema (Quick Reference) + +ALWAYS follow this exact structure when creating entity files: + +**File naming:** `base44/entities/{kebab-case-name}.jsonc` (e.g., `team-member.jsonc` for `TeamMember`) + +**Schema template:** +```jsonc +{ + "name": "EntityName", + "type": "object", + "properties": { + "field_name": { + "type": "string", + "description": "Field description" + } + }, + "required": ["field_name"] +} +``` + +**Field types:** `string`, `number`, `integer`, `boolean`, `array`, `object`, `binary` +**String formats:** `date`, `date-time`, `time`, `email`, `uri`, `hostname`, `ipv4`, `ipv6`, `uuid`, `file`, `regex`, `richtext` +**For enums:** Add `"enum": ["value1", "value2"]` and optionally `"default": "value1"` +**Entity names:** Must be alphanumeric only (pattern: `/^[a-zA-Z0-9]+$/`) + +For complete documentation, see [entities-create.md](references/entities-create.md). + +### Function Management + +| Action / Command | Description | Reference | +| ------------------------- | --------------------------------------------- | ------------------------------------------------------- | +| Create Functions | Define functions in `base44/functions` folder | [functions-create.md](references/functions-create.md) | +| `base44 functions deploy` | Deploy local functions to Base44 | [functions-deploy.md](references/functions-deploy.md) | + +### Agent Management + +Agents are conversational AI assistants that can interact with users, access your app's entities, and call backend functions. Use these commands to manage agent configurations. + +| Action / Command | Description | Reference | +| ----------------------- | --------------------------------------- | ----------------------------------------------- | +| Create Agents | Define agents in `base44/agents` folder | See Agent Schema below | +| `base44 agents pull` | Pull remote agents to local files | [agents-pull.md](references/agents-pull.md) | +| `base44 agents push` | Push local agents to Base44 | [agents-push.md](references/agents-push.md) | + +**Note:** Agent commands perform full synchronization - pushing replaces all remote agents with local ones, and pulling replaces all local agents with remote ones. + +#### Agent Schema (Quick Reference) + +**File naming:** `base44/agents/{agent_name}.jsonc` (e.g., `support_agent.jsonc`) + +**Schema template:** +```jsonc +{ + "name": "agent_name", + "description": "Brief description of what this agent does", + "instructions": "Detailed instructions for the agent's behavior", + "tool_configs": [ + // Entity tool - gives agent access to entity operations + { "entity_name": "tasks", "allowed_operations": ["read", "create", "update", "delete"] }, + // Backend function tool - gives agent access to a function + { "function_name": "send_email", "description": "Send an email notification" } + ], + "whatsapp_greeting": "Hello! How can I help you today?" +} +``` + +**Naming rules:** +- Agent names must match pattern: `/^[a-z0-9_]+$/` (lowercase alphanumeric with underscores, 1-100 chars) +- Valid: `support_agent`, `order_bot` +- Invalid: `Support-Agent`, `OrderBot` + +**Required fields:** `name`, `description`, `instructions` +**Optional fields:** `tool_configs` (defaults to `[]`), `whatsapp_greeting` + +**Tool config types:** +- **Entity tools**: `entity_name` + `allowed_operations` (array of: `read`, `create`, `update`, `delete`) +- **Backend function tools**: `function_name` + `description` + +### Site Deployment + +| Command | Description | Reference | +| -------------------- | ----------------------------------------- | ------------------------------------------- | +| `base44 site deploy` | Deploy built site files to Base44 hosting | [site-deploy.md](references/site-deploy.md) | + +**SPA only**: Base44 hosting supports Single Page Applications with a single `index.html` entry point. All routes are served from `index.html` (client-side routing). + +## Quick Start + +1. Install the CLI in your project: + ```bash + npm install --save-dev base44 + ``` + +2. Authenticate with Base44: + ```bash + npx base44 login + ``` + +3. Create a new project (ALWAYS provide name and `--path` flag): + ```bash + npx base44 create my-app -p . + ``` + +4. Build and deploy everything: + ```bash + npm run build + npx base44 deploy -y + ``` + +Or deploy individual resources: +- `npx base44 entities push` - Push entities only +- `npx base44 functions deploy` - Deploy functions only +- `npx base44 agents push` - Push agents only +- `npx base44 site deploy -y` - Deploy site only + +## Common Workflows + +### Creating a New Project + +**⚠️ MANDATORY: Before running `base44 create`, you MUST read [create.md](references/create.md) for:** +- **Template selection** - Choose the correct template (`backend-and-client` vs `backend-only`) +- **Correct workflow** - Different templates require different setup steps +- **Common pitfalls** - Avoid folder creation errors that cause failures + +Failure to follow the create.md instructions will result in broken project scaffolding. + +### Linking an Existing Project +```bash +# If you have base44/config.jsonc but no .app.jsonc +npx base44 link --create --name my-app +``` + +### Deploying All Changes +```bash +# Build your project first +npm run build + +# Deploy everything (entities, functions, and site) +npx base44 deploy -y +``` + +### Deploying Individual Resources +```bash +# Push only entities +npx base44 entities push + +# Deploy only functions +npx base44 functions deploy + +# Push only agents +npx base44 agents push + +# Deploy only site +npx base44 site deploy -y +``` + +### Opening the Dashboard +```bash +# Open app dashboard in browser +npx base44 dashboard +``` + +## Authentication + +Most commands require authentication. If you're not logged in, the CLI will automatically prompt you to login. Your session is stored locally and persists across CLI sessions. + +## Troubleshooting + +| Error | Solution | +| --------------------------- | ----------------------------------------------------------------------------------- | +| Not authenticated | Run `npx base44 login` first | +| No entities found | Ensure entities exist in `base44/entities/` directory | +| Entity not recognized | Ensure file uses kebab-case naming (e.g., `team-member.jsonc` not `TeamMember.jsonc`) | +| No functions found | Ensure functions exist in `base44/functions/` with valid `function.jsonc` configs | +| No agents found | Ensure agents exist in `base44/agents/` directory with valid `.jsonc` configs | +| Invalid agent name | Agent names must be lowercase alphanumeric with underscores only | +| No site configuration found | Check that `site.outputDirectory` is configured in project config | +| Site deployment fails | Ensure you ran `npm run build` first and the build succeeded | diff --git a/evals/fixtures/cli-functions/skills/base44-cli/references/agents-pull.md b/evals/fixtures/cli-functions/skills/base44-cli/references/agents-pull.md new file mode 100644 index 0000000..6a700f0 --- /dev/null +++ b/evals/fixtures/cli-functions/skills/base44-cli/references/agents-pull.md @@ -0,0 +1,73 @@ +# base44 agents pull + +Pull AI agent configurations from Base44 to local files. Agents are conversational AI assistants that can interact with users, access your app's entities, and call backend functions. + +## Syntax + +```bash +npx base44 agents pull +``` + +## Authentication + +**Required**: Yes. If not authenticated, you'll be prompted to login first. + +## What It Does + +1. Fetches all agents from Base44 +2. Writes agent files to the `base44/agents/` directory +3. Deletes local agent files that don't exist remotely +4. Reports written and deleted agents + +## Prerequisites + +- Must be run from a Base44 project directory +- Project must be linked to a Base44 app + +## Output + +```bash +$ npx base44 agents pull + +Fetching agents from Base44... +✓ Agents fetched successfully + +Writing agent files... +✓ Agent files written successfully + +Written: support_agent, order_bot +Deleted: old_agent + +Pulled 2 agents to base44/agents +``` + +## Agent Synchronization + +The pull operation synchronizes remote agents to your local files: + +- **Written**: Agent files created or updated from remote +- **Deleted**: Local agent files removed (didn't exist remotely) + +**Warning**: This operation replaces all local agent configurations with remote versions. Any local changes not pushed to Base44 will be overwritten. + +## Error Handling + +If no agents exist on Base44: +```bash +$ npx base44 agents pull +No agents found on Base44 +``` + +## Use Cases + +- Sync agent configurations to a new development machine +- Get the latest agent configurations from your team +- Restore local agent files after accidental deletion +- Start working on an existing project with agents + +## Notes + +- This command syncs agent configurations, not conversation data +- Agent files are stored as `.jsonc` in the `base44/agents/` directory +- The directory location is configurable via `agentsDir` in `config.jsonc` +- Use `base44 agents push` to upload local changes to Base44 diff --git a/evals/fixtures/cli-functions/skills/base44-cli/references/agents-push.md b/evals/fixtures/cli-functions/skills/base44-cli/references/agents-push.md new file mode 100644 index 0000000..fa2795b --- /dev/null +++ b/evals/fixtures/cli-functions/skills/base44-cli/references/agents-push.md @@ -0,0 +1,156 @@ +# base44 agents push + +Push local AI agent configurations to Base44. Agents are conversational AI assistants that can interact with users, access your app's entities, and call backend functions. + +## Syntax + +```bash +npx base44 agents push +``` + +## Authentication + +**Required**: Yes. If not authenticated, you'll be prompted to login first. + +## What It Does + +1. Reads all agent files from the `base44/agents/` directory +2. Validates agent configurations +3. Displays the count of agents to be pushed +4. Uploads agents to the Base44 backend +5. Reports the results: created, updated, and deleted agents + +## Prerequisites + +- Must be run from a Base44 project directory +- Project must have agent definitions in the `base44/agents/` folder + +## Output + +```bash +$ npx base44 agents push + +Found 2 agents to push +Pushing agents to Base44... + +Created: support_agent +Updated: order_bot +Deleted: old_agent + +✓ Agents pushed to Base44 +``` + +## Agent Synchronization + +The push operation synchronizes your local agents with Base44: + +- **Created**: New agents that didn't exist in Base44 +- **Updated**: Existing agents with modified configuration +- **Deleted**: Agents that were removed from your local configuration + +**Warning**: This is a full sync operation. Agents removed locally will be deleted from Base44. + +## Error Handling + +If no agents are found in your project: +```bash +$ npx base44 agents push +No local agents found - this will delete all remote agents +``` + +If an agent has an invalid name: +```bash +$ npx base44 agents push +Error: Agent name must be lowercase alphanumeric with underscores +``` + +## Agent Configuration Schema + +Each agent file should be a `.jsonc` file in `base44/agents/` with this structure: + +```jsonc +{ + "name": "agent_name", // Required: lowercase alphanumeric with underscores, 1-100 chars + "description": "Brief description of what this agent does", // Required: min 1 char + "instructions": "Detailed instructions for the agent's behavior", // Required: min 1 char + "tool_configs": [ // Optional: defaults to [] + // Entity tool - gives agent access to entity operations + { "entity_name": "Task", "allowed_operations": ["read", "create", "update", "delete"] }, + // Backend function tool - gives agent access to a function + { "function_name": "send_email", "description": "Send an email notification" } + ], + "whatsapp_greeting": "Hello! How can I help you today?" // Optional +} +``` + +**Naming rules:** +- **Agent names** must match pattern: `/^[a-z0-9_]+$/` (lowercase alphanumeric with underscores only, 1-100 characters) + - Valid: `support_agent`, `order_bot`, `task_helper` + - Invalid: `Support-Agent`, `OrderBot`, `task helper` +- **Agent file names** must use underscores (matching the agent name) + - Valid: `support_agent.jsonc`, `order_bot.jsonc` + - Invalid: `support-agent.jsonc` (hyphens not allowed) +- **Entity names in `tool_configs`** must use PascalCase (matching the entity's `name` field) + - Valid: `"entity_name": "Task"`, `"entity_name": "TeamMember"` + - Invalid: `"entity_name": "task"`, `"entity_name": "team_member"` + +**Required fields:** +- `name`: Required, must follow naming rules above +- `description`: Required, minimum 1 character +- `instructions`: Required, minimum 1 character +- `tool_configs`: Optional, defaults to empty array +- `whatsapp_greeting`: Optional + +### Common Mistake: Wrong tool_configs Format + +**WRONG** - Do NOT use `tools` with `type` and `entity`: +```jsonc +{ + "name": "my_agent", + "tools": [ // ❌ WRONG + { "type": "entity_query", "entity": "Task" } + ] +} +``` + +**CORRECT** - Use `tool_configs` with `entity_name` and `allowed_operations`: +```jsonc +{ + "name": "my_agent", + "tool_configs": [ // ✅ CORRECT + { "entity_name": "Task", "allowed_operations": ["read"] } + ] +} +``` + +### Best Practices for Agent Instructions + +When giving agents access to entities, be explicit in the instructions about using the tools: + +```jsonc +{ + "name": "support_agent", + "instructions": "You are a helpful support agent.\n\nIMPORTANT: You have access to customer data through entity tools. When users ask about their orders or account:\n1. ALWAYS use the Order entity tool to query their order history\n2. Use the Customer entity tool to look up account details\n3. Analyze the data and provide personalized responses\n\nAlways query the relevant entities first before answering questions about user data.", + "tool_configs": [ + { "entity_name": "Order", "allowed_operations": ["read"] }, + { "entity_name": "Customer", "allowed_operations": ["read"] } + ] +} +``` + +Without explicit instructions to use the entity tools, the agent may not proactively query user data when asked. + +## Use Cases + +- After defining new agents in your project +- When modifying existing agent configurations +- To sync agent changes before testing +- As part of your development workflow when agent behavior changes + +## Notes + +- This command syncs the agent configuration, not conversation data +- Changes are applied to your Base44 project immediately +- Make sure to test agent changes in a development environment first +- Agent definitions are located in the `base44/agents/` directory +- Use `base44 agents pull` to download agents from Base44 diff --git a/evals/fixtures/cli-functions/skills/base44-cli/references/auth-login.md b/evals/fixtures/cli-functions/skills/base44-cli/references/auth-login.md new file mode 100644 index 0000000..adebd27 --- /dev/null +++ b/evals/fixtures/cli-functions/skills/base44-cli/references/auth-login.md @@ -0,0 +1,54 @@ +# base44 login + +Authenticate with Base44 using device code flow. + +## Syntax + +```bash +npx base44 login +``` + +## Authentication + +**Required**: No (this is the login command itself) + +## How It Works + +The login command uses OAuth 2.0 device code flow for authentication: + +1. Generates a device code for authentication +2. Displays a verification code and verification URI +3. Directs you to visit the URI and enter the code +4. Polls for authentication completion (up to device code expiration) +5. Retrieves access and refresh tokens upon successful authentication +6. Fetches and displays your user information +7. Saves authentication data locally with expiration timestamp + +## Interactive Flow + +```bash +$ npx base44 login + +Please visit: https://auth.base44.com/device +Enter code: ABCD-EFGH + +Waiting for authentication... +✓ Successfully authenticated! + +Logged in as: user@example.com +``` + +## Session Management + +- Authentication tokens are stored locally on your device +- Tokens include expiration timestamps +- The session persists across CLI sessions +- Other commands will automatically use your stored credentials +- Use `npx base44 logout` to clear your session +- Use `npx base44 whoami` to check your current authentication status + +## Notes + +- You only need to login once per device +- If your session expires, you'll be prompted to login again when running authenticated commands +- The CLI automatically prompts for login when you run commands that require authentication diff --git a/evals/fixtures/cli-functions/skills/base44-cli/references/auth-logout.md b/evals/fixtures/cli-functions/skills/base44-cli/references/auth-logout.md new file mode 100644 index 0000000..33c2ddf --- /dev/null +++ b/evals/fixtures/cli-functions/skills/base44-cli/references/auth-logout.md @@ -0,0 +1,32 @@ +# base44 logout + +Logout from current device and clear stored authentication data. + +## Syntax + +```bash +npx base44 logout +``` + +## Authentication + +**Required**: No + +## What It Does + +- Deletes stored authentication data from your device +- Clears your local session +- Removes access and refresh tokens + +## Output + +```bash +$ npx base44 logout +Logged out successfully +``` + +## Notes + +- You can logout even if you're not currently logged in (no error) +- After logout, you'll need to run `npx base44 login` again to use authenticated commands +- This only affects the current device; your Base44 account remains active diff --git a/evals/fixtures/cli-functions/skills/base44-cli/references/auth-whoami.md b/evals/fixtures/cli-functions/skills/base44-cli/references/auth-whoami.md new file mode 100644 index 0000000..abe450c --- /dev/null +++ b/evals/fixtures/cli-functions/skills/base44-cli/references/auth-whoami.md @@ -0,0 +1,37 @@ +# base44 whoami + +Display the currently authenticated user. + +## Syntax + +```bash +npx base44 whoami +``` + +## Authentication + +**Required**: Yes. If not authenticated, you'll be prompted to login first. + +## What It Does + +- Reads stored authentication data +- Displays the email of the currently logged-in user + +## Output + +```bash +$ npx base44 whoami +Logged in as: user@example.com +``` + +## Use Cases + +- Verify you're logged in before running other commands +- Check which account you're currently using +- Confirm authentication is working properly +- Useful in scripts or automation to verify credentials + +## Notes + +- If you're not logged in, the command will prompt you to authenticate first +- The email displayed matches your Base44 account email diff --git a/evals/fixtures/cli-functions/skills/base44-cli/references/create.md b/evals/fixtures/cli-functions/skills/base44-cli/references/create.md new file mode 100644 index 0000000..7580645 --- /dev/null +++ b/evals/fixtures/cli-functions/skills/base44-cli/references/create.md @@ -0,0 +1,111 @@ +# base44 create + +Creates a new Base44 project from a template. This command is framework-agnostic and can either scaffold a complete project or add Base44 configuration to an existing project. + +## Critical: Non-Interactive Mode Required + +ALWAYS provide both the project name AND `--path` flag. Without both, the command opens an interactive TUI which agents cannot use properly. + +WRONG: `npx base44 create` +WRONG: `npx base44 create my-app` +RIGHT: `npx base44 create my-app -p ./my-app` + +## Syntax + +```bash +npx base44 create [name] --path [options] +``` + +## Arguments & Options + +| Argument/Option | Description | Required | +|--------|-------------|----------| +| `name` | Project name (positional argument) | Yes* | +| `-p, --path ` | Path where to create the project | Yes* | +| `-t, --template ` | Template ID (see templates below) | No | +| `--deploy` | Build and deploy the site (includes pushing entities) | No | +| `--no-skills` | Skip AI agent skills installation (skills are added by default) | No | + +*Required for non-interactive mode. Both `name` and `--path` must be provided together. + +## Template Selection (CRITICAL - Choose Appropriately) + +**You MUST select the most appropriate template based on user requirements:** + +| Template ID | When to Use | Example Scenarios | +|-------------|-------------|-------------------| +| `backend-and-client` | Creating a NEW full-stack web app from scratch | "Create a task app", "Build me a dashboard", "Make a SaaS app" | +| `backend-only` | Adding Base44 to an EXISTING project OR using a different framework (Next.js, Vue, Svelte, etc.) | "Add Base44 to my project", "I want to use Next.js", "I already have a frontend" | + +**Default Choice:** When the user asks to "create an app" or "build a project" without specifying a particular framework, use `backend-and-client` to provide a complete, production-ready application with Vite + React + Tailwind. + +## The `--path` Flag + +- **For `backend-and-client` template (new projects):** Use a new subfolder path + ```bash + npx base44 create my-app -p ./my-app -t backend-and-client + ``` +- **For `backend-only` template (existing projects):** Use `-p .` in the current directory + ```bash + npx base44 create my-app -p . + ``` + +## Workflow: Using `backend-only` with External Frameworks + +**CRITICAL: The project folder MUST exist BEFORE running `base44 create` with `backend-only`** + +The `backend-only` template only adds Base44 configuration files - it does NOT create a frontend. If you need a frontend with a specific framework: + +```bash +# Step 1: Initialize the frontend project FIRST +npm create vite@latest my-app -- --template react # or vue, svelte, etc. +# OR: npx create-next-app@latest my-app +# OR: any other framework's init command + +# Step 2: Navigate into the created folder +cd my-app + +# Step 3: Install Base44 CLI +npm install --save-dev base44 + +# Step 4: Add Base44 configuration +npx base44 create my-app -p . +``` + +**WARNING:** Do NOT: +- Create an empty folder manually, then try to run `npx create vite` inside it (will fail - folder exists) +- Run `base44 create` with `backend-only` expecting it to create a frontend (it won't) + +**DO:** +- Run the external framework's init command FIRST (it creates its own folder) +- Then run `base44 create` inside that folder with `-p .` + +## Examples + +```bash +# RECOMMENDED: Create full-stack project (for new apps) +npx base44 create my-app -p ./my-app -t backend-and-client + +# Create full-stack and deploy in one step +npx base44 create my-app -p ./my-app -t backend-and-client --deploy + +# Add Base44 to EXISTING project (must be inside the project folder) +npx base44 create my-app -p . + +# Add Base44 to existing project and deploy +npx base44 create my-app -p . --deploy + +# Create without adding AI agent skills +npx base44 create my-app -p . --no-skills +``` + +## What It Does + +1. Applies the selected template to the target path +2. Creates a `base44/` folder with configuration files +3. Registers the project with Base44 backend +4. Creates `base44/.app.jsonc` with the app ID +5. If `--deploy` is used: + - Pushes any entities defined in `base44/entities/` + - Runs install and build commands (for templates with frontend) + - Deploys the site to Base44 hosting diff --git a/evals/fixtures/cli-functions/skills/base44-cli/references/dashboard.md b/evals/fixtures/cli-functions/skills/base44-cli/references/dashboard.md new file mode 100644 index 0000000..95a534a --- /dev/null +++ b/evals/fixtures/cli-functions/skills/base44-cli/references/dashboard.md @@ -0,0 +1,35 @@ +# base44 dashboard + +Opens the Base44 app dashboard in your default web browser. + +## Syntax + +```bash +npx base44 dashboard +``` + +## Options + +This command has no options. + +## What It Does + +1. Reads the project's app ID from `base44/.app.jsonc` +2. Opens the dashboard URL in your default browser + +## Example + +```bash +# Open dashboard for current project +npx base44 dashboard +``` + +## Requirements + +- Must be run from a linked Base44 project directory (contains `base44/.app.jsonc`) +- Must be authenticated (run `npx base44 login` first) + +## Notes + +- The dashboard provides a web interface to manage your app's entities, functions, users, and settings +- If you're not in a project directory, the command will fail with an error diff --git a/evals/fixtures/cli-functions/skills/base44-cli/references/deploy.md b/evals/fixtures/cli-functions/skills/base44-cli/references/deploy.md new file mode 100644 index 0000000..500526f --- /dev/null +++ b/evals/fixtures/cli-functions/skills/base44-cli/references/deploy.md @@ -0,0 +1,86 @@ +# base44 deploy + +Deploys all project resources (entities, functions, and site) to Base44 in a single command. + +## Syntax + +```bash +npx base44 deploy [options] +``` + +## Options + +| Option | Description | +|--------|-------------| +| `-y, --yes` | Skip confirmation prompt | + +## What It Deploys + +The command automatically detects and deploys: + +1. **Entities** - All `.jsonc` files in `base44/entities/` +2. **Functions** - All functions in `base44/functions/` +3. **Agents** - All agent configurations in `base44/agents/` +4. **Site** - Built files from `site.outputDirectory` (if configured) + +## Examples + +```bash +# Interactive mode - shows what will be deployed and asks for confirmation +npx base44 deploy + +# Non-interactive - skip confirmation (for CI/CD or agent use) +npx base44 deploy -y +``` + +## Typical Workflow + +```bash +# 1. Make your changes (entities, functions, frontend code) + +# 2. Build the frontend (if you have one) +npm run build + +# 3. Deploy everything +npx base44 deploy -y +``` + +## What It Does + +1. Reads project configuration from `base44/config.jsonc` +2. Detects available resources (entities, functions, site) +3. Shows a summary of what will be deployed +4. Asks for confirmation (unless `-y` flag is used) +5. Deploys all resources in sequence: + - Pushes entity schemas + - Deploys functions + - Pushes agent configurations + - Uploads site files +6. Displays the dashboard URL and app URL (if site was deployed) + +## Requirements + +- Must be run from a linked Base44 project directory +- Must be authenticated (run `npx base44 login` first) +- For site deployment, must run `npm run build` first + +## Output + +After successful deployment: +- **Dashboard**: Link to your app's management dashboard +- **App URL**: Your deployed site's public URL (if site was included) + +## Notes + +- If no resources are found, the command exits with a message +- Use individual commands (`entities push`, `functions deploy`, `site deploy`) if you only want to deploy specific resources +- The site must be built before deployment - this command does not run `npm run build` for you + +## Related Commands + +| Command | Description | +|---------|-------------| +| `base44 entities push` | Push only entities | +| `base44 functions deploy` | Deploy only functions | +| `base44 agents push` | Push only agents | +| `base44 site deploy` | Deploy only the site | diff --git a/evals/fixtures/cli-functions/skills/base44-cli/references/entities-create.md b/evals/fixtures/cli-functions/skills/base44-cli/references/entities-create.md new file mode 100644 index 0000000..29b40aa --- /dev/null +++ b/evals/fixtures/cli-functions/skills/base44-cli/references/entities-create.md @@ -0,0 +1,552 @@ +# Creating Entities + +Base44 entities are defined locally in your project and then pushed to the Base44 backend. + +## Critical: File Naming + +Entity files MUST use kebab-case naming: `{kebab-case-name}.jsonc` + +| Entity Name | File Name | +|-------------|-----------| +| `Task` | `task.jsonc` | +| `TeamMember` | `team-member.jsonc` | +| `ActivityLog` | `activity-log.jsonc` | + +WRONG: `TeamMember.jsonc`, `teamMember.jsonc` +RIGHT: `team-member.jsonc` + +## Table of Contents + +- [Creating Entities](#creating-entities) + - [Entity Directory](#entity-directory) + - [How to Create an Entity](#how-to-create-an-entity) + - [Entity Schema Structure](#entity-schema-structure) + - [Supported Field Types](#supported-field-types) + - [Field Properties](#field-properties) + - [Complete Example](#complete-example) + - [Naming Conventions](#naming-conventions) + - [Relationships Between Entities](#relationships-between-entities) + - [Row Level Security (RLS)](#row-level-security-rls) + - [Field Level Security (FLS)](#field-level-security-fls) + - [Pushing Entities](#pushing-entities) + +## Entity Directory + +All entity definitions must be placed in the `base44/entities/` folder in your project root. Each entity is defined in its own `.jsonc` file. + +Example structure: +``` +my-app/ + base44/ + entities/ + user.jsonc + product.jsonc + order.jsonc +``` + +## How to Create an Entity + +1. Create a new `.jsonc` file in the `base44/entities/` directory +2. Define your entity schema following the structure below +3. Push the changes to Base44 using the CLI + +## Entity Schema Structure + +Each entity file follows a JSON Schema-like structure: + +```jsonc +{ + "name": "EntityName", // PascalCase entity name + "type": "object", // Always "object" + "properties": { + // Define your fields here + }, + "required": ["field1"] // Array of required field names +} +``` + +### Common Mistake: Nested Schema Property + +**WRONG** - Do NOT wrap properties in a `schema` object: +```jsonc +{ + "name": "Task", + "description": "A task entity", + "schema": { // ❌ WRONG - don't use nested "schema" + "type": "object", + "properties": { ... } + } +} +``` + +**CORRECT** - Put `type` and `properties` at the top level: +```jsonc +{ + "name": "Task", + "description": "A task entity", + "type": "object", // ✅ CORRECT - top level + "properties": { ... } // ✅ CORRECT - top level +} +``` + +This is a common mistake that will cause "Invalid schema: Schema must have a 'type' field" errors when pushing entities. + +## Supported Field Types + +### String + +Basic text field: +```jsonc +{ + "title": { + "type": "string", + "description": "Task title" + } +} +``` + +With format: +```jsonc +{ + "due_date": { + "type": "string", + "format": "date", + "description": "Due date" + } +} +``` + +Available formats: `date`, `date-time`, `time`, `email`, `uri`, `hostname`, `ipv4`, `ipv6`, `uuid`, `file`, `regex`, `richtext` + +### String with Enum + +Constrained to specific values: +```jsonc +{ + "status": { + "type": "string", + "enum": ["todo", "in_progress", "done"], + "default": "todo", + "description": "Current status" + } +} +``` + +### Number + +```jsonc +{ + "position": { + "type": "number", + "description": "Position for ordering" + } +} +``` + +### Integer + +For whole numbers only: +```jsonc +{ + "quantity": { + "type": "integer", + "description": "Item quantity", + "minimum": 0, + "maximum": 1000 + } +} +``` + +### Binary + +For file/blob data: +```jsonc +{ + "attachment": { + "type": "binary", + "description": "File attachment" + } +} +``` + +### Boolean + +```jsonc +{ + "notify_on_change": { + "type": "boolean", + "default": true, + "description": "Enable notifications" + } +} +``` + +### Array of Strings + +```jsonc +{ + "labels": { + "type": "array", + "items": { "type": "string" }, + "description": "Task labels/tags" + } +} +``` + +### Array of Objects + +```jsonc +{ + "attachments": { + "type": "array", + "description": "File attachments", + "items": { + "type": "object", + "properties": { + "name": { "type": "string" }, + "url": { "type": "string" }, + "type": { "type": "string" } + } + } + } +} +``` + +## Field Properties + +| Property | Description | +| ------------- | ---------------------------------------------------------------------------------------- | +| `type` | Data type: `string`, `number`, `integer`, `boolean`, `array`, `object`, `binary` | +| `description` | Human-readable description of the field | +| `enum` | Array of allowed values (for strings) | +| `enumNames` | Human-readable labels for enum values (same order as `enum`) | +| `default` | Default value when not provided | +| `format` | Format hint: `date`, `date-time`, `time`, `email`, `uri`, `hostname`, `ipv4`, `ipv6`, `uuid`, `file`, `regex`, `richtext` | +| `items` | Schema for array items | +| `properties` | Nested properties for object types | +| `$ref` | Reference to another schema definition | +| `minLength` | Minimum string length | +| `maxLength` | Maximum string length | +| `pattern` | Regex pattern for string validation | +| `minimum` | Minimum value for numbers | +| `maximum` | Maximum value for numbers | +| `rls` | Field-level security rules (see Field Level Security section) | + +## Complete Example + +Here's a complete entity definition for a Task: + +```jsonc +{ + "name": "Task", + "type": "object", + "properties": { + "title": { + "type": "string", + "description": "Task title" + }, + "description": { + "type": "string", + "description": "Task description" + }, + "status": { + "type": "string", + "enum": ["todo", "in_progress", "done"], + "default": "todo", + "description": "Current status of the task" + }, + "board_id": { + "type": "string", + "description": "Board this task belongs to" + }, + "assignee_email": { + "type": "string", + "description": "Email of assigned user" + }, + "priority": { + "type": "string", + "enum": ["low", "medium", "high"], + "default": "medium", + "description": "Task priority" + }, + "due_date": { + "type": "string", + "format": "date", + "description": "Due date" + }, + "labels": { + "type": "array", + "items": { "type": "string" }, + "description": "Task labels/tags" + } + }, + "required": ["title"] +} +``` + +## Naming Conventions + +- **Entity name**: Use PascalCase with alphanumeric characters only (e.g., `Task`, `TeamMember`, `ActivityLog`) + - Must match pattern: `/^[a-zA-Z0-9]+$/` + - Valid: `Task`, `TeamMember`, `Order123` + - Invalid: `Team_Member`, `Team-Member`, `Team Member` +- **File name**: Use kebab-case matching the entity (e.g., `task.jsonc`, `team-member.jsonc`, `activity-log.jsonc`) +- **Field names**: Use snake_case (e.g., `board_id`, `user_email`, `due_date`) + +## Relationships Between Entities + +To create relationships between entities, use ID reference fields: + +```jsonc +{ + "board_id": { + "type": "string", + "description": "Board this task belongs to" + }, + "team_id": { + "type": "string", + "description": "Associated team ID" + } +} +``` + +## Row Level Security (RLS) + +Row Level Security (RLS) controls which records users can access based on their identity and attributes. RLS rules are defined per entity inside the `rls` field of the schema. + +**Important:** If no RLS is defined, all records are accessible to all users. + +### RLS Operations + +RLS supports five operations: + +| Operation | Description | +|-----------|-------------| +| `create` | Control who can add new records | +| `read` | Control who can view records | +| `update` | Control who can modify records | +| `delete` | Control who can remove records | +| `write` | Shorthand for `create`, `update`, and `delete` combined | + +### Permission Values + +Each operation accepts one of the following values: + +1. **`true`** - Allow all users (including anonymous/unauthenticated) +2. **`false`** - Block all users +3. **Condition object** - Allow users matching the condition + +### Template Variables + +Use template variables to reference the current user's attributes: + +| Template | Description | +|----------|-------------| +| `{{user.id}}` | The user's ID | +| `{{user.email}}` | The user's email | +| `{{user.role}}` | The user's role | +| `{{user.data.field_name}}` | Custom field from the user's `data` object | + +### Built-in Entity Attributes + +Every entity record has these built-in attributes available for RLS rules: + +| Attribute | Description | +|-----------|-------------| +| `id` | Unique record identifier | +| `created_date` | Timestamp when record was created | +| `updated_date` | Timestamp when record was last updated | +| `created_by` | Email of the user who created the record | + +### Rule Types + +There are two condition types you can use: + +**1. Entity-to-user comparison** - Compare record fields to the current user's values: +```jsonc +{ + "created_by": "{{user.email}}" +} +``` + +**2. User condition check** - Check user properties directly using `user_condition`: +```jsonc +{ + "user_condition": { "role": "admin" } +} +``` + +**Important limitations:** +- `user_condition` only supports **simple equality** (e.g., `{ "role": "admin" }`) +- For `data.*` field comparisons, you can use operators: `$in`, `$nin`, `$ne`, `$all` +- Logical operators `$or`, `$and`, `$nor` are available for combining conditions +- You cannot filter by entity field values directly (e.g., `{"status": "published"}`) +- Only user-related conditions are allowed + +### RLS Examples + +**Owner-only access:** +```jsonc +{ + "created_by": "{{user.email}}" +} +``` + +**Department-based access:** +```jsonc +{ + "data.department": "{{user.data.department}}" +} +``` + +**Admin-only access:** +```jsonc +{ + "user_condition": { "role": "admin" } +} +``` + +**Complete RLS configuration:** +```jsonc +{ + "name": "Task", + "type": "object", + "properties": { + "title": { + "type": "string", + "description": "Task title" + }, + "status": { + "type": "string", + "enum": ["todo", "in_progress", "done"], + "default": "todo" + } + }, + "required": ["title"], + "rls": { + "create": true, + "read": { "created_by": "{{user.email}}" }, + "update": { "created_by": "{{user.email}}" }, + "delete": { "created_by": "{{user.email}}" } + } +} +``` + +### Common RLS Patterns + +**Public create, admin-only management (e.g., contact forms, waitlists):** +```jsonc +{ + "rls": { + "create": true, + "read": { "user_condition": { "role": "admin" } }, + "update": { "user_condition": { "role": "admin" } }, + "delete": { "user_condition": { "role": "admin" } } + } +} +``` + +**Owner-only access:** +```jsonc +{ + "rls": { + "create": true, + "read": { "created_by": "{{user.email}}" }, + "update": { "created_by": "{{user.email}}" }, + "delete": { "created_by": "{{user.email}}" } + } +} +``` + +**Logged-in users only:** +```jsonc +{ + "rls": { + "create": { "user_condition": { "id": "{{user.id}}" } }, + "read": true, + "update": { "created_by": "{{user.email}}" }, + "delete": { "created_by": "{{user.email}}" } + } +} +``` + +### Limitations + +- **user_condition is equality only:** `user_condition` only supports exact match (e.g., `{ "role": "admin" }`) - no operators +- **No comparison operators on user_condition:** `$gt`, `$lt`, `$regex`, `$expr`, `$where` are NOT supported for user conditions +- **No entity field filtering:** Cannot filter by entity field values (e.g., `{"status": "published"}`) +- **No deeply nested templates:** Templates like `{{user.data.profile.department}}` may not work + +**Supported operators:** +- **Logical operators:** `$or`, `$and`, `$nor` for combining multiple conditions +- **Field operators (for `data.*` fields only):** `$in`, `$nin`, `$ne`, `$all` + +### Complex Access Patterns + +For complex access patterns that require multiple conditions (e.g., "owner OR admin"), you have two options: + +1. **Use the Base44 Dashboard UI** - The dashboard allows adding multiple rules per operation with OR logic +2. **Use separate entities** - Split data into multiple entities with different access rules +3. **Use backend functions** - Implement custom access logic in backend functions + +## Field Level Security (FLS) + +Field Level Security allows you to control access to individual fields within an entity. FLS rules are defined within each field's schema using the `rls` property. + +### FLS Operations + +FLS supports the same operations as entity-level RLS: + +| Operation | Description | +|-----------|-------------| +| `create` | Control who can set this field when creating records | +| `read` | Control who can view this field | +| `update` | Control who can modify this field | +| `delete` | Control who can clear this field | +| `write` | Shorthand for `create`, `update`, and `delete` combined | + +### FLS Example + +```jsonc +{ + "name": "Employee", + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "Employee name" + }, + "salary": { + "type": "number", + "description": "Employee salary", + "rls": { + "read": { "user_condition": { "role": "hr" } }, + "update": { "user_condition": { "role": "hr" } } + } + }, + "department": { + "type": "string", + "description": "Department name" + } + }, + "required": ["name"] +} +``` + +In this example, only users with the `hr` role can read or update the `salary` field. All users with access to the entity can read/update other fields. + +### FLS Notes + +- If no field-level RLS is defined, the field inherits the entity-level RLS rules +- FLS rules follow the same condition format as entity-level RLS +- Use FLS for sensitive fields like salary, SSN, or internal notes + +## Pushing Entities + +The `entities push` command will push all entities that exist in the `base44/entities` folder. + +```bash +npx base44 entities push +``` + +For more details on the push command, see [entities-push.md](entities-push.md). diff --git a/evals/fixtures/cli-functions/skills/base44-cli/references/entities-push.md b/evals/fixtures/cli-functions/skills/base44-cli/references/entities-push.md new file mode 100644 index 0000000..63b92d7 --- /dev/null +++ b/evals/fixtures/cli-functions/skills/base44-cli/references/entities-push.md @@ -0,0 +1,71 @@ +# base44 entities push + +Push local entity definitions to Base44. + +## Syntax + +```bash +npx base44 entities push +``` + +## Authentication + +**Required**: Yes. If not authenticated, you'll be prompted to login first. + +## What It Does + +1. Pushes all entities that exist in the `base44/entities` folder +2. Validates that entities exist in the folder +3. Displays the count of entities to be pushed +4. Uploads entities to the Base44 backend +5. Reports the results: created, updated, and deleted entities + +## Prerequisites + +- Must be run from a Base44 project directory +- Project must have entity definitions in the `base44/entities` folder + +## Output + +```bash +$ npx base44 entities push + +Found 3 entities to push +Pushing entities to Base44... + +Created: User, Post +Updated: Comment +Deleted: OldEntity + +✓ Entities pushed successfully +``` + +## Entity Synchronization + +The push operation synchronizes your local entity schema with Base44: + +- **Created**: New entities that didn't exist in Base44 +- **Updated**: Existing entities with modified schema or configuration +- **Deleted**: Entities that were removed from your local configuration + +## Error Handling + +If no entities are found in your project: +```bash +$ npx base44 entities push +No entities found in project +``` + +## Use Cases + +- After defining new entities in your project +- When modifying existing entity schemas +- To sync entity changes before deploying +- As part of your development workflow when data models change + +## Notes + +- This command syncs the entity schema/structure, not the actual data +- Changes are applied to your Base44 project immediately +- Make sure to test entity changes in a development environment first +- Entity definitions are located in the `base44/entities/` directory diff --git a/evals/fixtures/cli-functions/skills/base44-cli/references/functions-create.md b/evals/fixtures/cli-functions/skills/base44-cli/references/functions-create.md new file mode 100644 index 0000000..c523cdc --- /dev/null +++ b/evals/fixtures/cli-functions/skills/base44-cli/references/functions-create.md @@ -0,0 +1,229 @@ +# Creating Functions + +Base44 functions are serverless backend functions that run on Deno. They are defined locally in your project and deployed to the Base44 backend. + +## Function Directory + +All function definitions must be placed in the `base44/functions/` folder in your project. Each function lives in its own subdirectory with a configuration file and entry point. + +Example structure: +``` +my-app/ + base44/ + functions/ + process-order/ + function.jsonc + index.ts + send-notification/ + function.jsonc + index.ts +``` + +## How to Create a Function + +1. Create a new directory in `base44/functions/` with your function name (use kebab-case) +2. Create a `function.jsonc` configuration file in the directory +3. Create the entry point file (e.g., `index.ts`) +4. Deploy the function using the CLI + +## Function Configuration + +Each function requires a `function.jsonc` configuration file: + +```jsonc +{ + "name": "my-function", + "entry": "index.ts" +} +``` + +### Configuration Properties + +| Property | Description | Required | +|----------|-------------|----------| +| `name` | Function name (must match `/^[^.]+$/` - no dots allowed) | Yes | +| `entry` | Entry point file path relative to the function directory (min 1 char) | Yes | + +## Entry Point File + +Functions run on Deno and must export using `Deno.serve()`. Use `npm:` prefix for npm packages. + +```typescript +import { createClientFromRequest } from "npm:@base44/sdk"; + +Deno.serve(async (req) => { + // Get authenticated client from request + const base44 = createClientFromRequest(req); + + // Parse input + const { orderId, action } = await req.json(); + + // Your logic here + const order = await base44.entities.Orders.get(orderId); + + // Return response + return Response.json({ + success: true, + order: order + }); +}); +``` + +### Request Object + +The function receives a standard Deno `Request` object: +- `req.json()` - Parse JSON body +- `req.text()` - Get raw text body +- `req.headers` - Access request headers +- `req.method` - HTTP method + +### Response Object + +Return using `Response.json()` for JSON responses: + +```typescript +// Success response +return Response.json({ data: result }); + +// Error response with status code +return Response.json({ error: "Something went wrong" }, { status: 400 }); + +// Not found +return Response.json({ error: "Order not found" }, { status: 404 }); +``` + +## Complete Example + +### Directory Structure +``` +base44/ + functions/ + process-order/ + function.jsonc + index.ts +``` + +### function.jsonc +```jsonc +{ + "name": "process-order", + "entry": "index.ts" +} +``` + +### index.ts +```typescript +import { createClientFromRequest } from "npm:@base44/sdk"; + +Deno.serve(async (req) => { + try { + const base44 = createClientFromRequest(req); + const { orderId } = await req.json(); + + // Validate input + if (!orderId) { + return Response.json( + { error: "Order ID is required" }, + { status: 400 } + ); + } + + // Fetch and process the order + const order = await base44.entities.Orders.get(orderId); + if (!order) { + return Response.json( + { error: "Order not found" }, + { status: 404 } + ); + } + + return Response.json({ + success: true, + orderId: order.id, + processedAt: new Date().toISOString() + }); + + } catch (error) { + return Response.json( + { error: error.message }, + { status: 500 } + ); + } +}); +``` + +## Using Service Role Access + +For admin-level operations, use `asServiceRole`: + +```typescript +import { createClientFromRequest } from "npm:@base44/sdk"; + +Deno.serve(async (req) => { + const base44 = createClientFromRequest(req); + + // Check user is authenticated + const user = await base44.auth.me(); + if (!user) { + return Response.json({ error: "Unauthorized" }, { status: 401 }); + } + + // Use service role for admin operations + const allOrders = await base44.asServiceRole.entities.Orders.list(); + + return Response.json({ orders: allOrders }); +}); +``` + +## Using Secrets + +Access environment variables configured in the app dashboard: + +```typescript +Deno.serve(async (req) => { + // Access environment variables (configured in app settings) + const apiKey = Deno.env.get("STRIPE_API_KEY"); + + const response = await fetch("https://api.stripe.com/v1/charges", { + headers: { + "Authorization": `Bearer ${apiKey}` + } + }); + + return Response.json(await response.json()); +}); +``` + +## Naming Conventions + +- **Directory name**: Use kebab-case (e.g., `process-order`, `send-notification`) +- **Function name**: Match the directory name, must match pattern `/^[^.]+$/` (no dots allowed) + - Valid: `process-order`, `send_notification`, `myFunction` + - Invalid: `process.order`, `send.notification.v2` +- **Entry file**: Typically `index.ts` or `index.js` + +## Deploying Functions + +After creating your function, deploy it to Base44: + +```bash +npx base44 functions deploy +``` + +For more details on deploying, see [functions-deploy.md](functions-deploy.md). + +## Notes + +- Functions run on Deno runtime, not Node.js +- Use `npm:` prefix for npm packages (e.g., `npm:@base44/sdk`) +- Use `createClientFromRequest(req)` to get a client that inherits the caller's auth context +- Configure secrets via app dashboard for API keys +- Make sure to handle errors gracefully and return appropriate HTTP status codes + +## Common Mistakes + +| Wrong | Correct | Why | +|-------|---------|-----| +| `functions/myFunction.js` (single file) | `functions/my-function/index.ts` + `function.jsonc` | Functions require subdirectory with config | +| `import { ... } from "@base44/sdk"` | `import { ... } from "npm:@base44/sdk"` | Deno requires `npm:` prefix for npm packages | +| `MyFunction` or `myFunction` directory | `my-function` directory | Use kebab-case for directory names | diff --git a/evals/fixtures/cli-functions/skills/base44-cli/references/functions-deploy.md b/evals/fixtures/cli-functions/skills/base44-cli/references/functions-deploy.md new file mode 100644 index 0000000..f89d796 --- /dev/null +++ b/evals/fixtures/cli-functions/skills/base44-cli/references/functions-deploy.md @@ -0,0 +1,78 @@ +# base44 functions deploy + +Deploy local function definitions to Base44. + +## Syntax + +```bash +npx base44 functions deploy +``` + +## Authentication + +**Required**: Yes. If not authenticated, you'll be prompted to login first. + +## What It Does + +1. Scans the `base44/functions/` directory for function definitions +2. Validates that functions exist and have valid configurations +3. Displays the count of functions to be deployed +4. Uploads function code and configuration to Base44 +5. Reports the results: deployed and deleted functions + +## Prerequisites + +- Must be run from a Base44 project directory +- Project must have function definitions in the `base44/functions/` folder +- Each function must have a valid `function.jsonc` config file + +## Output + +```bash +$ npx base44 functions deploy + +Found 2 functions to deploy +Deploying functions to Base44... + +Deployed: process-order, send-notification +Deleted: old-function + +✓ Functions deployed successfully +``` + +## Function Synchronization + +The deploy operation synchronizes your local functions with Base44: + +- **Deployed**: Functions that were created or updated +- **Deleted**: Functions that were removed from your local configuration + +## Error Handling + +If no functions are found in your project: +```bash +$ npx base44 functions deploy +No functions found. Create functions in the 'functions' directory. +``` + +If a function has configuration errors: +```bash +$ npx base44 functions deploy +Function deployment errors: +'my-function' function: Entry point cannot be empty +``` + +## Use Cases + +- After creating new functions in your project +- When modifying existing function code or configuration +- To sync function changes before testing +- As part of your development workflow when backend logic changes + +## Notes + +- This command deploys the function code and configuration +- Changes are applied to your Base44 project immediately +- Make sure to test functions in a development environment first +- Function definitions are located in the `base44/functions/` directory +- For how to create functions, see [functions-create.md](functions-create.md) diff --git a/evals/fixtures/cli-functions/skills/base44-cli/references/link.md b/evals/fixtures/cli-functions/skills/base44-cli/references/link.md new file mode 100644 index 0000000..0b009ce --- /dev/null +++ b/evals/fixtures/cli-functions/skills/base44-cli/references/link.md @@ -0,0 +1,81 @@ +# base44 link + +Links an existing local Base44 project to a Base44 app in the cloud. Use this when you have a `base44/config.jsonc` but haven't connected it to a Base44 app yet. + +## Critical: When to Use Link vs Create + +| Scenario | Command | +|----------|---------| +| Starting fresh, no `base44/` folder | `npx base44 create` | +| Have `base44/config.jsonc` but no `.app.jsonc` | `npx base44 link` | +| Project already linked (has `.app.jsonc`) | Already done, use `deploy` | + +## Syntax + +```bash +npx base44 link [options] +``` + +## Options + +| Option | Description | Required | +|--------|-------------|----------| +| `-c, --create` | Create a new project (skip selection prompt) | No | +| `-n, --name ` | Project name (required when `--create` is used) | With `--create` | +| `-d, --description ` | Project description | No | +| `-p, --projectId ` | Project ID to link to an existing project (skip selection prompt) | No | + +## Non-Interactive Mode + +For CI/CD or agent use: + +**Create a new project:** +```bash +npx base44 link --create --name my-app +``` + +**Link to an existing project:** +```bash +npx base44 link --projectId +``` + +WRONG: `npx base44 link --create` (missing --name) +WRONG: `npx base44 link --create --projectId ` (cannot use both) +RIGHT: `npx base44 link --create --name my-app` +RIGHT: `npx base44 link --projectId ` + +## Examples + +```bash +# Interactive mode - prompts for project details +npx base44 link + +# Non-interactive - create and link in one step +npx base44 link --create --name my-app + +# With description +npx base44 link --create --name my-app --description "My awesome app" + +# Link to a specific existing project by ID +npx base44 link --projectId abc123 +``` + +## What It Does + +1. Finds the `base44/config.jsonc` in the current directory (or parent directories) +2. Verifies no `.app.jsonc` exists (project not already linked) +3. Either: + - Creates a new Base44 app in the cloud (with `--create`), OR + - Links to an existing project (with `--projectId` or interactive selection) +4. Writes the app ID to `base44/.app.jsonc` + +## Requirements + +- Must have `base44/config.jsonc` in the project +- Must NOT have `base44/.app.jsonc` (use `deploy` if already linked) +- Must be authenticated (run `npx base44 login` first) + +## Notes + +- After linking, you can deploy resources with `npx base44 deploy` +- The `.app.jsonc` file should be git-ignored (contains your app ID) diff --git a/evals/fixtures/cli-functions/skills/base44-cli/references/rls-examples.md b/evals/fixtures/cli-functions/skills/base44-cli/references/rls-examples.md new file mode 100644 index 0000000..7d3ef42 --- /dev/null +++ b/evals/fixtures/cli-functions/skills/base44-cli/references/rls-examples.md @@ -0,0 +1,463 @@ +# RLS Examples + +Practical Row-Level Security patterns for common application types. + +**Important:** Base44 RLS supports: +- **Logical operators:** `$or`, `$and`, `$nor` for combining conditions +- **Field operators (for `data.*` fields):** `$in`, `$nin`, `$ne`, `$all` +- **user_condition:** Equality only (no operators) + +## Contents +- [Simple Patterns (JSON Schema)](#simple-patterns-json-schema) +- [Using Operators](#using-operators) +- [Field-Level Security Examples](#field-level-security-examples) +- [Complex Patterns (Dashboard UI or Backend)](#complex-patterns-dashboard-ui-or-backend) +- [Best Practices](#best-practices) + +--- + +## Simple Patterns (JSON Schema) + +These patterns work with the JSON schema RLS format. + +### Todo App - Owner-only access + +Users see and manage only their own tasks. + +```jsonc +{ + "name": "Task", + "type": "object", + "properties": { + "title": { "type": "string" }, + "description": { "type": "string" }, + "completed": { "type": "boolean" }, + "priority": { "type": "string", "enum": ["low", "medium", "high"] }, + "due_date": { "type": "string", "format": "date" } + }, + "rls": { + "create": true, + "read": { "created_by": "{{user.email}}" }, + "update": { "created_by": "{{user.email}}" }, + "delete": { "created_by": "{{user.email}}" } + } +} +``` + +### Contact Form - Public create, admin-only read + +Anyone can submit, only admins can view submissions. + +```jsonc +{ + "name": "ContactSubmission", + "type": "object", + "properties": { + "name": { "type": "string" }, + "email": { "type": "string", "format": "email" }, + "message": { "type": "string" } + }, + "rls": { + "create": true, + "read": { "user_condition": { "role": "admin" } }, + "update": { "user_condition": { "role": "admin" } }, + "delete": { "user_condition": { "role": "admin" } } + } +} +``` + +### User Profile - Self-management + +Users can only access their own profile. + +```jsonc +{ + "name": "UserProfile", + "type": "object", + "properties": { + "name": { "type": "string" }, + "avatar_url": { "type": "string" }, + "bio": { "type": "string" }, + "preferences": { "type": "object" } + }, + "rls": { + "create": true, + "read": { "created_by": "{{user.email}}" }, + "update": { "created_by": "{{user.email}}" }, + "delete": { "created_by": "{{user.email}}" } + } +} +``` + +### Department Data - Same department access + +Users can only see records from their department. + +```jsonc +{ + "name": "DepartmentAnnouncement", + "type": "object", + "properties": { + "title": { "type": "string" }, + "content": { "type": "string" }, + "department": { "type": "string" } + }, + "rls": { + "create": { "user_condition": { "role": "manager" } }, + "read": { "data.department": "{{user.data.department}}" }, + "update": { "user_condition": { "role": "manager" } }, + "delete": { "user_condition": { "role": "admin" } } + } +} +``` + +### Subscription - Admin-managed, user-readable via email field + +```jsonc +{ + "name": "Subscription", + "type": "object", + "properties": { + "user_email": { "type": "string" }, + "tier": { "type": "string", "enum": ["free", "basic", "pro", "enterprise"] }, + "credits": { "type": "number" }, + "renewal_date": { "type": "string", "format": "date" } + }, + "rls": { + "create": { "user_condition": { "role": "admin" } }, + "read": { "data.user_email": "{{user.email}}" }, + "update": { "user_condition": { "role": "admin" } }, + "delete": { "user_condition": { "role": "admin" } } + } +} +``` + +**Note:** This pattern only allows users to read their own subscription. Admins need to use the Dashboard UI to configure additional read access for themselves. + +### Private Data - Owner-only + +```jsonc +{ + "name": "PrivateNotes", + "type": "object", + "properties": { + "title": { "type": "string" }, + "content": { "type": "string" }, + "tags": { "type": "array", "items": { "type": "string" } } + }, + "rls": { + "create": true, + "read": { "created_by": "{{user.email}}" }, + "update": { "created_by": "{{user.email}}" }, + "delete": { "created_by": "{{user.email}}" } + } +} +``` + +### Public Read, Authenticated Write + +Anyone can read, only logged-in users can create/edit their own records. + +```jsonc +{ + "name": "BlogPost", + "type": "object", + "properties": { + "title": { "type": "string" }, + "content": { "type": "string" }, + "author_email": { "type": "string" } + }, + "rls": { + "create": true, + "read": true, + "update": { "created_by": "{{user.email}}" }, + "delete": { "created_by": "{{user.email}}" } + } +} +``` + +--- + +## Using Operators + +### Logical Operators + +Combine multiple conditions using `$or`, `$and`, or `$nor`: + +**Owner OR Admin access:** +```jsonc +{ + "name": "Document", + "type": "object", + "properties": { + "title": { "type": "string" }, + "content": { "type": "string" } + }, + "rls": { + "create": true, + "read": { + "$or": [ + { "created_by": "{{user.email}}" }, + { "user_condition": { "role": "admin" } } + ] + }, + "update": { + "$or": [ + { "created_by": "{{user.email}}" }, + { "user_condition": { "role": "admin" } } + ] + }, + "delete": { "user_condition": { "role": "admin" } } + } +} +``` + +**Multiple roles with $or:** +```jsonc +{ + "rls": { + "read": { + "$or": [ + { "user_condition": { "role": "admin" } }, + { "user_condition": { "role": "manager" } }, + { "user_condition": { "role": "hr" } } + ] + } + } +} +``` + +### Field Operators for data.* Fields + +Use `$in`, `$nin`, `$ne`, `$all` for comparing entity data fields: + +**Access based on tags ($in):** +```jsonc +{ + "rls": { + "read": { + "data.category": { "$in": ["public", "shared"] } + } + } +} +``` + +**Exclude specific statuses ($nin):** +```jsonc +{ + "rls": { + "read": { + "data.status": { "$nin": ["archived", "deleted"] } + } + } +} +``` + +**Not equal ($ne):** +```jsonc +{ + "rls": { + "read": { + "data.visibility": { "$ne": "private" } + } + } +} +``` + +**All tags must match ($all):** +```jsonc +{ + "rls": { + "read": { + "data.required_tags": { "$all": ["approved", "reviewed"] } + } + } +} +``` + +### Combining Logical and Field Operators + +```jsonc +{ + "rls": { + "read": { + "$and": [ + { "data.status": { "$ne": "draft" } }, + { + "$or": [ + { "created_by": "{{user.email}}" }, + { "data.visibility": "public" } + ] + } + ] + } + } +} +``` + +--- + +## Field-Level Security Examples + +Control access to specific fields within an entity. + +### Sensitive Salary Field + +```jsonc +{ + "name": "Employee", + "type": "object", + "properties": { + "name": { "type": "string" }, + "email": { "type": "string", "format": "email" }, + "salary": { + "type": "number", + "description": "Annual salary", + "rls": { + "read": { "user_condition": { "role": "hr" } }, + "write": { "user_condition": { "role": "hr" } } + } + }, + "performance_notes": { + "type": "string", + "description": "Manager notes", + "rls": { + "read": { + "$or": [ + { "user_condition": { "role": "manager" } }, + { "user_condition": { "role": "hr" } } + ] + }, + "write": { "user_condition": { "role": "manager" } } + } + } + } +} +``` + +### Admin-Only Internal Fields + +```jsonc +{ + "name": "Order", + "type": "object", + "properties": { + "order_number": { "type": "string" }, + "total": { "type": "number" }, + "internal_notes": { + "type": "string", + "description": "Internal processing notes", + "rls": { + "read": { "user_condition": { "role": "admin" } }, + "write": { "user_condition": { "role": "admin" } } + } + }, + "profit_margin": { + "type": "number", + "description": "Profit margin percentage", + "rls": { + "read": { "user_condition": { "role": "admin" } }, + "write": false + } + } + } +} +``` + +--- + +## Complex Patterns (Dashboard UI or Backend) + +Some patterns may still require the Dashboard UI or backend functions. + +### Bidirectional Relationships (e.g., Friendships, Matches) + +**Requirement:** Either party in a relationship should have access. + +**Now possible with $or:** +```jsonc +{ + "rls": { + "read": { + "$or": [ + { "data.user_a_email": "{{user.email}}" }, + { "data.user_b_email": "{{user.email}}" } + ] + } + } +} +``` + +**Alternative solutions:** +1. **Entity redesign:** Store two records per relationship (one for each party) +2. **Backend function:** Query with custom logic + +### Complex Business Logic + +**Requirement:** Access depends on multiple entity fields with complex conditions. + +**JSON Schema limitation:** While operators help, very complex business logic may still be hard to express. + +**Solution options:** +1. **Backend function:** Implement custom access logic +2. **Combine simpler rules:** Break complex rules into simpler entity-level and field-level rules + +--- + +## Best Practices + +### Security Strategy + +Use a combination of entity-level RLS and field-level security: + +| Data Type | Approach | Example | +|-----------|----------|---------| +| User-editable | Entity RLS: Owner-only | UserProfile with `created_by` check | +| Sensitive fields | Field-level RLS | Salary field with HR role check | +| Multi-role access | `$or` with user_condition | Admin OR Manager access | +| Conditional access | Field operators | `$in`, `$ne` on data fields | +| Public content | Entity RLS: `read: true` | PublicPost | +| Private content | Entity RLS: Owner-only | PrivateNote | + +### When to Use Each Approach + +| Requirement | Approach | +|-------------|----------| +| Single condition (owner, admin, department) | JSON Schema RLS | +| Multiple OR/AND conditions | JSON Schema RLS with `$or`/`$and` | +| Field value checks with `$in`/`$ne`/etc. | JSON Schema RLS for `data.*` fields | +| Field-level access control | JSON Schema FLS (field-level `rls`) | +| Complex comparison operators (`$gt`, `$lt`) | Backend functions | +| Very complex business logic | Backend functions | + +### Common Role Patterns + +| Role | Typical Access | +|------|----------------| +| `admin` | Full access to all records | +| `moderator` | Read/update access, limited delete | +| `manager` | Department-scoped access | +| `user` | Own records only | + +### Supported Operators Summary + +| Operator | Supported | Notes | +|----------|-----------|-------| +| `$or` | Yes | Combine multiple conditions | +| `$and` | Yes | All conditions must match | +| `$nor` | Yes | None of the conditions match | +| `$in` | Yes | For `data.*` fields only | +| `$nin` | Yes | For `data.*` fields only | +| `$ne` | Yes | For `data.*` fields only | +| `$all` | Yes | For `data.*` fields only | +| `$gt`, `$lt`, `$gte`, `$lte` | No | Use backend functions | +| `$regex` | No | Use backend functions | + +### Limitations Summary + +| Not Supported | Alternative | +|---------------|-------------| +| Operators on `user_condition` | Use equality only for user checks | +| Comparison operators (`$gt`, `$lt`) | Backend functions | +| Regex matching (`$regex`) | Backend functions | +| Cross-entity relationships | Backend functions | diff --git a/evals/fixtures/cli-functions/skills/base44-cli/references/site-deploy.md b/evals/fixtures/cli-functions/skills/base44-cli/references/site-deploy.md new file mode 100644 index 0000000..929d302 --- /dev/null +++ b/evals/fixtures/cli-functions/skills/base44-cli/references/site-deploy.md @@ -0,0 +1,118 @@ +# base44 site deploy + +Deploy built site files to Base44 hosting. + +## Table of Contents + +- [Syntax](#syntax) +- [Authentication](#authentication) +- [Prerequisites](#prerequisites) +- [How It Works](#how-it-works) +- [Interactive Flow](#interactive-flow) +- [Typical Workflow](#typical-workflow) +- [Configuration](#configuration) +- [Error Handling](#error-handling) +- [Use Cases](#use-cases) +- [Notes](#notes) + +## Syntax + +```bash +npx base44 site deploy [options] +``` + +## Options + +| Option | Description | +| ------------ | ------------------------- | +| `-y, --yes` | Skip confirmation prompt | + +Use `-y` flag for non-interactive/automated deployments: + +```bash +npx base44 site deploy -y +``` + +## Authentication + +**Required**: Yes. If not authenticated, you'll be prompted to login first. + +## Prerequisites + +- Must be run from a Base44 project directory +- Project must have `site.outputDirectory` configured in project config +- Site must be built before deploying (run your build command first) +- **SPA only**: Base44 hosting supports Single Page Applications with a single `index.html` entry point. All routes are served from `index.html` (client-side routing). + +## How It Works + +1. Reads project configuration +2. Validates that site configuration exists +3. Prompts for deployment confirmation showing the output directory +4. Creates an archive of site files from the output directory +5. Deploys to Base44 hosting +6. Returns the app URL + +## Interactive Flow + +```bash +$ npx base44 site deploy + +Deploy site from ./dist? (yes/no) yes + +Creating archive... +Uploading to Base44... +Deploying... + +✓ Deployment successful! + +Visit your site at: https://my-app.base44.app +``` + +## Typical Workflow + +```bash +# 1. Build your site using your framework's build command +npm run build + +# 2. Deploy to Base44 +npx base44 site deploy +``` + +## Configuration + +The `site.outputDirectory` in your project configuration should point to where your framework outputs built files: + +- Vite: typically `./dist` +- Next.js: typically `./.next` or `./out` +- Create React App: typically `./build` +- Custom: whatever your build tool outputs to + +## Error Handling + +If site configuration is missing: +```bash +$ npx base44 site deploy +Error: No site configuration found in project +``` + +If you cancel the deployment: +```bash +Deploy site from ./dist? (yes/no) no +Deployment cancelled +``` + +## Use Cases + +- Deploy your site after making changes +- Push new versions of your application +- Deploy after updating content or functionality +- Part of your CI/CD pipeline + +## Notes + +- Always build your site before deploying +- The command deploys whatever is in your output directory +- Make sure your build completed successfully before deploying +- Previous deployments are preserved (versioned) in Base44 +- Deployment is immediate and updates your live site diff --git a/evals/fixtures/cli-functions/skills/base44-sdk/SKILL.md b/evals/fixtures/cli-functions/skills/base44-sdk/SKILL.md new file mode 100644 index 0000000..01ddd7f --- /dev/null +++ b/evals/fixtures/cli-functions/skills/base44-sdk/SKILL.md @@ -0,0 +1,296 @@ +--- +name: base44-sdk +description: "The base44 SDK is the library to communicate with base44 services. In projects, you use it to communicate with remote resources (entities, backend functions, ai agents) and to write backend functions. This skill is the place for learning about available modules and types. When you plan or implement a feature, you must learn this skill" +--- + +# Base44 Coder + +Build apps on the Base44 platform using the Base44 JavaScript SDK. + +## ⚡ IMMEDIATE ACTION REQUIRED - Read This First + +This skill activates on ANY mention of "base44" or when a `base44/` folder exists. **DO NOT read documentation files or search the web before acting.** + +**Your first action MUST be:** +1. Check if `base44/config.jsonc` exists in the current directory +2. If **YES** (existing project scenario): + - This skill (base44-sdk) handles the request + - Implement features using Base44 SDK + - Do NOT use base44-cli unless user explicitly requests CLI commands +3. If **NO** (new project scenario): + - Transfer to base44-cli skill for project initialization + - This skill cannot help until project is initialized + +## When to Use This Skill vs base44-cli + +**Use base44-sdk when:** +- Building features in an **EXISTING** Base44 project +- `base44/config.jsonc` already exists in the project +- Base44 SDK imports are present (`@base44/sdk`) +- Writing JavaScript/TypeScript code using Base44 SDK modules +- Implementing functionality, components, or features +- User mentions: "implement", "build a feature", "add functionality", "write code for" +- User says "create a [type] app" **and** a Base44 project already exists + +**DO NOT USE base44-sdk for:** +- ❌ Initializing new Base44 projects (use `base44-cli` instead) +- ❌ Empty directories without Base44 configuration +- ❌ When user says "create a new Base44 project/app/site" and no project exists +- ❌ CLI commands like `npx base44 create`, `npx base44 deploy`, `npx base44 login` (use `base44-cli`) + +**Skill Dependencies:** +- `base44-sdk` assumes a Base44 project is **already initialized** +- `base44-cli` is a **prerequisite** for `base44-sdk` in new projects +- If user wants to "create an app" and no Base44 project exists, use `base44-cli` first + +**State Check Logic:** +Before selecting this skill, verify: +- IF (user mentions "create/build app" OR "make a project"): + - IF (directory is empty OR no `base44/config.jsonc` exists): + → Use **base44-cli** (project initialization needed) + - ELSE: + → Use **base44-sdk** (project exists, build features) + +## Quick Start + +```javascript +// In Base44-generated apps, base44 client is pre-configured and available + +// CRUD operations +const task = await base44.entities.Task.create({ title: "New task", status: "pending" }); +const tasks = await base44.entities.Task.list(); +await base44.entities.Task.update(task.id, { status: "done" }); + +// Get current user +const user = await base44.auth.me(); +``` + +```javascript +// External apps +import { createClient } from "@base44/sdk"; + +// IMPORTANT: Use 'appId' (NOT 'clientId' or 'id') +const base44 = createClient({ appId: "your-app-id" }); +await base44.auth.loginViaEmailPassword("user@example.com", "password"); +``` + +## ⚠️ CRITICAL: Do Not Hallucinate APIs + +**Before writing ANY Base44 code, verify method names against this table or [QUICK_REFERENCE.md](references/QUICK_REFERENCE.md).** + +Base44 SDK has unique method names. Do NOT assume patterns from Firebase, Supabase, or other SDKs. + +### Authentication - WRONG vs CORRECT + +| ❌ WRONG (hallucinated) | ✅ CORRECT | +|------------------------|-----------| +| `signInWithGoogle()` | `loginWithProvider('google')` | +| `signInWithProvider('google')` | `loginWithProvider('google')` | +| `auth.google()` | `loginWithProvider('google')` | +| `signInWithEmailAndPassword(email, pw)` | `loginViaEmailPassword(email, pw)` | +| `signIn(email, pw)` | `loginViaEmailPassword(email, pw)` | +| `createUser()` / `signUp()` | `register({email, password})` | +| `onAuthStateChanged()` | `me()` (no listener, call when needed) | +| `currentUser` | `await auth.me()` | + +### Functions - WRONG vs CORRECT + +| ❌ WRONG (hallucinated) | ✅ CORRECT | +|------------------------|-----------| +| `functions.call('name', data)` | `functions.invoke('name', data)` | +| `functions.run('name', data)` | `functions.invoke('name', data)` | +| `callFunction('name', data)` | `functions.invoke('name', data)` | +| `httpsCallable('name')(data)` | `functions.invoke('name', data)` | + +### Integrations - WRONG vs CORRECT + +| ❌ WRONG (hallucinated) | ✅ CORRECT | +|------------------------|-----------| +| `ai.generate(prompt)` | `integrations.Core.InvokeLLM({prompt})` | +| `openai.chat(prompt)` | `integrations.Core.InvokeLLM({prompt})` | +| `llm(prompt)` | `integrations.Core.InvokeLLM({prompt})` | +| `sendEmail(to, subject, body)` | `integrations.Core.SendEmail({to, subject, body})` | +| `email.send()` | `integrations.Core.SendEmail({to, subject, body})` | +| `uploadFile(file)` | `integrations.Core.UploadFile({file})` | +| `storage.upload(file)` | `integrations.Core.UploadFile({file})` | + +### Entities - WRONG vs CORRECT + +| ❌ WRONG (hallucinated) | ✅ CORRECT | +|------------------------|-----------| +| `entities.Task.find({...})` | `entities.Task.filter({...})` | +| `entities.Task.findOne(id)` | `entities.Task.get(id)` | +| `entities.Task.insert(data)` | `entities.Task.create(data)` | +| `entities.Task.remove(id)` | `entities.Task.delete(id)` | +| `entities.Task.onChange(cb)` | `entities.Task.subscribe(cb)` | + +## SDK Modules + +| Module | Purpose | Reference | +|--------|---------|-----------| +| `entities` | CRUD operations on data models | [entities.md](references/entities.md) | +| `auth` | Login, register, user management | [auth.md](references/auth.md) | +| `agents` | AI conversations and messages | [base44-agents.md](references/base44-agents.md) | +| `functions` | Backend function invocation | [functions.md](references/functions.md) | +| `integrations` | AI, email, file uploads, custom APIs | [integrations.md](references/integrations.md) | +| `connectors` | OAuth tokens (service role only) | [connectors.md](references/connectors.md) | +| `analytics` | Track custom events and user activity | [analytics.md](references/analytics.md) | +| `appLogs` | Log user activity in app | [app-logs.md](references/app-logs.md) | +| `users` | Invite users to the app | [users.md](references/users.md) | + +For client setup and authentication modes, see [client.md](references/client.md). + +**TypeScript Support:** Each reference file includes a "Type Definitions" section with TypeScript interfaces and types for the module's methods, parameters, and return values. + +## Installation + +Install the Base44 SDK: + +```bash +npm install @base44/sdk +``` + +**Important:** Never assume or hardcode the `@base44/sdk` package version. Always install without a version specifier to get the latest version. + +## Creating a Client (External Apps) + +When creating a client in external apps, **ALWAYS use `appId` as the parameter name**: + +```javascript +import { createClient } from "@base44/sdk"; + +// ✅ CORRECT +const base44 = createClient({ appId: "your-app-id" }); + +// ❌ WRONG - Do NOT use these: +// const base44 = createClient({ clientId: "your-app-id" }); // WRONG +// const base44 = createClient({ id: "your-app-id" }); // WRONG +``` + +**Required parameter:** `appId` (string) - Your Base44 application ID + +**Optional parameters:** +- `token` (string) - Pre-authenticated user token +- `options` (object) - Configuration options + - `options.onError` (function) - Global error handler + +**Example with error handler:** +```javascript +const base44 = createClient({ + appId: "your-app-id", + options: { + onError: (error) => { + console.error("Base44 error:", error); + } + } +}); +``` + +## Module Selection + +**Working with app data?** +- Create/read/update/delete records → `entities` +- Import data from file → `entities.importEntities()` +- Realtime updates → `entities.EntityName.subscribe()` + +**User management?** +- Login/register/logout → `auth` +- Get current user → `auth.me()` +- Update user profile → `auth.updateMe()` +- Invite users → `users.inviteUser()` + +**AI features?** +- Chat with AI agents → `agents` (requires logged-in user) +- Create new conversation → `agents.createConversation()` +- Manage conversations → `agents.getConversations()` +- Generate text/JSON with AI → `integrations.Core.InvokeLLM()` +- Generate images → `integrations.Core.GenerateImage()` + +**Custom backend logic?** +- Run server-side code → `functions.invoke()` +- Need admin access → `base44.asServiceRole.functions.invoke()` + +**External services?** +- Send emails → `integrations.Core.SendEmail()` +- Upload files → `integrations.Core.UploadFile()` +- Custom APIs → `integrations.custom.call()` +- OAuth tokens (Google, Slack) → `connectors` (backend only) + +**Tracking and analytics?** +- Track custom events → `analytics.track()` +- Log page views/activity → `appLogs.logUserInApp()` + +## Common Patterns + +### Filter and Sort Data + +```javascript +const pendingTasks = await base44.entities.Task.filter( + { status: "pending", assignedTo: userId }, // query + "-created_date", // sort (descending) + 10, // limit + 0 // skip +); +``` + +### Protected Routes (check auth) + +```javascript +const user = await base44.auth.me(); +if (!user) { + // Navigate to your custom login page + navigate('/login', { state: { returnTo: window.location.pathname } }); + return; +} +``` + +### Backend Function Call + +```javascript +// Frontend +const result = await base44.functions.invoke("processOrder", { + orderId: "123", + action: "ship" +}); + +// Backend function (Deno) +import { createClientFromRequest } from "npm:@base44/sdk"; + +Deno.serve(async (req) => { + const base44 = createClientFromRequest(req); + const { orderId, action } = await req.json(); + // Process with service role for admin access + const order = await base44.asServiceRole.entities.Orders.get(orderId); + return Response.json({ success: true }); +}); +``` + +### Service Role Access + +Use `asServiceRole` in backend functions for admin-level operations: + +```javascript +// User mode - respects permissions +const myTasks = await base44.entities.Task.list(); + +// Service role - full access (backend only) +const allTasks = await base44.asServiceRole.entities.Task.list(); +const token = await base44.asServiceRole.connectors.getAccessToken("slack"); +``` + +## Frontend vs Backend + +| Capability | Frontend | Backend | +|------------|----------|---------| +| `entities` (user's data) | Yes | Yes | +| `auth` | Yes | Yes | +| `agents` | Yes | Yes | +| `functions.invoke()` | Yes | Yes | +| `integrations` | Yes | Yes | +| `analytics` | Yes | Yes | +| `appLogs` | Yes | Yes | +| `users` | Yes | Yes | +| `asServiceRole.*` | No | Yes | +| `connectors` | No | Yes | + +Backend functions use `Deno.serve()` and `createClientFromRequest(req)` to get a properly authenticated client. diff --git a/evals/fixtures/cli-functions/skills/base44-sdk/references/QUICK_REFERENCE.md b/evals/fixtures/cli-functions/skills/base44-sdk/references/QUICK_REFERENCE.md new file mode 100644 index 0000000..d357384 --- /dev/null +++ b/evals/fixtures/cli-functions/skills/base44-sdk/references/QUICK_REFERENCE.md @@ -0,0 +1,155 @@ +# Base44 SDK Quick Reference + +Compact method signatures for all SDK modules. **Verify against this before writing code.** + +--- + +## Auth (`base44.auth.*`) + +``` +loginViaEmailPassword(email, password, turnstileToken?) → Promise<{access_token, user}> +loginWithProvider('google' | 'microsoft' | 'facebook', fromUrl?) → void +me() → Promise +updateMe(data) → Promise +isAuthenticated() → Promise +logout(redirectUrl?) → void +redirectToLogin(nextUrl) → void # ⚠️ Avoid - prefer custom login UI +register({email, password, turnstile_token?, referral_code?}) → Promise +verifyOtp({email, otpCode}) → Promise +resendOtp(email) → Promise +inviteUser(userEmail, role) → Promise +resetPasswordRequest(email) → Promise +resetPassword({resetToken, newPassword}) → Promise +changePassword({userId, currentPassword, newPassword}) → Promise +setToken(token, saveToStorage?) → void +``` + +--- + +## Entities (`base44.entities.EntityName.*`) + +``` +create(data) → Promise +bulkCreate(dataArray) → Promise +list(sort?, limit?, skip?, fields?) → Promise +filter(query, sort?, limit?, skip?, fields?) → Promise +get(id) → Promise +update(id, data) → Promise +delete(id) → Promise +deleteMany(query) → Promise +importEntities(file) → Promise // frontend only +subscribe(callback) → () => void // returns unsubscribe fn +``` + +**Sort:** Use `-fieldName` for descending (e.g., `-created_date`) + +--- + +## Functions (`base44.functions.*`) + +``` +invoke(functionName, data) → Promise +``` + +**Backend:** Use `base44.asServiceRole.functions.invoke()` for admin access. + +--- + +## Integrations (`base44.integrations.Core.*`) + +``` +InvokeLLM({prompt, add_context_from_internet?, response_json_schema?, file_urls?}) → Promise +GenerateImage({prompt}) → Promise<{url}> +SendEmail({to, subject, body, from_name?}) → Promise +UploadFile({file}) → Promise<{file_url}> +UploadPrivateFile({file}) → Promise<{file_uri}> +CreateFileSignedUrl({file_uri, expires_in?}) → Promise<{signed_url}> +ExtractDataFromUploadedFile({file_url, json_schema}) → Promise +``` + +### Custom Integrations (`base44.integrations.custom.*`) + +``` +call(slug, operationId, {payload?, pathParams?, queryParams?, headers?}?) → Promise<{success, status_code, data}> +``` + +**operationId format:** `"method:/path"` (e.g., `"get:/contacts"`, `"post:/users/{id}"`) + +--- + +## Analytics (`base44.analytics.*`) + +``` +track({eventName, properties?}) → void +``` + +--- + +## App Logs (`base44.appLogs.*`) + +``` +logUserInApp(pageName) → Promise +``` + +--- + +## Users (`base44.users.*`) + +``` +inviteUser(userEmail, role) → Promise // role: 'user' | 'admin' +``` + +--- + +## Connectors (`base44.asServiceRole.connectors.*`) + +**Backend only, service role required.** + +``` +getAccessToken(integrationType) → Promise +``` + +**Types:** `'googlecalendar'`, `'googledrive'`, `'slack'`, `'notion'`, `'salesforce'`, `'hubspot'`, `'linkedin'`, `'tiktok'`, `'github'` + +--- + +## Service Role Access + +**Backend functions only.** Prefix any module with `asServiceRole` for admin access: + +```javascript +base44.asServiceRole.entities.Task.list() +base44.asServiceRole.functions.invoke('name', data) +base44.asServiceRole.connectors.getAccessToken('slack') +``` + +--- + +## Backend Function Template + +```javascript +import { createClientFromRequest } from "@base44/sdk"; + +Deno.serve(async (req) => { + const base44 = createClientFromRequest(req); + const data = await req.json(); + + // User context + const user = await base44.auth.me(); + + // Service role for admin operations + const allRecords = await base44.asServiceRole.entities.Task.list(); + + return Response.json({ success: true }); +}); +``` + +--- + +## Client Initialization (External Apps) + +```javascript +import { createClient } from "@base44/sdk"; + +const base44 = createClient({ appId: "your-app-id" }); // MUST use 'appId' +``` diff --git a/evals/fixtures/cli-functions/skills/base44-sdk/references/analytics.md b/evals/fixtures/cli-functions/skills/base44-sdk/references/analytics.md new file mode 100644 index 0000000..d1b2c69 --- /dev/null +++ b/evals/fixtures/cli-functions/skills/base44-sdk/references/analytics.md @@ -0,0 +1,113 @@ +# Analytics Module + +Track custom events and user activity via `base44.analytics`. + +## Contents +- [Methods](#methods) +- [Examples](#examples) +- [Automatic Tracking](#automatic-tracking) +- [Best Practices](#best-practices) + +## Methods + +| Method | Signature | Description | +|--------|-----------|-------------| +| `track(params)` | `void` | Track a custom event | + +## Examples + +### Track Custom Event + +```javascript +// Track a simple event +base44.analytics.track({ + eventName: "button_clicked" +}); + +// Track event with properties +base44.analytics.track({ + eventName: "purchase_completed", + properties: { + product_id: "prod-123", + amount: 99.99, + currency: "USD" + } +}); +``` + +### Track User Actions + +```javascript +// Track page view +base44.analytics.track({ + eventName: "page_view", + properties: { + page: "/dashboard", + referrer: document.referrer + } +}); + +// Track feature usage +base44.analytics.track({ + eventName: "feature_used", + properties: { + feature: "export_data", + format: "csv" + } +}); +``` + +## Automatic Tracking + +The analytics module automatically tracks: +- **Initialization events**: When the app loads +- **Heartbeat events**: Periodic activity signals +- **Session duration**: Time spent in the app + +These internal events help measure user engagement without manual instrumentation. + +## Best Practices + +1. **Use descriptive event names**: `order_completed` instead of `click` +2. **Include relevant properties**: Add context that helps analyze the event +3. **Be consistent**: Use the same event names and property keys across your app +4. **Don't track sensitive data**: Avoid PII in event properties + +```javascript +// Good: Descriptive with relevant properties +base44.analytics.track({ + eventName: "subscription_started", + properties: { + plan: "pro", + billing_cycle: "annual" + } +}); + +// Avoid: Vague event name, no context +base44.analytics.track({ + eventName: "click" +}); +``` + +## Type Definitions + +```typescript +/** Properties that can be attached to a tracked event. */ +type TrackEventProperties = { + [key: string]: string | number | boolean | null | undefined; +}; + +/** Parameters for the track() method. */ +interface TrackEventParams { + /** The name of the event to track. */ + eventName: string; + /** Optional properties to attach to the event. */ + properties?: TrackEventProperties; +} + +/** The analytics module interface. */ +interface AnalyticsModule { + /** Track a custom event with optional properties. */ + track(params: TrackEventParams): void; +} +``` diff --git a/evals/fixtures/cli-functions/skills/base44-sdk/references/app-logs.md b/evals/fixtures/cli-functions/skills/base44-sdk/references/app-logs.md new file mode 100644 index 0000000..0439805 --- /dev/null +++ b/evals/fixtures/cli-functions/skills/base44-sdk/references/app-logs.md @@ -0,0 +1,78 @@ +# App Logs Module + +Log user activity in your app via `base44.appLogs`. + +## Contents +- [Methods](#methods) +- [Examples](#examples) +- [Use Cases](#use-cases) + +## Methods + +| Method | Signature | Description | +|--------|-----------|-------------| +| `logUserInApp(pageName)` | `Promise` | Log user activity on a page | + +## Examples + +### Log User Activity + +```javascript +// Log when user visits a page +await base44.appLogs.logUserInApp("dashboard"); + +// Log specific page visits +await base44.appLogs.logUserInApp("settings"); +await base44.appLogs.logUserInApp("profile"); + +// Log feature usage +await base44.appLogs.logUserInApp("export-button-click"); +``` + +The page name doesn't have to be an actual page - it can be any string you want to track. + +## Use Cases + +### Track Page Views in React + +```javascript +// Log page views on route change +useEffect(() => { + base44.appLogs.logUserInApp(window.location.pathname); +}, [location.pathname]); +``` + +### Track Feature Usage + +```javascript +// Log when user uses specific features +function handleExport() { + base44.appLogs.logUserInApp("export-data"); + // ... export logic +} + +function handleSettingsChange() { + base44.appLogs.logUserInApp("settings-updated"); + // ... save settings +} +``` + +## Notes + +- Logs appear in the Analytics page of your app dashboard +- App logs track page-level and feature-level activity +- Use `analytics.track()` for custom events with properties, `appLogs.logUserInApp()` for simple page/feature tracking + +## Type Definitions + +```typescript +/** App Logs module for tracking and analyzing app usage. */ +interface AppLogsModule { + /** + * Log user activity in the app. + * @param pageName - Name of the page or section being visited. + * @returns Promise that resolves when the log is recorded. + */ + logUserInApp(pageName: string): Promise; +} +``` diff --git a/evals/fixtures/cli-functions/skills/base44-sdk/references/auth.md b/evals/fixtures/cli-functions/skills/base44-sdk/references/auth.md new file mode 100644 index 0000000..5195b3a --- /dev/null +++ b/evals/fixtures/cli-functions/skills/base44-sdk/references/auth.md @@ -0,0 +1,761 @@ +# Auth Module + +User authentication, registration, and session management via `base44.auth`. + +## Contents +- [TypeScript Types](#typescript-types) +- [Methods](#methods) +- [Examples](#examples) +- [Error Handling](#error-handling) +- [Auth Providers](#auth-providers) +- [Environment Availability](#environment-availability) +- [App Visibility](#app-visibility) +- [Limitations](#limitations) + +--- + +## TypeScript Types + +### User Interface +```typescript +interface User { + id: string; + created_date: string; + updated_date: string; + email: string; + full_name: string | null; + disabled: boolean | null; + is_verified: boolean; + app_id: string; + is_service: boolean; + role: string; + [key: string]: any; // Custom schema fields +} +``` + +### LoginResponse Interface +```typescript +interface LoginResponse { + access_token: string; // JWT token + user: User; // Complete user object +} +``` + +### Parameter Interfaces + +#### RegisterParams +```typescript +interface RegisterParams { + email: string; // Required + password: string; // Required + turnstile_token?: string | null; // Optional: Cloudflare Turnstile for bot protection + referral_code?: string | null; // Optional: Referral code +} +``` + +#### VerifyOtpParams +```typescript +interface VerifyOtpParams { + email: string; // User's email + otpCode: string; // OTP code from email +} +``` + +#### ResetPasswordParams +```typescript +interface ResetPasswordParams { + resetToken: string; // Token from password reset email + newPassword: string; // New password to set +} +``` + +#### ChangePasswordParams +```typescript +interface ChangePasswordParams { + userId: string; // User ID + currentPassword: string; // Current password for verification + newPassword: string; // New password to set +} +``` + +### Provider Type +```typescript +type Provider = 'google' | 'microsoft' | 'facebook'; +``` + +--- + +## Methods + +### Module Interface +```typescript +interface AuthModule { + // User Info + me(): Promise; + updateMe(data: Partial>): Promise; + isAuthenticated(): Promise; + + // Login/Logout + loginViaEmailPassword(email: string, password: string, turnstileToken?: string): Promise; + loginWithProvider(provider: Provider, fromUrl?: string): void; + logout(redirectUrl?: string): void; + redirectToLogin(nextUrl: string): void; + + // Token Management + setToken(token: string, saveToStorage?: boolean): void; + + // Registration + register(params: RegisterParams): Promise; + verifyOtp(params: VerifyOtpParams): Promise; + resendOtp(email: string): Promise; + + // User Management + inviteUser(userEmail: string, role: string): Promise; + + // Password Management + resetPasswordRequest(email: string): Promise; + resetPassword(params: ResetPasswordParams): Promise; + changePassword(params: ChangePasswordParams): Promise; +} +``` + +### Method Reference Table + +| Method | Parameters | Return Type | Description | +|--------|-----------|-------------|-------------| +| `register()` | `params: RegisterParams` | `Promise` | Create new user account | +| `loginViaEmailPassword()` | `email: string, password: string, turnstileToken?: string` | `Promise` | Authenticate with email/password | +| `loginWithProvider()` | `provider: Provider, fromUrl?: string` | `void` | Initiate OAuth login flow | +| `me()` | None | `Promise` | Get current authenticated user | +| `updateMe()` | `data: Partial` | `Promise` | Update current user's profile | +| `logout()` | `redirectUrl?: string` | `void` | Clear session, optionally redirect | +| `redirectToLogin()` | `nextUrl: string` | `void` | ⚠️ **Avoid** - Prefer custom login UI with `loginViaEmailPassword()` or `loginWithProvider()` | +| `isAuthenticated()` | None | `Promise` | Check if user is logged in | +| `setToken()` | `token: string, saveToStorage?: boolean` | `void` | Manually set auth token | +| `inviteUser()` | `userEmail: string, role: string` | `Promise` | Send invitation email | +| `verifyOtp()` | `params: VerifyOtpParams` | `Promise` | Verify OTP code | +| `resendOtp()` | `email: string` | `Promise` | Resend OTP code | +| `resetPasswordRequest()` | `email: string` | `Promise` | Request password reset | +| `resetPassword()` | `params: ResetPasswordParams` | `Promise` | Reset password with token | +| `changePassword()` | `params: ChangePasswordParams` | `Promise` | Change user password | + +--- + +## Examples + +### Register New User (Complete Flow) + +Registration requires email verification before login. Complete flow: + +1. **Register** - Create the user account +2. **Verification email sent** - User receives an OTP code +3. **Verify OTP** - User enters code to verify email +4. **Login** - User can now log in + +```javascript +try { + // Step 1: Register the user + await base44.auth.register({ + email: "user@example.com", + password: "securePassword123", + referral_code: "OPTIONAL_CODE", // optional + turnstile_token: "CAPTCHA_TOKEN" // optional, for bot protection + }); + console.log('Registration successful. Check email for OTP code.'); + + // Step 2: User receives email with OTP code (e.g., "123456") + + // Step 3: Verify the OTP code + await base44.auth.verifyOtp({ + email: "user@example.com", + otpCode: "123456" // code from verification email + }); + console.log('Email verified successfully.'); + + // Step 4: Now the user can log in + const loginResponse = await base44.auth.loginViaEmailPassword( + "user@example.com", + "securePassword123" + ); + console.log('Login successful:', loginResponse.user); + +} catch (error) { + console.error('Registration flow failed:', error.message); + // Handle specific errors (see Error Handling section) +} +``` + +> **Important**: Users cannot log in until they complete OTP verification. Attempting to call `loginViaEmailPassword` before verification will fail. + +### Login with Email/Password + +```javascript +try { + const response = await base44.auth.loginViaEmailPassword( + "user@example.com", + "password123", + turnstileToken // optional: for bot protection + ); + + console.log('Login successful'); + console.log('User:', response.user); + console.log('Token:', response.access_token); + + // JWT is automatically stored for subsequent requests + +} catch (error) { + console.error('Login failed:', error.message); + if (error.status === 401) { + console.error('Invalid credentials'); + } else if (error.status === 403) { + console.error('Email not verified. Please check your email for OTP.'); + } +} +``` + +### Login with OAuth Provider + +```javascript +// Redirect to Google OAuth +base44.auth.loginWithProvider('google'); + +// Redirect to Google OAuth and return to current page after +base44.auth.loginWithProvider('google', window.location.href); + +// Supported providers: 'google', 'microsoft', 'facebook' +base44.auth.loginWithProvider('microsoft'); +``` + +### Get Current User + +```javascript +try { + const user = await base44.auth.me(); + + if (user) { + console.log('User ID:', user.id); + console.log('Email:', user.email); + console.log('Name:', user.full_name); + console.log('Role:', user.role); + console.log('Verified:', user.is_verified); + } else { + console.log('User not authenticated'); + } + +} catch (error) { + console.error('Failed to fetch user:', error.message); + if (error.status === 401) { + // Token expired or invalid - navigate to your custom login page + navigate('/login'); + } +} +``` + +### Update User Profile + +```javascript +try { + const updatedUser = await base44.auth.updateMe({ + full_name: "John Doe", + // Custom schema fields can be updated here + phone: "+1234567890", + preferences: { theme: "dark" } + }); + + console.log('Profile updated:', updatedUser); + +} catch (error) { + console.error('Profile update failed:', error.message); + if (error.status === 400) { + console.error('Invalid data provided'); + } else if (error.status === 401) { + console.error('Authentication required'); + navigate('/login'); + } +} +``` + +### Check Authentication Status + +```javascript +try { + const isLoggedIn = await base44.auth.isAuthenticated(); + + if (isLoggedIn) { + // Show authenticated UI + console.log('User is authenticated'); + } else { + // Show login button + console.log('User is not authenticated'); + } + +} catch (error) { + console.error('Failed to check authentication:', error.message); + // On error, treat as not authenticated +} +``` + +### Logout + +```javascript +// Simple logout +base44.auth.logout(); + +// Logout and redirect to goodbye page +base44.auth.logout("/goodbye"); + +// Logout and redirect to homepage +base44.auth.logout("/"); +``` + +### Protected Route Pattern + +```javascript +// Using a navigation function (e.g., React Router's useNavigate, Next.js router) +async function requireAuth(navigate) { + try { + const user = await base44.auth.me(); + + if (!user) { + // Navigate to your custom login page + navigate('/login', { state: { returnTo: window.location.pathname } }); + return null; + } + + return user; + + } catch (error) { + console.error('Authentication check failed:', error.message); + navigate('/login', { state: { returnTo: window.location.pathname } }); + return null; + } +} + +// Usage in your app +async function loadProtectedPage(navigate) { + const user = await requireAuth(navigate); + if (!user) { + // Will navigate to login + return; + } + + // Continue with authenticated logic + console.log('Welcome,', user.full_name); +} +``` + +### Set Authentication Token + +```javascript +// SECURITY WARNING: Never hardcode tokens or expose them in client code +// Tokens should only be received from secure authentication flows + +// Set token and save to localStorage (default) +base44.auth.setToken(receivedToken); + +// Set token without saving to localStorage (temporary session) +base44.auth.setToken(receivedToken, false); + +// Verify token was set +try { + const isAuthenticated = await base44.auth.isAuthenticated(); + if (!isAuthenticated) { + console.error('Token validation failed'); + } +} catch (error) { + console.error('Failed to set token:', error.message); +} +``` + +### Invite User to Application + +```javascript +try { + // Note: Typically requires admin privileges + const response = await base44.auth.inviteUser( + "newuser@example.com", + "user" // or "admin" + ); + + console.log('Invitation sent successfully'); + +} catch (error) { + console.error('Failed to invite user:', error.message); + if (error.status === 403) { + console.error('Insufficient permissions to invite users'); + } else if (error.status === 400) { + console.error('Invalid email or role'); + } +} +``` + +### OTP Verification + +```javascript +try { + // Verify OTP code sent to user's email + await base44.auth.verifyOtp({ + email: "user@example.com", + otpCode: "123456" + }); + + console.log('OTP verified successfully'); + +} catch (error) { + console.error('OTP verification failed:', error.message); + if (error.status === 400) { + console.error('Invalid or expired OTP code'); + } else if (error.status === 429) { + console.error('Too many attempts. Please try again later.'); + } +} + +// Resend OTP if needed +try { + await base44.auth.resendOtp("user@example.com"); + console.log('OTP resent successfully'); + +} catch (error) { + console.error('Failed to resend OTP:', error.message); + if (error.status === 429) { + console.error('Too many requests. Please wait before trying again.'); + } +} +``` + +### Password Reset Flow + +```javascript +// Step 1: Request password reset +try { + await base44.auth.resetPasswordRequest("user@example.com"); + console.log('Password reset email sent. Check your inbox.'); + +} catch (error) { + console.error('Password reset request failed:', error.message); + if (error.status === 429) { + console.error('Too many requests. Please try again later.'); + } + // Note: For security, don't reveal if email exists +} + +// Step 2: Reset password with token from email +try { + await base44.auth.resetPassword({ + resetToken: "token-from-email", + newPassword: "newSecurePassword123" + }); + + console.log('Password reset successfully. You can now log in.'); + +} catch (error) { + console.error('Password reset failed:', error.message); + if (error.status === 400) { + console.error('Invalid or expired reset token'); + } else if (error.status === 422) { + console.error('Password does not meet requirements'); + } +} +``` + +### Change Password + +```javascript +try { + const currentUser = await base44.auth.me(); + + if (!currentUser) { + throw new Error('User must be authenticated to change password'); + } + + await base44.auth.changePassword({ + userId: currentUser.id, + currentPassword: "oldPassword123", + newPassword: "newSecurePassword456" + }); + + console.log('Password changed successfully'); + +} catch (error) { + console.error('Password change failed:', error.message); + if (error.status === 401) { + console.error('Current password is incorrect'); + } else if (error.status === 422) { + console.error('New password does not meet requirements'); + } else if (error.status === 403) { + console.error('Not authorized to change this password'); + } +} +``` + +--- + +## Error Handling + +### Common Error Scenarios + +The auth module can throw various errors. Here are common scenarios and how to handle them: + +#### Authentication Errors (401/403) +```javascript +try { + const user = await base44.auth.me(); +} catch (error) { + if (error.status === 401) { + // Token expired or invalid - navigate to your custom login page + navigate('/login'); + } else if (error.status === 403) { + // Email not verified or insufficient permissions + console.error('Access denied:', error.message); + } +} +``` + +#### Validation Errors (400/422) +```javascript +try { + await base44.auth.register({ + email: "invalid-email", + password: "weak" + }); +} catch (error) { + if (error.status === 400) { + console.error('Invalid input:', error.message); + // Handle validation errors + } else if (error.status === 422) { + console.error('Data validation failed:', error.details); + } +} +``` + +#### Rate Limiting (429) +```javascript +try { + await base44.auth.resendOtp("user@example.com"); +} catch (error) { + if (error.status === 429) { + console.error('Too many requests. Please wait before trying again.'); + // Show countdown or disable button temporarily + } +} +``` + +#### Generic Error Handler +```javascript +function handleAuthError(error) { + switch (error.status) { + case 400: + return 'Invalid input. Please check your data.'; + case 401: + return 'Authentication required. Redirecting to login...'; + case 403: + return 'Access denied. You may need to verify your email.'; + case 404: + return 'Resource not found.'; + case 422: + return 'Validation failed. Please check your input.'; + case 429: + return 'Too many requests. Please try again later.'; + case 500: + return 'Server error. Please try again.'; + default: + return 'An unexpected error occurred.'; + } +} + +// Usage +try { + await base44.auth.loginViaEmailPassword(email, password); +} catch (error) { + console.error(handleAuthError(error)); +} +``` + +--- + +## Auth Providers + +Configure authentication providers in your app dashboard: + +### Available Providers + +**Built-in (All Plans):** +- **Email/Password** - Default, always enabled +- **Google** - OAuth authentication +- **Microsoft** - OAuth authentication +- **Facebook** - OAuth authentication + +**SSO Providers (Elite Plan):** +- **Okta** +- **Azure AD** +- **GitHub** + +### Using OAuth Providers + +```javascript +// Initiate OAuth login flow +base44.auth.loginWithProvider('google'); + +// Return to specific page after authentication +base44.auth.loginWithProvider('microsoft', '/dashboard'); + +// Supported values: 'google', 'microsoft', 'facebook' +``` + +--- + +## Environment Availability + +| Environment | Availability | Notes | +|------------|--------------|-------| +| **Frontend** | ✅ Yes | All methods available | +| **Backend Functions** | ✅ Yes | Use `createClientFromRequest(req)` for authenticated client | +| **Service Role** | ❌ No | Auth methods not available in service role context | + +### Frontend Usage +```javascript +// Standard browser usage +const user = await base44.auth.me(); +``` + +### Backend Functions Usage +```javascript +import { createClientFromRequest } from "@base44/sdk"; + +Deno.serve(async (req) => { + const base44 = createClientFromRequest(req); + + // Get user from request context + const user = await base44.auth.me(); + + // User operations here... + return Response.json({ user }); +}); +``` + +--- + +## App Visibility + +Control who can access your app in the app settings: + +### Public Apps +- No login required for basic access +- Users can view public content without authentication +- Authenticated users get additional features/data + +### Private Apps +- Login required to access any content +- Unauthenticated users are automatically redirected to login +- All content is protected by default + +--- + +## Limitations + +### Authentication UI Options +- **Recommended:** Build custom login/signup UI using `loginViaEmailPassword()` and `loginWithProvider()` for full control over user experience and branding +- **Alternative:** `redirectToLogin()` uses Base44's hosted authentication pages with limited customization + +### Hosted Login (via redirectToLogin) +- `redirectToLogin()` shows both login and signup options on the same page +- No separate `redirectToSignup()` method +- Users can switch between login/signup on the hosted page +- ⚠️ **Note:** Prefer building custom login UI for better user experience + +### Password Requirements +- Minimum length and complexity requirements enforced +- Requirements not exposed via API +- Validation errors returned when requirements not met + +### Rate Limiting +- OTP requests are rate-limited to prevent abuse +- Password reset requests are rate-limited +- Login attempts may be rate-limited with Turnstile protection + +### Token Management +- JWTs are automatically stored in localStorage by default +- Token expiration and refresh not exposed in API +- Call `me()` or `isAuthenticated()` to verify token validity + +--- + +## Best Practices + +### 1. Always Handle Errors +```javascript +try { + await base44.auth.loginViaEmailPassword(email, password); +} catch (error) { + // Always handle authentication errors + console.error('Login failed:', error.message); +} +``` + +### 2. Verify Authentication Before Protected Actions +```javascript +const user = await requireAuth(); +if (!user) return; // Will redirect to login +// Proceed with authenticated action +``` + +### 3. Use Type Safety with TypeScript +```typescript +import type { User, LoginResponse } from '@base44/sdk'; + +const user: User = await base44.auth.me(); +const response: LoginResponse = await base44.auth.loginViaEmailPassword(email, password); +``` + +### 4. Don't Hardcode Credentials +```javascript +// ❌ BAD +base44.auth.setToken("hardcoded-token-here"); + +// ✅ GOOD +const token = await secureAuthFlow(); +base44.auth.setToken(token); +``` + +### 5. Provide User Feedback +```javascript +try { + await base44.auth.register(params); + showSuccessMessage('Registration successful! Check your email for verification code.'); +} catch (error) { + showErrorMessage('Registration failed: ' + error.message); +} +``` + +### 6. Handle Token Expiration Gracefully +```javascript +try { + const user = await base44.auth.me(); +} catch (error) { + if (error.status === 401) { + // Clear local state and navigate to login + localStorage.clear(); + navigate('/login'); + } +} +``` + +### 7. Build Custom Login UI (Recommended) +```javascript +// ✅ RECOMMENDED - Custom login form with direct methods +const handleLogin = async (email, password) => { + try { + const { user } = await base44.auth.loginViaEmailPassword(email, password); + navigate('/dashboard'); + } catch (error) { + setError(error.message); + } +}; + +const handleGoogleLogin = () => { + base44.auth.loginWithProvider('google', '/dashboard'); +}; + +// ❌ AVOID - Redirecting to hosted login page +// base44.auth.redirectToLogin(window.location.href); +``` diff --git a/evals/fixtures/cli-functions/skills/base44-sdk/references/base44-agents.md b/evals/fixtures/cli-functions/skills/base44-sdk/references/base44-agents.md new file mode 100644 index 0000000..ee55dee --- /dev/null +++ b/evals/fixtures/cli-functions/skills/base44-sdk/references/base44-agents.md @@ -0,0 +1,364 @@ +# Agents Module + +AI agent conversations and messages via `base44.agents`. + +> **Note:** This module requires a logged-in user. All agent methods work in the context of the authenticated user. + +## Contents +- [Concepts](#concepts) +- [Methods](#methods) +- [Examples](#examples) (Create, Get Conversations, List, Subscribe, Send Message, WhatsApp) +- [Message Structure](#message-structure) +- [Conversation Structure](#conversation-structure) +- [Common Patterns](#common-patterns) + +## Concepts + +- **Conversation**: A dialogue between user and an AI agent. Has unique ID, agent name, user reference, and metadata. +- **Message**: Single message in a conversation. Has role (`user`, `assistant`, `system`), content, timestamps, and optional metadata. + +## Methods + +| Method | Signature | Description | +|--------|-----------|-------------| +| `createConversation(params)` | `Promise` | Create a new conversation with an agent | +| `getConversations()` | `Promise` | Get all user's conversations | +| `getConversation(id)` | `Promise` | Get conversation with messages | +| `listConversations(filterParams)` | `Promise` | Filter/sort/paginate conversations | +| `subscribeToConversation(id, onUpdate?)` | `() => void` | Real-time updates via WebSocket (returns unsubscribe function) | +| `addMessage(conversation, message)` | `Promise` | Send a message | +| `getWhatsAppConnectURL(agentName)` | `string` | Get WhatsApp connection URL for agent | + +## Examples + +### Create Conversation + +```javascript +const conversation = await base44.agents.createConversation({ + agent_name: "support-agent", + metadata: { + order_id: "ORD-123", + category: "billing" + } +}); + +console.log(conversation.id); +``` + +### Get All Conversations + +```javascript +const conversations = await base44.agents.getConversations(); + +conversations.forEach(conv => { + console.log(conv.id, conv.agent_name, conv.created_date); +}); +``` + +### Get Single Conversation (with messages) + +```javascript +const conversation = await base44.agents.getConversation("conv-id-123"); + +console.log(conversation.messages); +``` + +### List with Filters + +```javascript +// Using filterParams object with q, sort, limit, skip, fields +const recent = await base44.agents.listConversations({ + q: { agent_name: "support-agent" }, + sort: "-created_date", + limit: 10, + skip: 0 +}); + +// Filter by metadata +const highPriority = await base44.agents.listConversations({ + q: { + agent_name: "support-agent", + "metadata.priority": "high" + }, + sort: "-updated_date", + limit: 20 +}); +``` + +### Subscribe to Updates (Real-time) + +```javascript +const unsubscribe = base44.agents.subscribeToConversation( + "conv-id-123", + (updatedConversation) => { + // Called when new messages arrive + console.log("New messages:", updatedConversation.messages); + } +); + +// Later: unsubscribe +unsubscribe(); +``` + +### Send a Message + +```javascript +const conversation = await base44.agents.getConversation("conv-id-123"); + +await base44.agents.addMessage(conversation, { + role: "user", + content: "What's the weather like today?" +}); +``` + +### Get WhatsApp Connection URL + +```javascript +const whatsappUrl = base44.agents.getWhatsAppConnectURL("support-agent"); +// Returns URL for users to connect with agent via WhatsApp +console.log(whatsappUrl); +``` + +## Message Structure + +```javascript +{ + role: "user" | "assistant" | "system", + content: "Message text or structured object", + created_date: "2024-01-15T10:30:00Z", + updated_date: "2024-01-15T10:30:00Z", + + // Optional fields + reasoning: { + content: "Agent's reasoning process", + timing: 1500 + }, + tool_calls: [{ + name: "search", + arguments: { query: "weather" }, + result: { ... }, + status: "success" + }], + file_urls: ["https://..."], + usage: { + prompt_tokens: 150, + completion_tokens: 50 + }, + metadata: { ... }, + custom_context: { ... } +} +``` + +## Conversation Structure + +```javascript +{ + id: "conv-id-123", + app_id: "app-id", + agent_name: "support-agent", + created_by_id: "user-id", + created_date: "2024-01-15T10:00:00Z", + updated_date: "2024-01-15T10:30:00Z", + messages: [ ... ], + metadata: { ... } +} +``` + +## Common Patterns + +### Chat Interface + +```javascript +// Load conversation +const conv = await base44.agents.getConversation(conversationId); +setMessages(conv.messages); + +// Subscribe to updates +const unsubscribe = base44.agents.subscribeToConversation(conversationId, (updated) => { + setMessages(updated.messages); +}); + +// Send message +async function sendMessage(text) { + await base44.agents.addMessage(conv, { role: "user", content: text }); +} + +// Cleanup on unmount +return () => unsubscribe(); +``` + +## Type Definitions + +### AgentConversation + +```typescript +/** An agent conversation containing messages exchanged with an AI agent. */ +interface AgentConversation { + /** Unique identifier for the conversation. */ + id: string; + /** Application ID. */ + app_id: string; + /** Name of the agent in this conversation. */ + agent_name: string; + /** ID of the user who created the conversation. */ + created_by_id: string; + /** When the conversation was created. */ + created_date: string; + /** When the conversation was last updated. */ + updated_date: string; + /** Array of messages in the conversation. */ + messages: AgentMessage[]; + /** Optional metadata associated with the conversation. */ + metadata?: Record; +} +``` + +### AgentMessage + +```typescript +/** A message in an agent conversation. */ +interface AgentMessage { + /** Unique identifier for the message. */ + id: string; + /** Role of the message sender. */ + role: "user" | "assistant" | "system"; + /** When the message was created. */ + created_date: string; + /** When the message was last updated. */ + updated_date: string; + /** Message content. */ + content?: string | Record; + /** Optional reasoning information for the message. */ + reasoning?: AgentMessageReasoning | null; + /** URLs to files attached to the message. */ + file_urls?: string[]; + /** Tool calls made by the agent. */ + tool_calls?: AgentMessageToolCall[]; + /** Token usage statistics. */ + usage?: AgentMessageUsage; + /** Whether the message is hidden from the user. */ + hidden?: boolean; + /** Custom context provided with the message. */ + custom_context?: AgentMessageCustomContext[]; + /** Model used to generate the message. */ + model?: string; + /** Checkpoint ID for the message. */ + checkpoint_id?: string; + /** Metadata about when and by whom the message was created. */ + metadata?: AgentMessageMetadata; +} +``` + +### Supporting Types + +```typescript +/** Reasoning information for an agent message. */ +interface AgentMessageReasoning { + /** When reasoning started. */ + start_date: string; + /** When reasoning ended. */ + end_date?: string; + /** Reasoning content. */ + content: string; +} + +/** A tool call made by the agent. */ +interface AgentMessageToolCall { + /** Tool call ID. */ + id: string; + /** Name of the tool called. */ + name: string; + /** Arguments passed to the tool as JSON string. */ + arguments_string: string; + /** Status of the tool call. */ + status: "running" | "success" | "error" | "stopped"; + /** Results from the tool call. */ + results?: string; +} + +/** Token usage statistics for an agent message. */ +interface AgentMessageUsage { + /** Number of tokens in the prompt. */ + prompt_tokens?: number; + /** Number of tokens in the completion. */ + completion_tokens?: number; +} + +/** Custom context provided with an agent message. */ +interface AgentMessageCustomContext { + /** Context message. */ + message: string; + /** Associated data for the context. */ + data: Record; + /** Type of context. */ + type: string; +} + +/** Metadata about when and by whom a message was created. */ +interface AgentMessageMetadata { + /** When the message was created. */ + created_date: string; + /** Email of the user who created the message. */ + created_by_email: string; + /** Full name of the user who created the message. */ + created_by_full_name: string; +} +``` + +### CreateConversationParams + +```typescript +/** Parameters for creating a new conversation. */ +interface CreateConversationParams { + /** The name of the agent to create a conversation with. */ + agent_name: string; + /** Optional metadata to attach to the conversation. */ + metadata?: Record; +} +``` + +### ModelFilterParams + +```typescript +/** Parameters for filtering, sorting, and paginating conversations. */ +interface ModelFilterParams { + /** Query object with field-value pairs for filtering. */ + q?: Record; + /** Sort parameter (e.g., "-created_date" for descending). */ + sort?: string | null; + /** Maximum number of results to return. */ + limit?: number | null; + /** Number of results to skip for pagination. */ + skip?: number | null; + /** Array of field names to include in the response. */ + fields?: string[] | null; +} +``` + +### AgentsModule + +```typescript +/** Agents module for managing AI agent conversations. */ +interface AgentsModule { + /** Gets all conversations from all agents in the app. */ + getConversations(): Promise; + + /** Gets a specific conversation by ID. */ + getConversation(conversationId: string): Promise; + + /** Lists conversations with filtering, sorting, and pagination. */ + listConversations(filterParams: ModelFilterParams): Promise; + + /** Creates a new conversation with an agent. */ + createConversation(conversation: CreateConversationParams): Promise; + + /** Adds a message to a conversation. */ + addMessage(conversation: AgentConversation, message: Partial): Promise; + + /** Subscribes to real-time updates for a conversation. Returns unsubscribe function. */ + subscribeToConversation(conversationId: string, onUpdate?: (conversation: AgentConversation) => void): () => void; + + /** Gets WhatsApp connection URL for an agent. */ + getWhatsAppConnectURL(agentName: string): string; +} +``` diff --git a/evals/fixtures/cli-functions/skills/base44-sdk/references/client.md b/evals/fixtures/cli-functions/skills/base44-sdk/references/client.md new file mode 100644 index 0000000..554bf2b --- /dev/null +++ b/evals/fixtures/cli-functions/skills/base44-sdk/references/client.md @@ -0,0 +1,257 @@ +# Client Setup + +How to create and configure the Base44 client. + +## Contents +- [In Base44-Generated Apps](#in-base44-generated-apps) +- [In External Apps](#in-external-apps) +- [In Backend Functions](#in-backend-functions) +- [Authentication Modes](#authentication-modes) (Anonymous, User, Service Role) +- [Available Modules](#available-modules) +- [Client Methods](#client-methods) +- [Client Configuration Options](#client-configuration-options) + +## In Base44-Generated Apps + +The client is pre-configured and available as `base44`. Just use it: + +```javascript +const tasks = await base44.entities.Task.list(); +``` + +## In External Apps + +Install the SDK and create a client: + +```bash +npm install @base44/sdk +``` + +```javascript +import { createClient } from "@base44/sdk"; + +// IMPORTANT: The parameter name is 'appId' (NOT 'clientId', NOT 'id') +// IMPORTANT: onError must be nested inside 'options' object +const base44 = createClient({ + appId: "your-app-id", // Required: Use 'appId' parameter + token: "optional-user-token", // Optional: for pre-authenticated requests + options: { // Optional: configuration options + onError: (error) => { // Optional: error handler (must be in options) + console.error("Base44 error:", error); + } + } +}); +``` + +**Common Mistakes:** +- ❌ `createClient({ clientId: "..." })` - WRONG parameter name +- ❌ `createClient({ id: "..." })` - WRONG parameter name +- ❌ `createClient({ appId: "...", onError: ... })` - WRONG: onError must be in options +- ✅ `createClient({ appId: "..." })` - CORRECT parameter name +- ✅ `createClient({ appId: "...", options: { onError: ... } })` - CORRECT: onError in options + +## In Backend Functions + +Use `createClientFromRequest` to get a client with the caller's auth context: + +```javascript +import { createClientFromRequest } from "@base44/sdk"; + +Deno.serve(async (req) => { + const base44 = createClientFromRequest(req); + + // Client inherits authentication from the request + const user = await base44.auth.me(); + + return Response.json({ user }); +}); +``` + +## Authentication Modes + +| Mode | How to Get | Permissions | +|------|-----------|-------------| +| **Anonymous** | `createClient({ appId })` without token | Public data only | +| **User** | After `loginViaEmailPassword()` or via `createClientFromRequest` | User's own data | +| **Service Role** | `base44.asServiceRole.*` in backend | Full admin access | + +## Anonymous Mode + +No authentication. Can only access public resources. + +```javascript +const base44 = createClient({ appId: "your-app-id" }); + +// Only works if Task entity allows anonymous read +const publicTasks = await base44.entities.Task.list(); +``` + +## User Mode + +After user logs in, the client automatically includes their token. + +```javascript +const base44 = createClient({ appId: "your-app-id" }); + +// Login sets the token +await base44.auth.loginViaEmailPassword("user@example.com", "password"); + +// Subsequent requests are authenticated +const user = await base44.auth.me(); +const myTasks = await base44.entities.Task.list(); // filtered by permissions +``` + +## Service Role Mode + +Admin-level access. **Backend only.** + +```javascript +// Inside a backend function +Deno.serve(async (req) => { + const base44 = createClientFromRequest(req); + + // User mode - respects permissions + const myTasks = await base44.entities.Task.list(); + + // Service role - bypasses permissions + const allTasks = await base44.asServiceRole.entities.Task.list(); + const allUsers = await base44.asServiceRole.entities.User.list(); + const oauthToken = await base44.asServiceRole.connectors.getAccessToken("slack"); + + return Response.json({ myTasks, allTasks }); +}); +``` + +## Available Modules + +The client exposes these modules: + +```javascript +base44.entities // CRUD operations +base44.auth // Authentication +base44.agents // AI conversations +base44.functions // Backend function invocation +base44.integrations // Third-party services +base44.analytics // Event tracking +base44.appLogs // App usage logging +base44.users // User invitations + +// Service role only (backend) +base44.asServiceRole.entities +base44.asServiceRole.agents +base44.asServiceRole.functions +base44.asServiceRole.integrations +base44.asServiceRole.appLogs +base44.asServiceRole.connectors +``` + +## Client Methods + +The client provides these methods: + +```javascript +// Set authentication token for all subsequent requests +base44.setToken(newToken); + +// Cleanup WebSocket connections (call when done with client) +base44.cleanup(); +``` + +### setToken + +Updates the authentication token for all subsequent API requests and WebSocket connections. + +```javascript +// After receiving a token (e.g., from external auth) +base44.setToken("new-jwt-token"); +``` + +### cleanup + +Disconnects WebSocket connections. Call when you're done with the client or when the component unmounts. + +```javascript +// Cleanup on component unmount (React example) +useEffect(() => { + return () => base44.cleanup(); +}, []); +``` + +## Client Configuration Options + +```javascript +createClient({ + appId: "your-app-id", // Required: MUST use 'appId' (not 'clientId' or 'id') + token: "jwt-token", // Optional: pre-set auth token + options: { // Optional: configuration options + onError: (error) => {} // Optional: global error handler (must be in options) + } +}); +``` + +**⚠️ Critical:** +- The parameter name is `appId`, not `clientId` or `id`. Using the wrong parameter name will cause errors. +- The `onError` handler must be nested inside the `options` object, not at the top level. + +## Type Definitions + +### CreateClientConfig + +```typescript +/** Configuration for creating a Base44 client. */ +interface CreateClientConfig { + /** The Base44 app ID (required). */ + appId: string; + /** User authentication token. Used to authenticate as a specific user. */ + token?: string; + /** Service role authentication token (backend only). */ + serviceToken?: string; + /** Additional client options. */ + options?: CreateClientOptions; +} + +/** Options for creating a Base44 client. */ +interface CreateClientOptions { + /** Optional error handler called whenever an API error occurs. */ + onError?: (error: Error) => void; +} +``` + +### Base44Client + +```typescript +/** The Base44 client instance. */ +interface Base44Client { + /** Entities module for CRUD operations on your data models. */ + entities: EntitiesModule; + /** Integrations module for calling pre-built integration endpoints. */ + integrations: IntegrationsModule; + /** Auth module for user authentication and management. */ + auth: AuthModule; + /** Functions module for invoking custom backend functions. */ + functions: FunctionsModule; + /** Agents module for managing AI agent conversations. */ + agents: AgentsModule; + /** App logs module for tracking app usage. */ + appLogs: AppLogsModule; + /** Analytics module for tracking custom events. */ + analytics: AnalyticsModule; + + /** Cleanup function to disconnect WebSocket connections. */ + cleanup(): void; + + /** Sets a new authentication token for all subsequent requests. */ + setToken(newToken: string): void; + + /** Provides access to modules with elevated service role permissions (backend only). */ + readonly asServiceRole: { + entities: EntitiesModule; + integrations: IntegrationsModule; + connectors: ConnectorsModule; + functions: FunctionsModule; + agents: AgentsModule; + appLogs: AppLogsModule; + cleanup(): void; + }; +} +``` diff --git a/evals/fixtures/cli-functions/skills/base44-sdk/references/connectors.md b/evals/fixtures/cli-functions/skills/base44-sdk/references/connectors.md new file mode 100644 index 0000000..f3fbcb4 --- /dev/null +++ b/evals/fixtures/cli-functions/skills/base44-sdk/references/connectors.md @@ -0,0 +1,113 @@ +# Connectors Module + +OAuth token management for external services. **Service role only** (backend functions). + +## Method + +```javascript +base44.asServiceRole.connectors.getAccessToken(integrationType): Promise +``` + +Returns a raw OAuth access token for the specified service. + +## Supported Services + +| Integration Type | Service | +|-----------------|---------| +| `"googlecalendar"` | Google Calendar | +| `"googledrive"` | Google Drive | +| `"slack"` | Slack | +| `"notion"` | Notion | +| `"salesforce"` | Salesforce | +| `"hubspot"` | HubSpot | +| `"linkedin"` | LinkedIn | +| `"tiktok"` | TikTok | +| `"github"` | GitHub | + +## Example Usage + +```javascript +// Backend function only +Deno.serve(async (req) => { + const base44 = createClientFromRequest(req); + + // Get OAuth token for Slack + const slackToken = await base44.asServiceRole.connectors.getAccessToken("slack"); + + // Use token directly with Slack API + const response = await fetch("https://slack.com/api/chat.postMessage", { + method: "POST", + headers: { + "Authorization": `Bearer ${slackToken}`, + "Content-Type": "application/json" + }, + body: JSON.stringify({ + channel: "#general", + text: "Hello from Base44!" + }) + }); + + return Response.json(await response.json()); +}); +``` + +## Google Calendar Example + +```javascript +Deno.serve(async (req) => { + const base44 = createClientFromRequest(req); + + const token = await base44.asServiceRole.connectors.getAccessToken("googlecalendar"); + + // List upcoming events + const response = await fetch( + "https://www.googleapis.com/calendar/v3/calendars/primary/events?" + + new URLSearchParams({ + maxResults: "10", + orderBy: "startTime", + singleEvents: "true", + timeMin: new Date().toISOString() + }), + { + headers: { "Authorization": `Bearer ${token}` } + } + ); + + const events = await response.json(); + return Response.json(events); +}); +``` + +## Setup Requirements + +1. **Builder plan** or higher +2. **Backend functions** enabled +3. **Connector configured** in Base44 dashboard (OAuth flow completed) + +## Important Notes + +- **One account per connector per app**: All users share the same connected account +- **Backend only**: `connectors` module not available in frontend code +- **Service role required**: Must use `base44.asServiceRole.connectors` +- **You handle the API calls**: Base44 only provides the token; you make the actual API requests +- **Token refresh**: Base44 handles token refresh automatically + +## Type Definitions + +```typescript +/** + * The type of external integration/connector. + * Examples: 'googlecalendar', 'slack', 'github', 'notion', etc. + */ +type ConnectorIntegrationType = string; + +/** Connectors module for managing OAuth tokens for external services. */ +interface ConnectorsModule { + /** + * Retrieves an OAuth access token for a specific external integration type. + * @param integrationType - The type of integration (e.g., 'googlecalendar', 'slack'). + * @returns Promise resolving to the access token string. + */ + getAccessToken(integrationType: ConnectorIntegrationType): Promise; +} +``` diff --git a/evals/fixtures/cli-functions/skills/base44-sdk/references/entities.md b/evals/fixtures/cli-functions/skills/base44-sdk/references/entities.md new file mode 100644 index 0000000..a7bdf35 --- /dev/null +++ b/evals/fixtures/cli-functions/skills/base44-sdk/references/entities.md @@ -0,0 +1,253 @@ +# Entities Module + +CRUD operations on data models. Access via `base44.entities.EntityName.method()`. + +## Contents +- [Methods](#methods) +- [Examples](#examples) (Create, Bulk Create, List, Filter, Get, Update, Delete, Subscribe) +- [User Entity](#user-entity) +- [Service Role Access](#service-role-access) +- [Permissions](#permissions) + +## Methods + +| Method | Signature | Description | +|--------|-----------|-------------| +| `create(data)` | `Promise` | Create one record | +| `bulkCreate(dataArray)` | `Promise` | Create multiple records | +| `list(sort?, limit?, skip?, fields?)` | `Promise` | Get all records (paginated) | +| `filter(query, sort?, limit?, skip?, fields?)` | `Promise` | Get records matching conditions | +| `get(id)` | `Promise` | Get single record by ID | +| `update(id, data)` | `Promise` | Update record (partial update) | +| `delete(id)` | `Promise` | Delete record by ID | +| `deleteMany(query)` | `Promise` | Delete all matching records | +| `importEntities(file)` | `Promise` | Import from CSV (frontend only) | +| `subscribe(callback)` | `() => void` | Subscribe to realtime updates (returns unsubscribe function) | + +## Examples + +### Create + +```javascript +const task = await base44.entities.Task.create({ + title: "Complete documentation", + status: "pending", + dueDate: "2024-12-31" +}); +``` + +### Bulk Create + +```javascript +const tasks = await base44.entities.Task.bulkCreate([ + { title: "Task 1", status: "pending" }, + { title: "Task 2", status: "pending" } +]); +``` + +### List with Pagination + +```javascript +// Get first 10 records, sorted by created_date descending +const tasks = await base44.entities.Task.list( + "-created_date", // sort (string: prefix with - for descending) + 10, // limit + 0 // skip +); + +// Get next page +const page2 = await base44.entities.Task.list("-created_date", 10, 10); +``` + +### Filter + +```javascript +// Simple filter +const pending = await base44.entities.Task.filter({ status: "pending" }); + +// Multiple conditions +const myPending = await base44.entities.Task.filter({ + status: "pending", + assignedTo: userId +}); + +// With sort, limit, skip +const recent = await base44.entities.Task.filter( + { status: "pending" }, + "-created_date", // sort (string: prefix with - for descending) + 5, + 0 +); + +// Select specific fields +const titles = await base44.entities.Task.filter( + { status: "pending" }, + null, + null, + null, + ["id", "title"] +); +``` + +### Get by ID + +```javascript +const task = await base44.entities.Task.get("task-id-123"); +``` + +### Update + +```javascript +// Partial update - only specified fields change +await base44.entities.Task.update("task-id-123", { + status: "completed", + completedAt: new Date().toISOString() +}); +``` + +### Delete + +```javascript +// Single record +await base44.entities.Task.delete("task-id-123"); + +// Multiple records matching query +await base44.entities.Task.deleteMany({ status: "archived" }); +``` + +### Subscribe to Realtime Updates + +```javascript +// Subscribe to all changes on Task entity +const unsubscribe = base44.entities.Task.subscribe((event) => { + console.log(`Task ${event.id} was ${event.type}:`, event.data); + // event.type is "create", "update", or "delete" +}); + +// Later: unsubscribe to stop receiving updates +unsubscribe(); +``` + +**Event structure:** +```javascript +{ + type: "create" | "update" | "delete", + data: { ... }, // the entity data + id: "entity-id", // the affected entity's ID + timestamp: "2024-01-15T10:30:00Z" +} +``` + +## User Entity + +Every app has a built-in `User` entity with special rules: + +- Regular users can only read/update **their own** record +- Cannot create users via `entities.create()` - use `auth.register()` instead +- Service role has full access to all user records + +```javascript +// Get current user's record +const me = await base44.entities.User.get(currentUserId); + +// Service role: get any user +const anyUser = await base44.asServiceRole.entities.User.get(userId); +``` + +## Service Role Access + +For admin-level operations (bypass user permissions): + +```javascript +// Backend only +const allTasks = await base44.asServiceRole.entities.Task.list(); +const allUsers = await base44.asServiceRole.entities.User.list(); +``` + +## Permissions (RLS & FLS) + +Data access is controlled by **Row Level Security (RLS)** and **Field Level Security (FLS)** rules defined in entity schemas. + +1. **Authentication level**: anonymous, authenticated, or service role +2. **RLS rules**: Control which records (rows) users can create/read/update/delete +3. **FLS rules**: Control which fields users can read/write within accessible records + +Operations succeed or fail based on these rules - no partial results. + +RLS and FLS are configured in entity schema files (`base44/entities/*.jsonc`). See [entities-create.md](../../base44-cli/references/entities-create.md#row-level-security-rls) for configuration details. + +**Note:** `asServiceRole` sets the user's role to `"admin"` but does NOT bypass RLS. Your RLS rules must include admin access (e.g., `{ "user_condition": { "role": "admin" } }`) for service role operations to succeed. + +## Type Definitions + +### RealtimeEvent + +```typescript +/** Event types for realtime entity updates. */ +type RealtimeEventType = "create" | "update" | "delete"; + +/** Payload received when a realtime event occurs. */ +interface RealtimeEvent { + /** The type of change that occurred. */ + type: RealtimeEventType; + /** The entity data. */ + data: any; + /** The unique identifier of the affected entity. */ + id: string; + /** ISO 8601 timestamp of when the event occurred. */ + timestamp: string; +} + +/** Callback function invoked when a realtime event occurs. */ +type RealtimeCallback = (event: RealtimeEvent) => void; + +/** Function returned from subscribe, call it to unsubscribe. */ +type Subscription = () => void; +``` + +### EntityHandler + +```typescript +/** Entity handler providing CRUD operations for a specific entity type. */ +interface EntityHandler { + /** Lists records with optional pagination and sorting. */ + list(sort?: string, limit?: number, skip?: number, fields?: string[]): Promise; + + /** Filters records based on a query. */ + filter(query: Record, sort?: string, limit?: number, skip?: number, fields?: string[]): Promise; + + /** Gets a single record by ID. */ + get(id: string): Promise; + + /** Creates a new record. */ + create(data: Record): Promise; + + /** Updates an existing record. */ + update(id: string, data: Record): Promise; + + /** Deletes a single record by ID. */ + delete(id: string): Promise; + + /** Deletes multiple records matching a query. */ + deleteMany(query: Record): Promise; + + /** Creates multiple records in a single request. */ + bulkCreate(data: Record[]): Promise; + + /** Imports records from a file (frontend only). */ + importEntities(file: File): Promise; + + /** Subscribes to realtime updates. Returns unsubscribe function. */ + subscribe(callback: RealtimeCallback): Subscription; +} +``` + +### EntitiesModule + +```typescript +/** Entities module for managing app data. */ +interface EntitiesModule { + /** Access any entity by name dynamically. */ + [entityName: string]: EntityHandler; +} +``` diff --git a/evals/fixtures/cli-functions/skills/base44-sdk/references/functions.md b/evals/fixtures/cli-functions/skills/base44-sdk/references/functions.md new file mode 100644 index 0000000..8072465 --- /dev/null +++ b/evals/fixtures/cli-functions/skills/base44-sdk/references/functions.md @@ -0,0 +1,216 @@ +# Functions Module + +Invoke custom backend functions via `base44.functions`. + +## Contents +- [Method](#method) +- [Invoking Functions](#invoking-functions) (Frontend, File Upload, Service Role, REST API) +- [Writing Backend Functions](#writing-backend-functions) (Basic, Service Role, Secrets, Errors) +- [Setup Requirements](#setup-requirements) +- [Authentication Modes](#authentication-modes) + +## Method + +```javascript +base44.functions.invoke(functionName, data): Promise +``` + +- `functionName`: Name of the backend function +- `data`: Object of parameters (sent as JSON, or multipart if contains File objects) +- Returns: Whatever the function returns + +## Invoking Functions + +### From Frontend + +```javascript +const result = await base44.functions.invoke("processOrder", { + orderId: "order-123", + action: "ship" +}); + +console.log(result); +``` + +### With File Upload + +```javascript +const fileInput = document.querySelector('input[type="file"]'); +const file = fileInput.files[0]; + +// Automatically uses multipart/form-data when File objects present +const result = await base44.functions.invoke("uploadDocument", { + file: file, + category: "invoices" +}); +``` + +### With Service Role (Backend) + +```javascript +// Inside another backend function +const result = await base44.asServiceRole.functions.invoke("adminTask", { + userId: "user-123" +}); +``` + +### Via REST API (curl) + +Functions can be called via HTTP POST to your app domain: + +```bash +curl -X POST "https:///functions/" \ + -H "Content-Type: application/json" \ + -d '{"key": "value"}' +``` + +## Writing Backend Functions + +Backend functions run on Deno. Must export using `Deno.serve()`. + +### Required Directory Structure + +Each function must be in its own subdirectory under `base44/functions/` with a configuration file: + +``` +base44/ + functions/ + process-order/ # kebab-case directory name + function.jsonc # required configuration + index.ts # entry point +``` + +**function.jsonc:** +```jsonc +{ + "name": "process-order", + "entry": "index.ts" +} +``` + +For complete setup and deployment instructions, see [functions-create.md](../../base44-cli/references/functions-create.md) in base44-cli. + +### Basic Structure + +```javascript +// base44/functions/process-order/index.ts +import { createClientFromRequest } from "npm:@base44/sdk"; + +Deno.serve(async (req) => { + // Get authenticated client from request + const base44 = createClientFromRequest(req); + + // Parse input + const { orderId, action } = await req.json(); + + // Your logic here + const order = await base44.entities.Orders.get(orderId); + + // Return response + return Response.json({ + success: true, + order: order + }); +}); +``` + +### With Service Role Access + +```javascript +import { createClientFromRequest } from "npm:@base44/sdk"; + +Deno.serve(async (req) => { + const base44 = createClientFromRequest(req); + + // Check user is authenticated + const user = await base44.auth.me(); + if (!user) { + return Response.json({ error: "Unauthorized" }, { status: 401 }); + } + + // Use service role for admin operations + const allOrders = await base44.asServiceRole.entities.Orders.list(); + + return Response.json({ orders: allOrders }); +}); +``` + +### Using Secrets + +```javascript +Deno.serve(async (req) => { + // Access environment variables (configured in app settings) + const apiKey = Deno.env.get("STRIPE_API_KEY"); + + const response = await fetch("https://api.stripe.com/v1/charges", { + headers: { + "Authorization": `Bearer ${apiKey}` + } + }); + + return Response.json(await response.json()); +}); +``` + +### Error Handling + +```javascript +import { createClientFromRequest } from "npm:@base44/sdk"; + +Deno.serve(async (req) => { + try { + const base44 = createClientFromRequest(req); + const { orderId } = await req.json(); + + const order = await base44.entities.Orders.get(orderId); + if (!order) { + return Response.json( + { error: "Order not found" }, + { status: 404 } + ); + } + + return Response.json({ order }); + + } catch (error) { + return Response.json( + { error: error.message }, + { status: 500 } + ); + } +}); +``` + +## Setup Requirements + +1. Enable Backend Functions in app settings (requires appropriate plan) +2. Create function files in `/functions` folder +3. Configure secrets via app dashboard for API keys + +## Authentication Modes + +| Mode | Context | Permissions | +|------|---------|-------------| +| User | `base44.functions.invoke()` | Runs under calling user's permissions | +| Service Role | `base44.asServiceRole.functions.invoke()` | Admin-level access | + +Inside the function, use `createClientFromRequest(req)` to get a client that inherits the caller's auth context. + +## Type Definitions + +```typescript +/** Functions module for invoking custom backend functions. */ +interface FunctionsModule { + /** + * Invokes a custom backend function by name. + * + * If any parameter is a File object, the request will automatically be + * sent as multipart/form-data. Otherwise, it will be sent as JSON. + * + * @param functionName - The name of the function to invoke. + * @param data - An object containing named parameters for the function. + * @returns Promise resolving to the function's response. + */ + invoke(functionName: string, data: Record): Promise; +} +``` diff --git a/evals/fixtures/cli-functions/skills/base44-sdk/references/integrations.md b/evals/fixtures/cli-functions/skills/base44-sdk/references/integrations.md new file mode 100644 index 0000000..f356a18 --- /dev/null +++ b/evals/fixtures/cli-functions/skills/base44-sdk/references/integrations.md @@ -0,0 +1,377 @@ +# Integrations Module + +Access third-party services via `base44.integrations`. + +## Types of Integrations + +1. **Core/Built-in**: AI, email, file uploads (available by default) +2. **Catalog integrations**: Pre-built connectors from Base44 catalog +3. **Custom integrations**: Your own OpenAPI-based integrations + +## Accessing Integrations + +```javascript +// Core integrations +base44.integrations.Core.FunctionName(params) + +// Custom integrations +base44.integrations.custom.call(slug, operationId, params) +``` + +## Core Integrations + +### InvokeLLM (AI Text Generation) + +Generate text or structured JSON data using AI models. + +```javascript +// Basic prompt - returns string +const response = await base44.integrations.Core.InvokeLLM({ + prompt: "Summarize this text: ..." +}); + +// With internet context (uses Google Search, Maps, News) +const response = await base44.integrations.Core.InvokeLLM({ + prompt: "What's the current weather in NYC?", + add_context_from_internet: true +}); + +// Structured JSON response - returns object +const response = await base44.integrations.Core.InvokeLLM({ + prompt: "Analyze the sentiment of: 'Great product but slow shipping'", + response_json_schema: { + type: "object", + properties: { + sentiment: { type: "string", enum: ["positive", "negative", "mixed"] }, + score: { type: "number", description: "Score from 1-10" }, + key_points: { type: "array", items: { type: "string" } } + } + } +}); +// Returns: { sentiment: "mixed", score: 7, key_points: ["great product", "slow shipping"] } + +// With file attachments (uploaded via UploadFile) +const response = await base44.integrations.Core.InvokeLLM({ + prompt: "Describe what's in this image", + file_urls: ["https://...uploaded_image.png"] +}); +``` + +**Parameters:** +- `prompt` (string, required): The prompt text to send to the model +- `add_context_from_internet` (boolean, optional): If true, uses Google Search/Maps/News for real-time context +- `response_json_schema` (object, optional): JSON schema for structured output +- `file_urls` (string[], optional): URLs of uploaded files for context + +### GenerateImage + +Create AI-generated images from text prompts. + +```javascript +const { url } = await base44.integrations.Core.GenerateImage({ + prompt: "A serene mountain landscape with a lake" +}); +console.log(url); // https://...generated_image.png +``` + +### SendEmail + +Send emails to registered users. Every app gets this integration (no plan upgrade required). + +```javascript +await base44.integrations.Core.SendEmail({ + to: "user@example.com", + subject: "Welcome!", + body: "

Hello

Welcome to our app.

", + from_name: "My App" // optional, defaults to app name +}); +``` + +**Parameters:** +- `to` (string, required): Recipient email address +- `subject` (string, required): Email subject line +- `body` (string, required): Plain text or HTML email body +- `from_name` (string, optional): Sender name displayed to recipient + +**Limitations:** +- 1 credit per email (2 credits with custom domain) + +### UploadFile (Public) + +Upload files to public storage. + +```javascript +const fileInput = document.querySelector('input[type="file"]'); +const { file_url } = await base44.integrations.Core.UploadFile({ + file: fileInput.files[0] +}); +console.log(file_url); // https://...uploaded_file.pdf +``` + +### UploadPrivateFile + +Upload files to private storage that requires a signed URL to access. + +```javascript +const { file_uri } = await base44.integrations.Core.UploadPrivateFile({ + file: fileInput.files[0] +}); +console.log(file_uri); // "private/user123/document.pdf" + +// Create a signed URL to access the file +const { signed_url } = await base44.integrations.Core.CreateFileSignedUrl({ + file_uri, + expires_in: 3600 // URL expires in 1 hour (default: 300 seconds) +}); +console.log(signed_url); // Temporary URL to access the private file +``` + +### CreateFileSignedUrl + +Generate temporary access links for private files. + +```javascript +const { signed_url } = await base44.integrations.Core.CreateFileSignedUrl({ + file_uri: "private/user123/document.pdf", + expires_in: 7200 // 2 hours +}); +``` + +**Parameters:** +- `file_uri` (string, required): URI from UploadPrivateFile +- `expires_in` (number, optional): Expiration time in seconds (default: 300) + +### ExtractDataFromUploadedFile + +Extract structured data from uploaded files using AI. + +```javascript +// First upload the file +const { file_url } = await base44.integrations.Core.UploadFile({ file }); + +// Then extract structured data +const result = await base44.integrations.Core.ExtractDataFromUploadedFile({ + file_url, + json_schema: { + type: "object", + properties: { + invoice_number: { type: "string" }, + total_amount: { type: "number" }, + date: { type: "string" }, + vendor_name: { type: "string" } + } + } +}); +console.log(result); // { invoice_number: "INV-12345", total_amount: 1250.00, ... } +``` + +## Custom Integrations + +Custom integrations allow workspace administrators to connect any external API by importing an OpenAPI specification. Use `base44.integrations.custom.call()` to invoke them. + +### Syntax + +```javascript +const response = await base44.integrations.custom.call( + slug, // Integration identifier (set by admin) + operationId, // Endpoint in "method:path" format + params // Optional: payload, pathParams, queryParams +); +``` + +### Examples + +```javascript +// GET request with query params +const response = await base44.integrations.custom.call( + "my-crm", + "get:/contacts", + { queryParams: { limit: 10, status: "active" } } +); + +// POST request with body +const response = await base44.integrations.custom.call( + "my-crm", + "post:/contacts", + { payload: { name: "John Doe", email: "john@example.com" } } +); + +// Request with path parameters +const response = await base44.integrations.custom.call( + "github", + "post:/repos/{owner}/{repo}/issues", + { + pathParams: { owner: "myorg", repo: "myrepo" }, + payload: { title: "Bug report", body: "Something is broken" } + } +); +``` + +### Response Structure + +```javascript +{ + success: true, // Whether external API returned 2xx + status_code: 200, // HTTP status code + data: { ... } // Response from external API +} +``` + +## Requirements + +- **Core integrations**: Available on all plans +- **Catalog/Custom integrations**: Require Builder plan or higher + +## Type Definitions + +### Core Integration Parameters + +```typescript +/** Parameters for the InvokeLLM function. */ +interface InvokeLLMParams { + /** The prompt text to send to the model. */ + prompt: string; + /** If true, uses Google Search/Maps/News for real-time context. */ + add_context_from_internet?: boolean; + /** JSON schema for structured output. If provided, returns object instead of string. */ + response_json_schema?: object; + /** File URLs (from UploadFile) to provide as context. */ + file_urls?: string[]; +} + +/** Parameters for the GenerateImage function. */ +interface GenerateImageParams { + /** Description of the image to generate. */ + prompt: string; +} + +/** Result from GenerateImage. */ +interface GenerateImageResult { + /** URL of the generated image. */ + url: string; +} + +/** Parameters for the UploadFile function. */ +interface UploadFileParams { + /** The file object to upload. */ + file: File; +} + +/** Result from UploadFile. */ +interface UploadFileResult { + /** URL of the uploaded file. */ + file_url: string; +} + +/** Parameters for the SendEmail function. */ +interface SendEmailParams { + /** Recipient email address. */ + to: string; + /** Email subject line. */ + subject: string; + /** Plain text or HTML email body. */ + body: string; + /** Sender name (defaults to app name). */ + from_name?: string; +} + +/** Parameters for ExtractDataFromUploadedFile. */ +interface ExtractDataFromUploadedFileParams { + /** URL of the uploaded file. */ + file_url: string; + /** JSON schema defining fields to extract. */ + json_schema: object; +} + +/** Parameters for UploadPrivateFile. */ +interface UploadPrivateFileParams { + /** The file object to upload. */ + file: File; +} + +/** Result from UploadPrivateFile. */ +interface UploadPrivateFileResult { + /** URI of the private file (used for signed URLs). */ + file_uri: string; +} + +/** Parameters for CreateFileSignedUrl. */ +interface CreateFileSignedUrlParams { + /** URI from UploadPrivateFile. */ + file_uri: string; + /** Expiration time in seconds (default: 300). */ + expires_in?: number; +} + +/** Result from CreateFileSignedUrl. */ +interface CreateFileSignedUrlResult { + /** Temporary signed URL to access the file. */ + signed_url: string; +} +``` + +### CoreIntegrations + +```typescript +/** Core package containing built-in Base44 integration functions. */ +interface CoreIntegrations { + InvokeLLM(params: InvokeLLMParams): Promise; + GenerateImage(params: GenerateImageParams): Promise; + UploadFile(params: UploadFileParams): Promise; + SendEmail(params: SendEmailParams): Promise; + ExtractDataFromUploadedFile(params: ExtractDataFromUploadedFileParams): Promise; + UploadPrivateFile(params: UploadPrivateFileParams): Promise; + CreateFileSignedUrl(params: CreateFileSignedUrlParams): Promise; +} +``` + +### Custom Integrations + +```typescript +/** Parameters for calling a custom integration endpoint. */ +interface CustomIntegrationCallParams { + /** Request body payload. */ + payload?: Record; + /** Path parameters to substitute in the URL. */ + pathParams?: Record; + /** Query string parameters. */ + queryParams?: Record; + /** Additional headers for this request. */ + headers?: Record; +} + +/** Response from a custom integration call. */ +interface CustomIntegrationCallResponse { + /** Whether the external API returned a 2xx status code. */ + success: boolean; + /** The HTTP status code from the external API. */ + status_code: number; + /** The response data from the external API. */ + data: any; +} + +/** Module for calling custom workspace-level API integrations. */ +interface CustomIntegrationsModule { + /** + * Call a custom integration endpoint. + * @param slug - The integration's unique identifier. + * @param operationId - The operation ID from the OpenAPI spec. + * @param params - Optional payload, pathParams, queryParams, headers. + */ + call(slug: string, operationId: string, params?: CustomIntegrationCallParams): Promise; +} +``` + +### IntegrationsModule + +```typescript +/** Integrations module for calling integration endpoints. */ +type IntegrationsModule = { + /** Core package with built-in integrations. */ + Core: CoreIntegrations; + /** Custom integrations module. */ + custom: CustomIntegrationsModule; + /** Additional integration packages (dynamic). */ + [packageName: string]: any; +}; +``` diff --git a/evals/fixtures/cli-functions/skills/base44-sdk/references/users.md b/evals/fixtures/cli-functions/skills/base44-sdk/references/users.md new file mode 100644 index 0000000..4205b06 --- /dev/null +++ b/evals/fixtures/cli-functions/skills/base44-sdk/references/users.md @@ -0,0 +1,82 @@ +# Users Module + +Invite users to the app via `base44.users`. + +## Contents +- [Methods](#methods) +- [Examples](#examples) (Invite User) +- [Roles](#roles) +- [Notes](#notes) + +## Methods + +| Method | Signature | Description | +|--------|-----------|-------------| +| `inviteUser(user_email, role)` | `Promise` | Invite a user to the app | + +## Examples + +### Invite User + +```javascript +// Invite a user with "user" role +await base44.users.inviteUser("newuser@example.com", "user"); + +// Invite an admin +await base44.users.inviteUser("admin@example.com", "admin"); +``` + +### Invite Multiple Users + +```javascript +const usersToInvite = [ + { email: "user1@example.com", role: "user" }, + { email: "user2@example.com", role: "user" }, + { email: "manager@example.com", role: "admin" } +]; + +for (const user of usersToInvite) { + await base44.users.inviteUser(user.email, user.role); + console.log(`Invited ${user.email} as ${user.role}`); +} +``` + +## Roles + +The `role` parameter must be one of: + +| Role | Description | +|------|-------------| +| `"user"` | Standard user with default permissions | +| `"admin"` | Administrator with elevated permissions | + +**Note:** Only `"user"` and `"admin"` are valid role values. An error will be thrown if you pass any other value. + +## Notes + +- **Email invitation**: The invited user receives an email with a link to join the app +- **Duplicate handling**: Inviting an existing user will re-send the invitation +- **Also available in auth**: `base44.auth.inviteUser()` provides the same functionality +- **Role validation**: Only `"user"` or `"admin"` are accepted + +```javascript +// These are equivalent: +await base44.users.inviteUser("newuser@example.com", "user"); +await base44.auth.inviteUser("newuser@example.com", "user"); +``` + +## Type Definitions + +```typescript +/** Users module for inviting users to the app. */ +interface UsersModule { + /** + * Invite a user to the application. + * @param user_email - User's email address. + * @param role - User's role ('user' or 'admin'). + * @returns Promise resolving when the invitation is sent. + * @throws Error if role is not 'user' or 'admin'. + */ + inviteUser(user_email: string, role: "user" | "admin"): Promise; +} +``` diff --git a/evals/fixtures/create-support-agent/AGENTS.md b/evals/fixtures/create-support-agent/AGENTS.md new file mode 100644 index 0000000..67387aa --- /dev/null +++ b/evals/fixtures/create-support-agent/AGENTS.md @@ -0,0 +1,14 @@ +# Support Agent Fixture + +This fixture tests creating an AI agent configuration file for a support assistant. + +## Project Context + +This is an existing Base44 project for an e-commerce app. It has Order and Customer entities already defined. + +## Expected Outcome + +Create an agent config file at `base44/agents/support_agent.jsonc` that: +- Uses the correct agent schema format +- Has `tool_configs` with entity access (not the old `tools` format) +- Includes clear instructions for the agent behavior diff --git a/evals/fixtures/create-support-agent/eval.json b/evals/fixtures/create-support-agent/eval.json new file mode 100644 index 0000000..07c1080 --- /dev/null +++ b/evals/fixtures/create-support-agent/eval.json @@ -0,0 +1,66 @@ +{ + "$schema": "../../eval.schema.json", + "name": "create-support-agent", + "description": "Test creating an AI agent configuration with entity access", + "prompt": "Create a support agent called 'support_agent' that helps users with their orders. The agent should have read access to Order and Customer entities. Give it clear instructions to always look up order details when users ask about their orders.", + "expectedSkills": ["base44-cli"], + "checks": [ + { + "type": "file-exists", + "description": "Agent config file created", + "filePath": "base44/agents/support_agent.jsonc" + }, + { + "type": "valid-json", + "description": "Agent config is valid JSON", + "filePath": "base44/agents/support_agent.jsonc" + }, + { + "type": "file-content", + "description": "Has correct agent name", + "filePath": "base44/agents/support_agent.jsonc", + "pattern": "\"name\"\\s*:\\s*\"support_agent\"" + }, + { + "type": "file-content", + "description": "Has tool_configs with entity access", + "filePath": "base44/agents/support_agent.jsonc", + "pattern": "\"tool_configs\"\\s*:\\s*\\[" + }, + { + "type": "file-content", + "description": "Has Order entity access", + "filePath": "base44/agents/support_agent.jsonc", + "pattern": "\"entity_name\"\\s*:\\s*\"Order\"" + }, + { + "type": "file-content", + "description": "Has instructions field", + "filePath": "base44/agents/support_agent.jsonc", + "pattern": "\"instructions\"\\s*:" + }, + { + "type": "contains", + "description": "Mentions agents push command", + "value": "agents push" + }, + { + "type": "agent-config", + "description": "Agent config validates against Base44 agent schema", + "filePath": "base44/agents/support_agent.jsonc", + "target": "file" + }, + { + "type": "file-content", + "description": "Has Customer entity access (both Order AND Customer required)", + "filePath": "base44/agents/support_agent.jsonc", + "pattern": "\"entity_name\"\\s*:\\s*\"Customer\"" + }, + { + "type": "file-content", + "description": "allowed_operations is an array with 'read'", + "filePath": "base44/agents/support_agent.jsonc", + "pattern": "\"allowed_operations\"\\s*:\\s*\\[[^\\]]*\"read\"" + } + ] +} diff --git a/evals/fixtures/create-support-agent/project/.claude/settings.json b/evals/fixtures/create-support-agent/project/.claude/settings.json new file mode 100644 index 0000000..ab60505 --- /dev/null +++ b/evals/fixtures/create-support-agent/project/.claude/settings.json @@ -0,0 +1,6 @@ +{ + "permissions": { + "allow": ["Read", "Write", "Edit", "Bash", "Glob", "Grep"] + }, + "skills": ["./skills"] +} diff --git a/evals/fixtures/create-support-agent/project/AGENTS.md b/evals/fixtures/create-support-agent/project/AGENTS.md new file mode 100644 index 0000000..6c0f2be --- /dev/null +++ b/evals/fixtures/create-support-agent/project/AGENTS.md @@ -0,0 +1,25 @@ +IMPORTANT: Prefer retrieval-led reasoning over pre-training-led reasoning. Base44 is NOT Firebase, Supabase, or any other SDK you know. Every API name below is the ONLY correct form. Always verify against this index and the referenced docs. + +[Base44 SDK Docs Index]|root: ./skills/base44-sdk/references +|entities.md: CRUD - create(data), list(), filter(query), get(id), update(id,data), delete(id), subscribe(cb) +|auth.md: Auth - loginViaEmailPassword(), loginWithProvider('google'), me(), register(), logout() +|base44-agents.md: Agents - createConversation({agent_name}), addMessage(conv,{role,content}), subscribeToConversation(id,cb) +|functions.md: Functions - invoke(name, data) +|integrations.md: Core - InvokeLLM({prompt}), SendEmail({to,subject,body}), UploadFile({file}) +|client.md: Client - createClient({appId}), import from "@base44/sdk" +|QUICK_REFERENCE.md: All method signatures + +[Base44 CLI Docs Index]|root: ./skills/base44-cli/references +|entities-create.md: Entity schema - {name:"PascalCase", type:"object", properties:{}, required:[]} +|functions-create.md: Function - function.jsonc {name,entry} + index.ts with Deno.serve + createClientFromRequest +|create.md: Project creation - npx base44 create name -p . +|deploy.md: Deploy - npx base44 deploy -y +|agents-push.md: Agent config - {name:"snake_case", description, instructions, tool_configs:[{entity_name,allowed_operations}|{function_name,description}]} + +[Common Mistakes - NEVER use these hallucinated names] +|SDK: Do NOT use find() → use filter(), findOne() → get(), insert() → create(), remove() → delete(), onChange() → subscribe() +|SDK: Do NOT use signInWithGoogle() → use loginWithProvider('google'), functions.call() → functions.invoke() +|SDK: Do NOT use ai.generate() → use integrations.Core.InvokeLLM({prompt}) +|CLI: Entity names must be PascalCase alphanumeric. File names must be kebab-case.jsonc +|CLI: Agent names must be lowercase with underscores only. Function entry field is "entry" not "entrypoint" +|CLI: Backend functions import from "npm:@base44/sdk" (Deno requires npm: prefix) diff --git a/evals/fixtures/create-support-agent/project/CLAUDE.md b/evals/fixtures/create-support-agent/project/CLAUDE.md new file mode 100644 index 0000000..724ce4d --- /dev/null +++ b/evals/fixtures/create-support-agent/project/CLAUDE.md @@ -0,0 +1,7 @@ +# Base44 Development Guidelines + +When working with Base44 projects, first explore the existing project structure to understand what already exists. Look at existing code, config files, and patterns in use. + +Then consult the AGENTS.md index in the project root for correct API patterns. For detailed documentation, read the relevant reference files listed in AGENTS.md. + +IMPORTANT: Prefer retrieval-led reasoning over pre-training-led reasoning. Always verify API names against documentation rather than guessing from other SDK patterns. diff --git a/evals/fixtures/create-support-agent/project/base44/config.jsonc b/evals/fixtures/create-support-agent/project/base44/config.jsonc new file mode 100644 index 0000000..463a0c4 --- /dev/null +++ b/evals/fixtures/create-support-agent/project/base44/config.jsonc @@ -0,0 +1,4 @@ +{ + "name": "E-Commerce App", + "description": "Online store with order management" +} diff --git a/evals/fixtures/create-support-agent/project/package.json b/evals/fixtures/create-support-agent/project/package.json new file mode 100644 index 0000000..307831b --- /dev/null +++ b/evals/fixtures/create-support-agent/project/package.json @@ -0,0 +1,9 @@ +{ + "name": "ecommerce-app", + "version": "1.0.0", + "type": "module", + "dependencies": { + "@base44/sdk": "latest", + "react": "^18.2.0" + } +} diff --git a/evals/fixtures/create-support-agent/skills/base44-cli/SKILL.md b/evals/fixtures/create-support-agent/skills/base44-cli/SKILL.md new file mode 100644 index 0000000..3c33f05 --- /dev/null +++ b/evals/fixtures/create-support-agent/skills/base44-cli/SKILL.md @@ -0,0 +1,384 @@ +--- +name: base44-cli +description: "The base44 CLI is used for EVERYTHING related to base44 projects: resource configuration (entities, backend functions, ai agents), initialization and actions (resource creation, deployment). This skill is the place for learning about how to configure resources. When you plan or implement a feature, you must learn this skill" +--- + +# Base44 CLI + +Create and manage Base44 apps (projects) using the Base44 CLI tool. + +## ⚡ IMMEDIATE ACTION REQUIRED - Read This First + +This skill activates on ANY mention of "base44" or when a `base44/` folder exists. **DO NOT read documentation files or search the web before acting.** + +**Your first action MUST be:** +1. Check if `base44/config.jsonc` exists in the current directory +2. If **NO** (new project scenario): + - This skill (base44-cli) handles the request + - Guide user through project initialization + - Do NOT activate base44-sdk yet +3. If **YES** (existing project scenario): + - Transfer to base44-sdk skill for implementation + - This skill only handles CLI commands (login, deploy, entities push) + +## Critical: Local Installation Only + +NEVER call `base44` directly. The CLI is installed locally as a dev dependency and must be accessed via a package manager: + +- `npx base44 ` (npm - recommended) +- `yarn base44 ` (yarn) +- `pnpm base44 ` (pnpm) + +WRONG: `base44 login` +RIGHT: `npx base44 login` + +## MANDATORY: Authentication Check at Session Start + +**CRITICAL**: At the very start of every AI session when this skill is activated, you MUST: + +1. **Check authentication status** by running: + ```bash + npx base44 whoami + ``` + +2. **If the user is logged in** (command succeeds and shows an email): + - Continue with the requested task + +3. **If the user is NOT logged in** (command fails or shows an error): + - **STOP immediately** + - **DO NOT proceed** with any CLI operations + - **Ask the user to login manually** by running: + ```bash + npx base44 login + ``` + - Wait for the user to confirm they have logged in before continuing + +**This check is mandatory and must happen before executing any other Base44 CLI commands.** + +## Overview + +The Base44 CLI provides command-line tools for authentication, creating projects, managing entities, and deploying Base44 applications. It is framework-agnostic and works with popular frontend frameworks like Vite, Next.js, and Create React App, Svelte, Vue, and more. + +## When to Use This Skill vs base44-sdk + +**Use base44-cli when:** +- Creating a **NEW** Base44 project from scratch +- Initializing a project in an empty directory +- Directory is missing `base44/config.jsonc` +- User mentions: "create a new project", "initialize project", "setup a project", "start a new Base44 app" +- Deploying, pushing entities, or authenticating via CLI +- Working with CLI commands (`npx base44 ...`) + +**Use base44-sdk when:** +- Building features in an **EXISTING** Base44 project +- `base44/config.jsonc` already exists +- Writing JavaScript/TypeScript code using Base44 SDK +- Implementing functionality, components, or features +- User mentions: "implement", "build a feature", "add functionality", "write code" + +**Skill Dependencies:** +- `base44-cli` is a **prerequisite** for `base44-sdk` in new projects +- If user wants to "create an app" and no Base44 project exists, use `base44-cli` first +- `base44-sdk` assumes a Base44 project is already initialized + +**State Check Logic:** +Before selecting a skill, check: +- IF (user mentions "create/build app" OR "make a project"): + - IF (directory is empty OR no `base44/config.jsonc` exists): + → Use **base44-cli** (project initialization needed) + - ELSE: + → Use **base44-sdk** (project exists, build features) + +## Project Structure + +A Base44 project combines a standard frontend project with a `base44/` configuration folder: + +``` +my-app/ +├── base44/ # Base44 configuration (created by CLI) +│ ├── config.jsonc # Project settings, site config +│ ├── entities/ # Entity schema definitions +│ │ ├── task.jsonc +│ │ └── board.jsonc +│ ├── functions/ # Backend functions (optional) +│ │ └── my-function/ +│ │ ├── function.jsonc +│ │ └── index.ts +│ └── agents/ # Agent configurations (optional) +│ └── support_agent.jsonc +├── src/ # Frontend source code +│ ├── api/ +│ │ └── base44Client.js # Base44 SDK client +│ ├── pages/ +│ ├── components/ +│ └── main.jsx +├── index.html # SPA entry point +├── package.json +└── vite.config.js # Or your framework's config +``` + +**Key files:** +- `base44/config.jsonc` - Project name, description, site build settings +- `base44/entities/*.jsonc` - Data model schemas (see Entity Schema section) +- `base44/agents/*.jsonc` - Agent configurations (optional) +- `src/api/base44Client.js` - Pre-configured SDK client for frontend use + +**config.jsonc example:** +```jsonc +{ + "name": "My App", // Required: project name + "description": "App description", // Optional: project description + "entitiesDir": "./entities", // Optional: default "entities" + "functionsDir": "./functions", // Optional: default "functions" + "agentsDir": "./agents", // Optional: default "agents" + "site": { // Optional: site deployment config + "installCommand": "npm install", // Optional: install dependencies + "buildCommand": "npm run build", // Optional: build command + "serveCommand": "npm run dev", // Optional: local dev server + "outputDirectory": "./dist" // Optional: build output directory + } +} +``` + +**Config properties:** + +| Property | Description | Default | +|----------|-------------|---------| +| `name` | Project name (required) | - | +| `description` | Project description | - | +| `entitiesDir` | Directory for entity schemas | `"entities"` | +| `functionsDir` | Directory for backend functions | `"functions"` | +| `agentsDir` | Directory for agent configs | `"agents"` | +| `site.installCommand` | Command to install dependencies | - | +| `site.buildCommand` | Command to build the project | - | +| `site.serveCommand` | Command to run dev server | - | +| `site.outputDirectory` | Build output directory for deployment | - | + +## Installation + +Install the Base44 CLI as a dev dependency in your project: + +```bash +npm install --save-dev base44 +``` + +**Important:** Never assume or hardcode the `base44` package version. Always install without a version specifier to get the latest version. + +Then run commands using `npx`: + +```bash +npx base44 +``` + +**Note:** All commands in this documentation use `npx base44`. You can also use `yarn base44`, or `pnpm base44` if preferred. + +## Available Commands + +### Authentication + +| Command | Description | Reference | +| --------------- | ----------------------------------------------- | ------------------------------------------- | +| `base44 login` | Authenticate with Base44 using device code flow | [auth-login.md](references/auth-login.md) | +| `base44 logout` | Logout from current device | [auth-logout.md](references/auth-logout.md) | +| `base44 whoami` | Display current authenticated user | [auth-whoami.md](references/auth-whoami.md) | + +### Project Management + +| Command | Description | Reference | +|---------|-------------|-----------| +| `base44 create` | Create a new Base44 project from a template | [create.md](references/create.md) ⚠️ **MUST READ** | +| `base44 link` | Link an existing local project to Base44 | [link.md](references/link.md) | +| `base44 dashboard` | Open the app dashboard in your browser | [dashboard.md](references/dashboard.md) | + +### Deployment + +| Command | Description | Reference | +|---------|-------------|-----------| +| `base44 deploy` | Deploy all resources (entities, functions, site) | [deploy.md](references/deploy.md) | + +### Entity Management + +| Action / Command | Description | Reference | +| ---------------------- | ------------------------------------------- | --------------------------------------------------- | +| Create Entities | Define entities in `base44/entities` folder | [entities-create.md](references/entities-create.md) | +| `base44 entities push` | Push local entities to Base44 | [entities-push.md](references/entities-push.md) | + +#### Entity Schema (Quick Reference) + +ALWAYS follow this exact structure when creating entity files: + +**File naming:** `base44/entities/{kebab-case-name}.jsonc` (e.g., `team-member.jsonc` for `TeamMember`) + +**Schema template:** +```jsonc +{ + "name": "EntityName", + "type": "object", + "properties": { + "field_name": { + "type": "string", + "description": "Field description" + } + }, + "required": ["field_name"] +} +``` + +**Field types:** `string`, `number`, `integer`, `boolean`, `array`, `object`, `binary` +**String formats:** `date`, `date-time`, `time`, `email`, `uri`, `hostname`, `ipv4`, `ipv6`, `uuid`, `file`, `regex`, `richtext` +**For enums:** Add `"enum": ["value1", "value2"]` and optionally `"default": "value1"` +**Entity names:** Must be alphanumeric only (pattern: `/^[a-zA-Z0-9]+$/`) + +For complete documentation, see [entities-create.md](references/entities-create.md). + +### Function Management + +| Action / Command | Description | Reference | +| ------------------------- | --------------------------------------------- | ------------------------------------------------------- | +| Create Functions | Define functions in `base44/functions` folder | [functions-create.md](references/functions-create.md) | +| `base44 functions deploy` | Deploy local functions to Base44 | [functions-deploy.md](references/functions-deploy.md) | + +### Agent Management + +Agents are conversational AI assistants that can interact with users, access your app's entities, and call backend functions. Use these commands to manage agent configurations. + +| Action / Command | Description | Reference | +| ----------------------- | --------------------------------------- | ----------------------------------------------- | +| Create Agents | Define agents in `base44/agents` folder | See Agent Schema below | +| `base44 agents pull` | Pull remote agents to local files | [agents-pull.md](references/agents-pull.md) | +| `base44 agents push` | Push local agents to Base44 | [agents-push.md](references/agents-push.md) | + +**Note:** Agent commands perform full synchronization - pushing replaces all remote agents with local ones, and pulling replaces all local agents with remote ones. + +#### Agent Schema (Quick Reference) + +**File naming:** `base44/agents/{agent_name}.jsonc` (e.g., `support_agent.jsonc`) + +**Schema template:** +```jsonc +{ + "name": "agent_name", + "description": "Brief description of what this agent does", + "instructions": "Detailed instructions for the agent's behavior", + "tool_configs": [ + // Entity tool - gives agent access to entity operations + { "entity_name": "tasks", "allowed_operations": ["read", "create", "update", "delete"] }, + // Backend function tool - gives agent access to a function + { "function_name": "send_email", "description": "Send an email notification" } + ], + "whatsapp_greeting": "Hello! How can I help you today?" +} +``` + +**Naming rules:** +- Agent names must match pattern: `/^[a-z0-9_]+$/` (lowercase alphanumeric with underscores, 1-100 chars) +- Valid: `support_agent`, `order_bot` +- Invalid: `Support-Agent`, `OrderBot` + +**Required fields:** `name`, `description`, `instructions` +**Optional fields:** `tool_configs` (defaults to `[]`), `whatsapp_greeting` + +**Tool config types:** +- **Entity tools**: `entity_name` + `allowed_operations` (array of: `read`, `create`, `update`, `delete`) +- **Backend function tools**: `function_name` + `description` + +### Site Deployment + +| Command | Description | Reference | +| -------------------- | ----------------------------------------- | ------------------------------------------- | +| `base44 site deploy` | Deploy built site files to Base44 hosting | [site-deploy.md](references/site-deploy.md) | + +**SPA only**: Base44 hosting supports Single Page Applications with a single `index.html` entry point. All routes are served from `index.html` (client-side routing). + +## Quick Start + +1. Install the CLI in your project: + ```bash + npm install --save-dev base44 + ``` + +2. Authenticate with Base44: + ```bash + npx base44 login + ``` + +3. Create a new project (ALWAYS provide name and `--path` flag): + ```bash + npx base44 create my-app -p . + ``` + +4. Build and deploy everything: + ```bash + npm run build + npx base44 deploy -y + ``` + +Or deploy individual resources: +- `npx base44 entities push` - Push entities only +- `npx base44 functions deploy` - Deploy functions only +- `npx base44 agents push` - Push agents only +- `npx base44 site deploy -y` - Deploy site only + +## Common Workflows + +### Creating a New Project + +**⚠️ MANDATORY: Before running `base44 create`, you MUST read [create.md](references/create.md) for:** +- **Template selection** - Choose the correct template (`backend-and-client` vs `backend-only`) +- **Correct workflow** - Different templates require different setup steps +- **Common pitfalls** - Avoid folder creation errors that cause failures + +Failure to follow the create.md instructions will result in broken project scaffolding. + +### Linking an Existing Project +```bash +# If you have base44/config.jsonc but no .app.jsonc +npx base44 link --create --name my-app +``` + +### Deploying All Changes +```bash +# Build your project first +npm run build + +# Deploy everything (entities, functions, and site) +npx base44 deploy -y +``` + +### Deploying Individual Resources +```bash +# Push only entities +npx base44 entities push + +# Deploy only functions +npx base44 functions deploy + +# Push only agents +npx base44 agents push + +# Deploy only site +npx base44 site deploy -y +``` + +### Opening the Dashboard +```bash +# Open app dashboard in browser +npx base44 dashboard +``` + +## Authentication + +Most commands require authentication. If you're not logged in, the CLI will automatically prompt you to login. Your session is stored locally and persists across CLI sessions. + +## Troubleshooting + +| Error | Solution | +| --------------------------- | ----------------------------------------------------------------------------------- | +| Not authenticated | Run `npx base44 login` first | +| No entities found | Ensure entities exist in `base44/entities/` directory | +| Entity not recognized | Ensure file uses kebab-case naming (e.g., `team-member.jsonc` not `TeamMember.jsonc`) | +| No functions found | Ensure functions exist in `base44/functions/` with valid `function.jsonc` configs | +| No agents found | Ensure agents exist in `base44/agents/` directory with valid `.jsonc` configs | +| Invalid agent name | Agent names must be lowercase alphanumeric with underscores only | +| No site configuration found | Check that `site.outputDirectory` is configured in project config | +| Site deployment fails | Ensure you ran `npm run build` first and the build succeeded | diff --git a/evals/fixtures/create-support-agent/skills/base44-cli/references/agents-pull.md b/evals/fixtures/create-support-agent/skills/base44-cli/references/agents-pull.md new file mode 100644 index 0000000..6a700f0 --- /dev/null +++ b/evals/fixtures/create-support-agent/skills/base44-cli/references/agents-pull.md @@ -0,0 +1,73 @@ +# base44 agents pull + +Pull AI agent configurations from Base44 to local files. Agents are conversational AI assistants that can interact with users, access your app's entities, and call backend functions. + +## Syntax + +```bash +npx base44 agents pull +``` + +## Authentication + +**Required**: Yes. If not authenticated, you'll be prompted to login first. + +## What It Does + +1. Fetches all agents from Base44 +2. Writes agent files to the `base44/agents/` directory +3. Deletes local agent files that don't exist remotely +4. Reports written and deleted agents + +## Prerequisites + +- Must be run from a Base44 project directory +- Project must be linked to a Base44 app + +## Output + +```bash +$ npx base44 agents pull + +Fetching agents from Base44... +✓ Agents fetched successfully + +Writing agent files... +✓ Agent files written successfully + +Written: support_agent, order_bot +Deleted: old_agent + +Pulled 2 agents to base44/agents +``` + +## Agent Synchronization + +The pull operation synchronizes remote agents to your local files: + +- **Written**: Agent files created or updated from remote +- **Deleted**: Local agent files removed (didn't exist remotely) + +**Warning**: This operation replaces all local agent configurations with remote versions. Any local changes not pushed to Base44 will be overwritten. + +## Error Handling + +If no agents exist on Base44: +```bash +$ npx base44 agents pull +No agents found on Base44 +``` + +## Use Cases + +- Sync agent configurations to a new development machine +- Get the latest agent configurations from your team +- Restore local agent files after accidental deletion +- Start working on an existing project with agents + +## Notes + +- This command syncs agent configurations, not conversation data +- Agent files are stored as `.jsonc` in the `base44/agents/` directory +- The directory location is configurable via `agentsDir` in `config.jsonc` +- Use `base44 agents push` to upload local changes to Base44 diff --git a/evals/fixtures/create-support-agent/skills/base44-cli/references/agents-push.md b/evals/fixtures/create-support-agent/skills/base44-cli/references/agents-push.md new file mode 100644 index 0000000..fa2795b --- /dev/null +++ b/evals/fixtures/create-support-agent/skills/base44-cli/references/agents-push.md @@ -0,0 +1,156 @@ +# base44 agents push + +Push local AI agent configurations to Base44. Agents are conversational AI assistants that can interact with users, access your app's entities, and call backend functions. + +## Syntax + +```bash +npx base44 agents push +``` + +## Authentication + +**Required**: Yes. If not authenticated, you'll be prompted to login first. + +## What It Does + +1. Reads all agent files from the `base44/agents/` directory +2. Validates agent configurations +3. Displays the count of agents to be pushed +4. Uploads agents to the Base44 backend +5. Reports the results: created, updated, and deleted agents + +## Prerequisites + +- Must be run from a Base44 project directory +- Project must have agent definitions in the `base44/agents/` folder + +## Output + +```bash +$ npx base44 agents push + +Found 2 agents to push +Pushing agents to Base44... + +Created: support_agent +Updated: order_bot +Deleted: old_agent + +✓ Agents pushed to Base44 +``` + +## Agent Synchronization + +The push operation synchronizes your local agents with Base44: + +- **Created**: New agents that didn't exist in Base44 +- **Updated**: Existing agents with modified configuration +- **Deleted**: Agents that were removed from your local configuration + +**Warning**: This is a full sync operation. Agents removed locally will be deleted from Base44. + +## Error Handling + +If no agents are found in your project: +```bash +$ npx base44 agents push +No local agents found - this will delete all remote agents +``` + +If an agent has an invalid name: +```bash +$ npx base44 agents push +Error: Agent name must be lowercase alphanumeric with underscores +``` + +## Agent Configuration Schema + +Each agent file should be a `.jsonc` file in `base44/agents/` with this structure: + +```jsonc +{ + "name": "agent_name", // Required: lowercase alphanumeric with underscores, 1-100 chars + "description": "Brief description of what this agent does", // Required: min 1 char + "instructions": "Detailed instructions for the agent's behavior", // Required: min 1 char + "tool_configs": [ // Optional: defaults to [] + // Entity tool - gives agent access to entity operations + { "entity_name": "Task", "allowed_operations": ["read", "create", "update", "delete"] }, + // Backend function tool - gives agent access to a function + { "function_name": "send_email", "description": "Send an email notification" } + ], + "whatsapp_greeting": "Hello! How can I help you today?" // Optional +} +``` + +**Naming rules:** +- **Agent names** must match pattern: `/^[a-z0-9_]+$/` (lowercase alphanumeric with underscores only, 1-100 characters) + - Valid: `support_agent`, `order_bot`, `task_helper` + - Invalid: `Support-Agent`, `OrderBot`, `task helper` +- **Agent file names** must use underscores (matching the agent name) + - Valid: `support_agent.jsonc`, `order_bot.jsonc` + - Invalid: `support-agent.jsonc` (hyphens not allowed) +- **Entity names in `tool_configs`** must use PascalCase (matching the entity's `name` field) + - Valid: `"entity_name": "Task"`, `"entity_name": "TeamMember"` + - Invalid: `"entity_name": "task"`, `"entity_name": "team_member"` + +**Required fields:** +- `name`: Required, must follow naming rules above +- `description`: Required, minimum 1 character +- `instructions`: Required, minimum 1 character +- `tool_configs`: Optional, defaults to empty array +- `whatsapp_greeting`: Optional + +### Common Mistake: Wrong tool_configs Format + +**WRONG** - Do NOT use `tools` with `type` and `entity`: +```jsonc +{ + "name": "my_agent", + "tools": [ // ❌ WRONG + { "type": "entity_query", "entity": "Task" } + ] +} +``` + +**CORRECT** - Use `tool_configs` with `entity_name` and `allowed_operations`: +```jsonc +{ + "name": "my_agent", + "tool_configs": [ // ✅ CORRECT + { "entity_name": "Task", "allowed_operations": ["read"] } + ] +} +``` + +### Best Practices for Agent Instructions + +When giving agents access to entities, be explicit in the instructions about using the tools: + +```jsonc +{ + "name": "support_agent", + "instructions": "You are a helpful support agent.\n\nIMPORTANT: You have access to customer data through entity tools. When users ask about their orders or account:\n1. ALWAYS use the Order entity tool to query their order history\n2. Use the Customer entity tool to look up account details\n3. Analyze the data and provide personalized responses\n\nAlways query the relevant entities first before answering questions about user data.", + "tool_configs": [ + { "entity_name": "Order", "allowed_operations": ["read"] }, + { "entity_name": "Customer", "allowed_operations": ["read"] } + ] +} +``` + +Without explicit instructions to use the entity tools, the agent may not proactively query user data when asked. + +## Use Cases + +- After defining new agents in your project +- When modifying existing agent configurations +- To sync agent changes before testing +- As part of your development workflow when agent behavior changes + +## Notes + +- This command syncs the agent configuration, not conversation data +- Changes are applied to your Base44 project immediately +- Make sure to test agent changes in a development environment first +- Agent definitions are located in the `base44/agents/` directory +- Use `base44 agents pull` to download agents from Base44 diff --git a/evals/fixtures/create-support-agent/skills/base44-cli/references/auth-login.md b/evals/fixtures/create-support-agent/skills/base44-cli/references/auth-login.md new file mode 100644 index 0000000..adebd27 --- /dev/null +++ b/evals/fixtures/create-support-agent/skills/base44-cli/references/auth-login.md @@ -0,0 +1,54 @@ +# base44 login + +Authenticate with Base44 using device code flow. + +## Syntax + +```bash +npx base44 login +``` + +## Authentication + +**Required**: No (this is the login command itself) + +## How It Works + +The login command uses OAuth 2.0 device code flow for authentication: + +1. Generates a device code for authentication +2. Displays a verification code and verification URI +3. Directs you to visit the URI and enter the code +4. Polls for authentication completion (up to device code expiration) +5. Retrieves access and refresh tokens upon successful authentication +6. Fetches and displays your user information +7. Saves authentication data locally with expiration timestamp + +## Interactive Flow + +```bash +$ npx base44 login + +Please visit: https://auth.base44.com/device +Enter code: ABCD-EFGH + +Waiting for authentication... +✓ Successfully authenticated! + +Logged in as: user@example.com +``` + +## Session Management + +- Authentication tokens are stored locally on your device +- Tokens include expiration timestamps +- The session persists across CLI sessions +- Other commands will automatically use your stored credentials +- Use `npx base44 logout` to clear your session +- Use `npx base44 whoami` to check your current authentication status + +## Notes + +- You only need to login once per device +- If your session expires, you'll be prompted to login again when running authenticated commands +- The CLI automatically prompts for login when you run commands that require authentication diff --git a/evals/fixtures/create-support-agent/skills/base44-cli/references/auth-logout.md b/evals/fixtures/create-support-agent/skills/base44-cli/references/auth-logout.md new file mode 100644 index 0000000..33c2ddf --- /dev/null +++ b/evals/fixtures/create-support-agent/skills/base44-cli/references/auth-logout.md @@ -0,0 +1,32 @@ +# base44 logout + +Logout from current device and clear stored authentication data. + +## Syntax + +```bash +npx base44 logout +``` + +## Authentication + +**Required**: No + +## What It Does + +- Deletes stored authentication data from your device +- Clears your local session +- Removes access and refresh tokens + +## Output + +```bash +$ npx base44 logout +Logged out successfully +``` + +## Notes + +- You can logout even if you're not currently logged in (no error) +- After logout, you'll need to run `npx base44 login` again to use authenticated commands +- This only affects the current device; your Base44 account remains active diff --git a/evals/fixtures/create-support-agent/skills/base44-cli/references/auth-whoami.md b/evals/fixtures/create-support-agent/skills/base44-cli/references/auth-whoami.md new file mode 100644 index 0000000..abe450c --- /dev/null +++ b/evals/fixtures/create-support-agent/skills/base44-cli/references/auth-whoami.md @@ -0,0 +1,37 @@ +# base44 whoami + +Display the currently authenticated user. + +## Syntax + +```bash +npx base44 whoami +``` + +## Authentication + +**Required**: Yes. If not authenticated, you'll be prompted to login first. + +## What It Does + +- Reads stored authentication data +- Displays the email of the currently logged-in user + +## Output + +```bash +$ npx base44 whoami +Logged in as: user@example.com +``` + +## Use Cases + +- Verify you're logged in before running other commands +- Check which account you're currently using +- Confirm authentication is working properly +- Useful in scripts or automation to verify credentials + +## Notes + +- If you're not logged in, the command will prompt you to authenticate first +- The email displayed matches your Base44 account email diff --git a/evals/fixtures/create-support-agent/skills/base44-cli/references/create.md b/evals/fixtures/create-support-agent/skills/base44-cli/references/create.md new file mode 100644 index 0000000..7580645 --- /dev/null +++ b/evals/fixtures/create-support-agent/skills/base44-cli/references/create.md @@ -0,0 +1,111 @@ +# base44 create + +Creates a new Base44 project from a template. This command is framework-agnostic and can either scaffold a complete project or add Base44 configuration to an existing project. + +## Critical: Non-Interactive Mode Required + +ALWAYS provide both the project name AND `--path` flag. Without both, the command opens an interactive TUI which agents cannot use properly. + +WRONG: `npx base44 create` +WRONG: `npx base44 create my-app` +RIGHT: `npx base44 create my-app -p ./my-app` + +## Syntax + +```bash +npx base44 create [name] --path [options] +``` + +## Arguments & Options + +| Argument/Option | Description | Required | +|--------|-------------|----------| +| `name` | Project name (positional argument) | Yes* | +| `-p, --path ` | Path where to create the project | Yes* | +| `-t, --template ` | Template ID (see templates below) | No | +| `--deploy` | Build and deploy the site (includes pushing entities) | No | +| `--no-skills` | Skip AI agent skills installation (skills are added by default) | No | + +*Required for non-interactive mode. Both `name` and `--path` must be provided together. + +## Template Selection (CRITICAL - Choose Appropriately) + +**You MUST select the most appropriate template based on user requirements:** + +| Template ID | When to Use | Example Scenarios | +|-------------|-------------|-------------------| +| `backend-and-client` | Creating a NEW full-stack web app from scratch | "Create a task app", "Build me a dashboard", "Make a SaaS app" | +| `backend-only` | Adding Base44 to an EXISTING project OR using a different framework (Next.js, Vue, Svelte, etc.) | "Add Base44 to my project", "I want to use Next.js", "I already have a frontend" | + +**Default Choice:** When the user asks to "create an app" or "build a project" without specifying a particular framework, use `backend-and-client` to provide a complete, production-ready application with Vite + React + Tailwind. + +## The `--path` Flag + +- **For `backend-and-client` template (new projects):** Use a new subfolder path + ```bash + npx base44 create my-app -p ./my-app -t backend-and-client + ``` +- **For `backend-only` template (existing projects):** Use `-p .` in the current directory + ```bash + npx base44 create my-app -p . + ``` + +## Workflow: Using `backend-only` with External Frameworks + +**CRITICAL: The project folder MUST exist BEFORE running `base44 create` with `backend-only`** + +The `backend-only` template only adds Base44 configuration files - it does NOT create a frontend. If you need a frontend with a specific framework: + +```bash +# Step 1: Initialize the frontend project FIRST +npm create vite@latest my-app -- --template react # or vue, svelte, etc. +# OR: npx create-next-app@latest my-app +# OR: any other framework's init command + +# Step 2: Navigate into the created folder +cd my-app + +# Step 3: Install Base44 CLI +npm install --save-dev base44 + +# Step 4: Add Base44 configuration +npx base44 create my-app -p . +``` + +**WARNING:** Do NOT: +- Create an empty folder manually, then try to run `npx create vite` inside it (will fail - folder exists) +- Run `base44 create` with `backend-only` expecting it to create a frontend (it won't) + +**DO:** +- Run the external framework's init command FIRST (it creates its own folder) +- Then run `base44 create` inside that folder with `-p .` + +## Examples + +```bash +# RECOMMENDED: Create full-stack project (for new apps) +npx base44 create my-app -p ./my-app -t backend-and-client + +# Create full-stack and deploy in one step +npx base44 create my-app -p ./my-app -t backend-and-client --deploy + +# Add Base44 to EXISTING project (must be inside the project folder) +npx base44 create my-app -p . + +# Add Base44 to existing project and deploy +npx base44 create my-app -p . --deploy + +# Create without adding AI agent skills +npx base44 create my-app -p . --no-skills +``` + +## What It Does + +1. Applies the selected template to the target path +2. Creates a `base44/` folder with configuration files +3. Registers the project with Base44 backend +4. Creates `base44/.app.jsonc` with the app ID +5. If `--deploy` is used: + - Pushes any entities defined in `base44/entities/` + - Runs install and build commands (for templates with frontend) + - Deploys the site to Base44 hosting diff --git a/evals/fixtures/create-support-agent/skills/base44-cli/references/dashboard.md b/evals/fixtures/create-support-agent/skills/base44-cli/references/dashboard.md new file mode 100644 index 0000000..95a534a --- /dev/null +++ b/evals/fixtures/create-support-agent/skills/base44-cli/references/dashboard.md @@ -0,0 +1,35 @@ +# base44 dashboard + +Opens the Base44 app dashboard in your default web browser. + +## Syntax + +```bash +npx base44 dashboard +``` + +## Options + +This command has no options. + +## What It Does + +1. Reads the project's app ID from `base44/.app.jsonc` +2. Opens the dashboard URL in your default browser + +## Example + +```bash +# Open dashboard for current project +npx base44 dashboard +``` + +## Requirements + +- Must be run from a linked Base44 project directory (contains `base44/.app.jsonc`) +- Must be authenticated (run `npx base44 login` first) + +## Notes + +- The dashboard provides a web interface to manage your app's entities, functions, users, and settings +- If you're not in a project directory, the command will fail with an error diff --git a/evals/fixtures/create-support-agent/skills/base44-cli/references/deploy.md b/evals/fixtures/create-support-agent/skills/base44-cli/references/deploy.md new file mode 100644 index 0000000..500526f --- /dev/null +++ b/evals/fixtures/create-support-agent/skills/base44-cli/references/deploy.md @@ -0,0 +1,86 @@ +# base44 deploy + +Deploys all project resources (entities, functions, and site) to Base44 in a single command. + +## Syntax + +```bash +npx base44 deploy [options] +``` + +## Options + +| Option | Description | +|--------|-------------| +| `-y, --yes` | Skip confirmation prompt | + +## What It Deploys + +The command automatically detects and deploys: + +1. **Entities** - All `.jsonc` files in `base44/entities/` +2. **Functions** - All functions in `base44/functions/` +3. **Agents** - All agent configurations in `base44/agents/` +4. **Site** - Built files from `site.outputDirectory` (if configured) + +## Examples + +```bash +# Interactive mode - shows what will be deployed and asks for confirmation +npx base44 deploy + +# Non-interactive - skip confirmation (for CI/CD or agent use) +npx base44 deploy -y +``` + +## Typical Workflow + +```bash +# 1. Make your changes (entities, functions, frontend code) + +# 2. Build the frontend (if you have one) +npm run build + +# 3. Deploy everything +npx base44 deploy -y +``` + +## What It Does + +1. Reads project configuration from `base44/config.jsonc` +2. Detects available resources (entities, functions, site) +3. Shows a summary of what will be deployed +4. Asks for confirmation (unless `-y` flag is used) +5. Deploys all resources in sequence: + - Pushes entity schemas + - Deploys functions + - Pushes agent configurations + - Uploads site files +6. Displays the dashboard URL and app URL (if site was deployed) + +## Requirements + +- Must be run from a linked Base44 project directory +- Must be authenticated (run `npx base44 login` first) +- For site deployment, must run `npm run build` first + +## Output + +After successful deployment: +- **Dashboard**: Link to your app's management dashboard +- **App URL**: Your deployed site's public URL (if site was included) + +## Notes + +- If no resources are found, the command exits with a message +- Use individual commands (`entities push`, `functions deploy`, `site deploy`) if you only want to deploy specific resources +- The site must be built before deployment - this command does not run `npm run build` for you + +## Related Commands + +| Command | Description | +|---------|-------------| +| `base44 entities push` | Push only entities | +| `base44 functions deploy` | Deploy only functions | +| `base44 agents push` | Push only agents | +| `base44 site deploy` | Deploy only the site | diff --git a/evals/fixtures/create-support-agent/skills/base44-cli/references/entities-create.md b/evals/fixtures/create-support-agent/skills/base44-cli/references/entities-create.md new file mode 100644 index 0000000..29b40aa --- /dev/null +++ b/evals/fixtures/create-support-agent/skills/base44-cli/references/entities-create.md @@ -0,0 +1,552 @@ +# Creating Entities + +Base44 entities are defined locally in your project and then pushed to the Base44 backend. + +## Critical: File Naming + +Entity files MUST use kebab-case naming: `{kebab-case-name}.jsonc` + +| Entity Name | File Name | +|-------------|-----------| +| `Task` | `task.jsonc` | +| `TeamMember` | `team-member.jsonc` | +| `ActivityLog` | `activity-log.jsonc` | + +WRONG: `TeamMember.jsonc`, `teamMember.jsonc` +RIGHT: `team-member.jsonc` + +## Table of Contents + +- [Creating Entities](#creating-entities) + - [Entity Directory](#entity-directory) + - [How to Create an Entity](#how-to-create-an-entity) + - [Entity Schema Structure](#entity-schema-structure) + - [Supported Field Types](#supported-field-types) + - [Field Properties](#field-properties) + - [Complete Example](#complete-example) + - [Naming Conventions](#naming-conventions) + - [Relationships Between Entities](#relationships-between-entities) + - [Row Level Security (RLS)](#row-level-security-rls) + - [Field Level Security (FLS)](#field-level-security-fls) + - [Pushing Entities](#pushing-entities) + +## Entity Directory + +All entity definitions must be placed in the `base44/entities/` folder in your project root. Each entity is defined in its own `.jsonc` file. + +Example structure: +``` +my-app/ + base44/ + entities/ + user.jsonc + product.jsonc + order.jsonc +``` + +## How to Create an Entity + +1. Create a new `.jsonc` file in the `base44/entities/` directory +2. Define your entity schema following the structure below +3. Push the changes to Base44 using the CLI + +## Entity Schema Structure + +Each entity file follows a JSON Schema-like structure: + +```jsonc +{ + "name": "EntityName", // PascalCase entity name + "type": "object", // Always "object" + "properties": { + // Define your fields here + }, + "required": ["field1"] // Array of required field names +} +``` + +### Common Mistake: Nested Schema Property + +**WRONG** - Do NOT wrap properties in a `schema` object: +```jsonc +{ + "name": "Task", + "description": "A task entity", + "schema": { // ❌ WRONG - don't use nested "schema" + "type": "object", + "properties": { ... } + } +} +``` + +**CORRECT** - Put `type` and `properties` at the top level: +```jsonc +{ + "name": "Task", + "description": "A task entity", + "type": "object", // ✅ CORRECT - top level + "properties": { ... } // ✅ CORRECT - top level +} +``` + +This is a common mistake that will cause "Invalid schema: Schema must have a 'type' field" errors when pushing entities. + +## Supported Field Types + +### String + +Basic text field: +```jsonc +{ + "title": { + "type": "string", + "description": "Task title" + } +} +``` + +With format: +```jsonc +{ + "due_date": { + "type": "string", + "format": "date", + "description": "Due date" + } +} +``` + +Available formats: `date`, `date-time`, `time`, `email`, `uri`, `hostname`, `ipv4`, `ipv6`, `uuid`, `file`, `regex`, `richtext` + +### String with Enum + +Constrained to specific values: +```jsonc +{ + "status": { + "type": "string", + "enum": ["todo", "in_progress", "done"], + "default": "todo", + "description": "Current status" + } +} +``` + +### Number + +```jsonc +{ + "position": { + "type": "number", + "description": "Position for ordering" + } +} +``` + +### Integer + +For whole numbers only: +```jsonc +{ + "quantity": { + "type": "integer", + "description": "Item quantity", + "minimum": 0, + "maximum": 1000 + } +} +``` + +### Binary + +For file/blob data: +```jsonc +{ + "attachment": { + "type": "binary", + "description": "File attachment" + } +} +``` + +### Boolean + +```jsonc +{ + "notify_on_change": { + "type": "boolean", + "default": true, + "description": "Enable notifications" + } +} +``` + +### Array of Strings + +```jsonc +{ + "labels": { + "type": "array", + "items": { "type": "string" }, + "description": "Task labels/tags" + } +} +``` + +### Array of Objects + +```jsonc +{ + "attachments": { + "type": "array", + "description": "File attachments", + "items": { + "type": "object", + "properties": { + "name": { "type": "string" }, + "url": { "type": "string" }, + "type": { "type": "string" } + } + } + } +} +``` + +## Field Properties + +| Property | Description | +| ------------- | ---------------------------------------------------------------------------------------- | +| `type` | Data type: `string`, `number`, `integer`, `boolean`, `array`, `object`, `binary` | +| `description` | Human-readable description of the field | +| `enum` | Array of allowed values (for strings) | +| `enumNames` | Human-readable labels for enum values (same order as `enum`) | +| `default` | Default value when not provided | +| `format` | Format hint: `date`, `date-time`, `time`, `email`, `uri`, `hostname`, `ipv4`, `ipv6`, `uuid`, `file`, `regex`, `richtext` | +| `items` | Schema for array items | +| `properties` | Nested properties for object types | +| `$ref` | Reference to another schema definition | +| `minLength` | Minimum string length | +| `maxLength` | Maximum string length | +| `pattern` | Regex pattern for string validation | +| `minimum` | Minimum value for numbers | +| `maximum` | Maximum value for numbers | +| `rls` | Field-level security rules (see Field Level Security section) | + +## Complete Example + +Here's a complete entity definition for a Task: + +```jsonc +{ + "name": "Task", + "type": "object", + "properties": { + "title": { + "type": "string", + "description": "Task title" + }, + "description": { + "type": "string", + "description": "Task description" + }, + "status": { + "type": "string", + "enum": ["todo", "in_progress", "done"], + "default": "todo", + "description": "Current status of the task" + }, + "board_id": { + "type": "string", + "description": "Board this task belongs to" + }, + "assignee_email": { + "type": "string", + "description": "Email of assigned user" + }, + "priority": { + "type": "string", + "enum": ["low", "medium", "high"], + "default": "medium", + "description": "Task priority" + }, + "due_date": { + "type": "string", + "format": "date", + "description": "Due date" + }, + "labels": { + "type": "array", + "items": { "type": "string" }, + "description": "Task labels/tags" + } + }, + "required": ["title"] +} +``` + +## Naming Conventions + +- **Entity name**: Use PascalCase with alphanumeric characters only (e.g., `Task`, `TeamMember`, `ActivityLog`) + - Must match pattern: `/^[a-zA-Z0-9]+$/` + - Valid: `Task`, `TeamMember`, `Order123` + - Invalid: `Team_Member`, `Team-Member`, `Team Member` +- **File name**: Use kebab-case matching the entity (e.g., `task.jsonc`, `team-member.jsonc`, `activity-log.jsonc`) +- **Field names**: Use snake_case (e.g., `board_id`, `user_email`, `due_date`) + +## Relationships Between Entities + +To create relationships between entities, use ID reference fields: + +```jsonc +{ + "board_id": { + "type": "string", + "description": "Board this task belongs to" + }, + "team_id": { + "type": "string", + "description": "Associated team ID" + } +} +``` + +## Row Level Security (RLS) + +Row Level Security (RLS) controls which records users can access based on their identity and attributes. RLS rules are defined per entity inside the `rls` field of the schema. + +**Important:** If no RLS is defined, all records are accessible to all users. + +### RLS Operations + +RLS supports five operations: + +| Operation | Description | +|-----------|-------------| +| `create` | Control who can add new records | +| `read` | Control who can view records | +| `update` | Control who can modify records | +| `delete` | Control who can remove records | +| `write` | Shorthand for `create`, `update`, and `delete` combined | + +### Permission Values + +Each operation accepts one of the following values: + +1. **`true`** - Allow all users (including anonymous/unauthenticated) +2. **`false`** - Block all users +3. **Condition object** - Allow users matching the condition + +### Template Variables + +Use template variables to reference the current user's attributes: + +| Template | Description | +|----------|-------------| +| `{{user.id}}` | The user's ID | +| `{{user.email}}` | The user's email | +| `{{user.role}}` | The user's role | +| `{{user.data.field_name}}` | Custom field from the user's `data` object | + +### Built-in Entity Attributes + +Every entity record has these built-in attributes available for RLS rules: + +| Attribute | Description | +|-----------|-------------| +| `id` | Unique record identifier | +| `created_date` | Timestamp when record was created | +| `updated_date` | Timestamp when record was last updated | +| `created_by` | Email of the user who created the record | + +### Rule Types + +There are two condition types you can use: + +**1. Entity-to-user comparison** - Compare record fields to the current user's values: +```jsonc +{ + "created_by": "{{user.email}}" +} +``` + +**2. User condition check** - Check user properties directly using `user_condition`: +```jsonc +{ + "user_condition": { "role": "admin" } +} +``` + +**Important limitations:** +- `user_condition` only supports **simple equality** (e.g., `{ "role": "admin" }`) +- For `data.*` field comparisons, you can use operators: `$in`, `$nin`, `$ne`, `$all` +- Logical operators `$or`, `$and`, `$nor` are available for combining conditions +- You cannot filter by entity field values directly (e.g., `{"status": "published"}`) +- Only user-related conditions are allowed + +### RLS Examples + +**Owner-only access:** +```jsonc +{ + "created_by": "{{user.email}}" +} +``` + +**Department-based access:** +```jsonc +{ + "data.department": "{{user.data.department}}" +} +``` + +**Admin-only access:** +```jsonc +{ + "user_condition": { "role": "admin" } +} +``` + +**Complete RLS configuration:** +```jsonc +{ + "name": "Task", + "type": "object", + "properties": { + "title": { + "type": "string", + "description": "Task title" + }, + "status": { + "type": "string", + "enum": ["todo", "in_progress", "done"], + "default": "todo" + } + }, + "required": ["title"], + "rls": { + "create": true, + "read": { "created_by": "{{user.email}}" }, + "update": { "created_by": "{{user.email}}" }, + "delete": { "created_by": "{{user.email}}" } + } +} +``` + +### Common RLS Patterns + +**Public create, admin-only management (e.g., contact forms, waitlists):** +```jsonc +{ + "rls": { + "create": true, + "read": { "user_condition": { "role": "admin" } }, + "update": { "user_condition": { "role": "admin" } }, + "delete": { "user_condition": { "role": "admin" } } + } +} +``` + +**Owner-only access:** +```jsonc +{ + "rls": { + "create": true, + "read": { "created_by": "{{user.email}}" }, + "update": { "created_by": "{{user.email}}" }, + "delete": { "created_by": "{{user.email}}" } + } +} +``` + +**Logged-in users only:** +```jsonc +{ + "rls": { + "create": { "user_condition": { "id": "{{user.id}}" } }, + "read": true, + "update": { "created_by": "{{user.email}}" }, + "delete": { "created_by": "{{user.email}}" } + } +} +``` + +### Limitations + +- **user_condition is equality only:** `user_condition` only supports exact match (e.g., `{ "role": "admin" }`) - no operators +- **No comparison operators on user_condition:** `$gt`, `$lt`, `$regex`, `$expr`, `$where` are NOT supported for user conditions +- **No entity field filtering:** Cannot filter by entity field values (e.g., `{"status": "published"}`) +- **No deeply nested templates:** Templates like `{{user.data.profile.department}}` may not work + +**Supported operators:** +- **Logical operators:** `$or`, `$and`, `$nor` for combining multiple conditions +- **Field operators (for `data.*` fields only):** `$in`, `$nin`, `$ne`, `$all` + +### Complex Access Patterns + +For complex access patterns that require multiple conditions (e.g., "owner OR admin"), you have two options: + +1. **Use the Base44 Dashboard UI** - The dashboard allows adding multiple rules per operation with OR logic +2. **Use separate entities** - Split data into multiple entities with different access rules +3. **Use backend functions** - Implement custom access logic in backend functions + +## Field Level Security (FLS) + +Field Level Security allows you to control access to individual fields within an entity. FLS rules are defined within each field's schema using the `rls` property. + +### FLS Operations + +FLS supports the same operations as entity-level RLS: + +| Operation | Description | +|-----------|-------------| +| `create` | Control who can set this field when creating records | +| `read` | Control who can view this field | +| `update` | Control who can modify this field | +| `delete` | Control who can clear this field | +| `write` | Shorthand for `create`, `update`, and `delete` combined | + +### FLS Example + +```jsonc +{ + "name": "Employee", + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "Employee name" + }, + "salary": { + "type": "number", + "description": "Employee salary", + "rls": { + "read": { "user_condition": { "role": "hr" } }, + "update": { "user_condition": { "role": "hr" } } + } + }, + "department": { + "type": "string", + "description": "Department name" + } + }, + "required": ["name"] +} +``` + +In this example, only users with the `hr` role can read or update the `salary` field. All users with access to the entity can read/update other fields. + +### FLS Notes + +- If no field-level RLS is defined, the field inherits the entity-level RLS rules +- FLS rules follow the same condition format as entity-level RLS +- Use FLS for sensitive fields like salary, SSN, or internal notes + +## Pushing Entities + +The `entities push` command will push all entities that exist in the `base44/entities` folder. + +```bash +npx base44 entities push +``` + +For more details on the push command, see [entities-push.md](entities-push.md). diff --git a/evals/fixtures/create-support-agent/skills/base44-cli/references/entities-push.md b/evals/fixtures/create-support-agent/skills/base44-cli/references/entities-push.md new file mode 100644 index 0000000..63b92d7 --- /dev/null +++ b/evals/fixtures/create-support-agent/skills/base44-cli/references/entities-push.md @@ -0,0 +1,71 @@ +# base44 entities push + +Push local entity definitions to Base44. + +## Syntax + +```bash +npx base44 entities push +``` + +## Authentication + +**Required**: Yes. If not authenticated, you'll be prompted to login first. + +## What It Does + +1. Pushes all entities that exist in the `base44/entities` folder +2. Validates that entities exist in the folder +3. Displays the count of entities to be pushed +4. Uploads entities to the Base44 backend +5. Reports the results: created, updated, and deleted entities + +## Prerequisites + +- Must be run from a Base44 project directory +- Project must have entity definitions in the `base44/entities` folder + +## Output + +```bash +$ npx base44 entities push + +Found 3 entities to push +Pushing entities to Base44... + +Created: User, Post +Updated: Comment +Deleted: OldEntity + +✓ Entities pushed successfully +``` + +## Entity Synchronization + +The push operation synchronizes your local entity schema with Base44: + +- **Created**: New entities that didn't exist in Base44 +- **Updated**: Existing entities with modified schema or configuration +- **Deleted**: Entities that were removed from your local configuration + +## Error Handling + +If no entities are found in your project: +```bash +$ npx base44 entities push +No entities found in project +``` + +## Use Cases + +- After defining new entities in your project +- When modifying existing entity schemas +- To sync entity changes before deploying +- As part of your development workflow when data models change + +## Notes + +- This command syncs the entity schema/structure, not the actual data +- Changes are applied to your Base44 project immediately +- Make sure to test entity changes in a development environment first +- Entity definitions are located in the `base44/entities/` directory diff --git a/evals/fixtures/create-support-agent/skills/base44-cli/references/functions-create.md b/evals/fixtures/create-support-agent/skills/base44-cli/references/functions-create.md new file mode 100644 index 0000000..c523cdc --- /dev/null +++ b/evals/fixtures/create-support-agent/skills/base44-cli/references/functions-create.md @@ -0,0 +1,229 @@ +# Creating Functions + +Base44 functions are serverless backend functions that run on Deno. They are defined locally in your project and deployed to the Base44 backend. + +## Function Directory + +All function definitions must be placed in the `base44/functions/` folder in your project. Each function lives in its own subdirectory with a configuration file and entry point. + +Example structure: +``` +my-app/ + base44/ + functions/ + process-order/ + function.jsonc + index.ts + send-notification/ + function.jsonc + index.ts +``` + +## How to Create a Function + +1. Create a new directory in `base44/functions/` with your function name (use kebab-case) +2. Create a `function.jsonc` configuration file in the directory +3. Create the entry point file (e.g., `index.ts`) +4. Deploy the function using the CLI + +## Function Configuration + +Each function requires a `function.jsonc` configuration file: + +```jsonc +{ + "name": "my-function", + "entry": "index.ts" +} +``` + +### Configuration Properties + +| Property | Description | Required | +|----------|-------------|----------| +| `name` | Function name (must match `/^[^.]+$/` - no dots allowed) | Yes | +| `entry` | Entry point file path relative to the function directory (min 1 char) | Yes | + +## Entry Point File + +Functions run on Deno and must export using `Deno.serve()`. Use `npm:` prefix for npm packages. + +```typescript +import { createClientFromRequest } from "npm:@base44/sdk"; + +Deno.serve(async (req) => { + // Get authenticated client from request + const base44 = createClientFromRequest(req); + + // Parse input + const { orderId, action } = await req.json(); + + // Your logic here + const order = await base44.entities.Orders.get(orderId); + + // Return response + return Response.json({ + success: true, + order: order + }); +}); +``` + +### Request Object + +The function receives a standard Deno `Request` object: +- `req.json()` - Parse JSON body +- `req.text()` - Get raw text body +- `req.headers` - Access request headers +- `req.method` - HTTP method + +### Response Object + +Return using `Response.json()` for JSON responses: + +```typescript +// Success response +return Response.json({ data: result }); + +// Error response with status code +return Response.json({ error: "Something went wrong" }, { status: 400 }); + +// Not found +return Response.json({ error: "Order not found" }, { status: 404 }); +``` + +## Complete Example + +### Directory Structure +``` +base44/ + functions/ + process-order/ + function.jsonc + index.ts +``` + +### function.jsonc +```jsonc +{ + "name": "process-order", + "entry": "index.ts" +} +``` + +### index.ts +```typescript +import { createClientFromRequest } from "npm:@base44/sdk"; + +Deno.serve(async (req) => { + try { + const base44 = createClientFromRequest(req); + const { orderId } = await req.json(); + + // Validate input + if (!orderId) { + return Response.json( + { error: "Order ID is required" }, + { status: 400 } + ); + } + + // Fetch and process the order + const order = await base44.entities.Orders.get(orderId); + if (!order) { + return Response.json( + { error: "Order not found" }, + { status: 404 } + ); + } + + return Response.json({ + success: true, + orderId: order.id, + processedAt: new Date().toISOString() + }); + + } catch (error) { + return Response.json( + { error: error.message }, + { status: 500 } + ); + } +}); +``` + +## Using Service Role Access + +For admin-level operations, use `asServiceRole`: + +```typescript +import { createClientFromRequest } from "npm:@base44/sdk"; + +Deno.serve(async (req) => { + const base44 = createClientFromRequest(req); + + // Check user is authenticated + const user = await base44.auth.me(); + if (!user) { + return Response.json({ error: "Unauthorized" }, { status: 401 }); + } + + // Use service role for admin operations + const allOrders = await base44.asServiceRole.entities.Orders.list(); + + return Response.json({ orders: allOrders }); +}); +``` + +## Using Secrets + +Access environment variables configured in the app dashboard: + +```typescript +Deno.serve(async (req) => { + // Access environment variables (configured in app settings) + const apiKey = Deno.env.get("STRIPE_API_KEY"); + + const response = await fetch("https://api.stripe.com/v1/charges", { + headers: { + "Authorization": `Bearer ${apiKey}` + } + }); + + return Response.json(await response.json()); +}); +``` + +## Naming Conventions + +- **Directory name**: Use kebab-case (e.g., `process-order`, `send-notification`) +- **Function name**: Match the directory name, must match pattern `/^[^.]+$/` (no dots allowed) + - Valid: `process-order`, `send_notification`, `myFunction` + - Invalid: `process.order`, `send.notification.v2` +- **Entry file**: Typically `index.ts` or `index.js` + +## Deploying Functions + +After creating your function, deploy it to Base44: + +```bash +npx base44 functions deploy +``` + +For more details on deploying, see [functions-deploy.md](functions-deploy.md). + +## Notes + +- Functions run on Deno runtime, not Node.js +- Use `npm:` prefix for npm packages (e.g., `npm:@base44/sdk`) +- Use `createClientFromRequest(req)` to get a client that inherits the caller's auth context +- Configure secrets via app dashboard for API keys +- Make sure to handle errors gracefully and return appropriate HTTP status codes + +## Common Mistakes + +| Wrong | Correct | Why | +|-------|---------|-----| +| `functions/myFunction.js` (single file) | `functions/my-function/index.ts` + `function.jsonc` | Functions require subdirectory with config | +| `import { ... } from "@base44/sdk"` | `import { ... } from "npm:@base44/sdk"` | Deno requires `npm:` prefix for npm packages | +| `MyFunction` or `myFunction` directory | `my-function` directory | Use kebab-case for directory names | diff --git a/evals/fixtures/create-support-agent/skills/base44-cli/references/functions-deploy.md b/evals/fixtures/create-support-agent/skills/base44-cli/references/functions-deploy.md new file mode 100644 index 0000000..f89d796 --- /dev/null +++ b/evals/fixtures/create-support-agent/skills/base44-cli/references/functions-deploy.md @@ -0,0 +1,78 @@ +# base44 functions deploy + +Deploy local function definitions to Base44. + +## Syntax + +```bash +npx base44 functions deploy +``` + +## Authentication + +**Required**: Yes. If not authenticated, you'll be prompted to login first. + +## What It Does + +1. Scans the `base44/functions/` directory for function definitions +2. Validates that functions exist and have valid configurations +3. Displays the count of functions to be deployed +4. Uploads function code and configuration to Base44 +5. Reports the results: deployed and deleted functions + +## Prerequisites + +- Must be run from a Base44 project directory +- Project must have function definitions in the `base44/functions/` folder +- Each function must have a valid `function.jsonc` config file + +## Output + +```bash +$ npx base44 functions deploy + +Found 2 functions to deploy +Deploying functions to Base44... + +Deployed: process-order, send-notification +Deleted: old-function + +✓ Functions deployed successfully +``` + +## Function Synchronization + +The deploy operation synchronizes your local functions with Base44: + +- **Deployed**: Functions that were created or updated +- **Deleted**: Functions that were removed from your local configuration + +## Error Handling + +If no functions are found in your project: +```bash +$ npx base44 functions deploy +No functions found. Create functions in the 'functions' directory. +``` + +If a function has configuration errors: +```bash +$ npx base44 functions deploy +Function deployment errors: +'my-function' function: Entry point cannot be empty +``` + +## Use Cases + +- After creating new functions in your project +- When modifying existing function code or configuration +- To sync function changes before testing +- As part of your development workflow when backend logic changes + +## Notes + +- This command deploys the function code and configuration +- Changes are applied to your Base44 project immediately +- Make sure to test functions in a development environment first +- Function definitions are located in the `base44/functions/` directory +- For how to create functions, see [functions-create.md](functions-create.md) diff --git a/evals/fixtures/create-support-agent/skills/base44-cli/references/link.md b/evals/fixtures/create-support-agent/skills/base44-cli/references/link.md new file mode 100644 index 0000000..0b009ce --- /dev/null +++ b/evals/fixtures/create-support-agent/skills/base44-cli/references/link.md @@ -0,0 +1,81 @@ +# base44 link + +Links an existing local Base44 project to a Base44 app in the cloud. Use this when you have a `base44/config.jsonc` but haven't connected it to a Base44 app yet. + +## Critical: When to Use Link vs Create + +| Scenario | Command | +|----------|---------| +| Starting fresh, no `base44/` folder | `npx base44 create` | +| Have `base44/config.jsonc` but no `.app.jsonc` | `npx base44 link` | +| Project already linked (has `.app.jsonc`) | Already done, use `deploy` | + +## Syntax + +```bash +npx base44 link [options] +``` + +## Options + +| Option | Description | Required | +|--------|-------------|----------| +| `-c, --create` | Create a new project (skip selection prompt) | No | +| `-n, --name ` | Project name (required when `--create` is used) | With `--create` | +| `-d, --description ` | Project description | No | +| `-p, --projectId ` | Project ID to link to an existing project (skip selection prompt) | No | + +## Non-Interactive Mode + +For CI/CD or agent use: + +**Create a new project:** +```bash +npx base44 link --create --name my-app +``` + +**Link to an existing project:** +```bash +npx base44 link --projectId +``` + +WRONG: `npx base44 link --create` (missing --name) +WRONG: `npx base44 link --create --projectId ` (cannot use both) +RIGHT: `npx base44 link --create --name my-app` +RIGHT: `npx base44 link --projectId ` + +## Examples + +```bash +# Interactive mode - prompts for project details +npx base44 link + +# Non-interactive - create and link in one step +npx base44 link --create --name my-app + +# With description +npx base44 link --create --name my-app --description "My awesome app" + +# Link to a specific existing project by ID +npx base44 link --projectId abc123 +``` + +## What It Does + +1. Finds the `base44/config.jsonc` in the current directory (or parent directories) +2. Verifies no `.app.jsonc` exists (project not already linked) +3. Either: + - Creates a new Base44 app in the cloud (with `--create`), OR + - Links to an existing project (with `--projectId` or interactive selection) +4. Writes the app ID to `base44/.app.jsonc` + +## Requirements + +- Must have `base44/config.jsonc` in the project +- Must NOT have `base44/.app.jsonc` (use `deploy` if already linked) +- Must be authenticated (run `npx base44 login` first) + +## Notes + +- After linking, you can deploy resources with `npx base44 deploy` +- The `.app.jsonc` file should be git-ignored (contains your app ID) diff --git a/evals/fixtures/create-support-agent/skills/base44-cli/references/rls-examples.md b/evals/fixtures/create-support-agent/skills/base44-cli/references/rls-examples.md new file mode 100644 index 0000000..7d3ef42 --- /dev/null +++ b/evals/fixtures/create-support-agent/skills/base44-cli/references/rls-examples.md @@ -0,0 +1,463 @@ +# RLS Examples + +Practical Row-Level Security patterns for common application types. + +**Important:** Base44 RLS supports: +- **Logical operators:** `$or`, `$and`, `$nor` for combining conditions +- **Field operators (for `data.*` fields):** `$in`, `$nin`, `$ne`, `$all` +- **user_condition:** Equality only (no operators) + +## Contents +- [Simple Patterns (JSON Schema)](#simple-patterns-json-schema) +- [Using Operators](#using-operators) +- [Field-Level Security Examples](#field-level-security-examples) +- [Complex Patterns (Dashboard UI or Backend)](#complex-patterns-dashboard-ui-or-backend) +- [Best Practices](#best-practices) + +--- + +## Simple Patterns (JSON Schema) + +These patterns work with the JSON schema RLS format. + +### Todo App - Owner-only access + +Users see and manage only their own tasks. + +```jsonc +{ + "name": "Task", + "type": "object", + "properties": { + "title": { "type": "string" }, + "description": { "type": "string" }, + "completed": { "type": "boolean" }, + "priority": { "type": "string", "enum": ["low", "medium", "high"] }, + "due_date": { "type": "string", "format": "date" } + }, + "rls": { + "create": true, + "read": { "created_by": "{{user.email}}" }, + "update": { "created_by": "{{user.email}}" }, + "delete": { "created_by": "{{user.email}}" } + } +} +``` + +### Contact Form - Public create, admin-only read + +Anyone can submit, only admins can view submissions. + +```jsonc +{ + "name": "ContactSubmission", + "type": "object", + "properties": { + "name": { "type": "string" }, + "email": { "type": "string", "format": "email" }, + "message": { "type": "string" } + }, + "rls": { + "create": true, + "read": { "user_condition": { "role": "admin" } }, + "update": { "user_condition": { "role": "admin" } }, + "delete": { "user_condition": { "role": "admin" } } + } +} +``` + +### User Profile - Self-management + +Users can only access their own profile. + +```jsonc +{ + "name": "UserProfile", + "type": "object", + "properties": { + "name": { "type": "string" }, + "avatar_url": { "type": "string" }, + "bio": { "type": "string" }, + "preferences": { "type": "object" } + }, + "rls": { + "create": true, + "read": { "created_by": "{{user.email}}" }, + "update": { "created_by": "{{user.email}}" }, + "delete": { "created_by": "{{user.email}}" } + } +} +``` + +### Department Data - Same department access + +Users can only see records from their department. + +```jsonc +{ + "name": "DepartmentAnnouncement", + "type": "object", + "properties": { + "title": { "type": "string" }, + "content": { "type": "string" }, + "department": { "type": "string" } + }, + "rls": { + "create": { "user_condition": { "role": "manager" } }, + "read": { "data.department": "{{user.data.department}}" }, + "update": { "user_condition": { "role": "manager" } }, + "delete": { "user_condition": { "role": "admin" } } + } +} +``` + +### Subscription - Admin-managed, user-readable via email field + +```jsonc +{ + "name": "Subscription", + "type": "object", + "properties": { + "user_email": { "type": "string" }, + "tier": { "type": "string", "enum": ["free", "basic", "pro", "enterprise"] }, + "credits": { "type": "number" }, + "renewal_date": { "type": "string", "format": "date" } + }, + "rls": { + "create": { "user_condition": { "role": "admin" } }, + "read": { "data.user_email": "{{user.email}}" }, + "update": { "user_condition": { "role": "admin" } }, + "delete": { "user_condition": { "role": "admin" } } + } +} +``` + +**Note:** This pattern only allows users to read their own subscription. Admins need to use the Dashboard UI to configure additional read access for themselves. + +### Private Data - Owner-only + +```jsonc +{ + "name": "PrivateNotes", + "type": "object", + "properties": { + "title": { "type": "string" }, + "content": { "type": "string" }, + "tags": { "type": "array", "items": { "type": "string" } } + }, + "rls": { + "create": true, + "read": { "created_by": "{{user.email}}" }, + "update": { "created_by": "{{user.email}}" }, + "delete": { "created_by": "{{user.email}}" } + } +} +``` + +### Public Read, Authenticated Write + +Anyone can read, only logged-in users can create/edit their own records. + +```jsonc +{ + "name": "BlogPost", + "type": "object", + "properties": { + "title": { "type": "string" }, + "content": { "type": "string" }, + "author_email": { "type": "string" } + }, + "rls": { + "create": true, + "read": true, + "update": { "created_by": "{{user.email}}" }, + "delete": { "created_by": "{{user.email}}" } + } +} +``` + +--- + +## Using Operators + +### Logical Operators + +Combine multiple conditions using `$or`, `$and`, or `$nor`: + +**Owner OR Admin access:** +```jsonc +{ + "name": "Document", + "type": "object", + "properties": { + "title": { "type": "string" }, + "content": { "type": "string" } + }, + "rls": { + "create": true, + "read": { + "$or": [ + { "created_by": "{{user.email}}" }, + { "user_condition": { "role": "admin" } } + ] + }, + "update": { + "$or": [ + { "created_by": "{{user.email}}" }, + { "user_condition": { "role": "admin" } } + ] + }, + "delete": { "user_condition": { "role": "admin" } } + } +} +``` + +**Multiple roles with $or:** +```jsonc +{ + "rls": { + "read": { + "$or": [ + { "user_condition": { "role": "admin" } }, + { "user_condition": { "role": "manager" } }, + { "user_condition": { "role": "hr" } } + ] + } + } +} +``` + +### Field Operators for data.* Fields + +Use `$in`, `$nin`, `$ne`, `$all` for comparing entity data fields: + +**Access based on tags ($in):** +```jsonc +{ + "rls": { + "read": { + "data.category": { "$in": ["public", "shared"] } + } + } +} +``` + +**Exclude specific statuses ($nin):** +```jsonc +{ + "rls": { + "read": { + "data.status": { "$nin": ["archived", "deleted"] } + } + } +} +``` + +**Not equal ($ne):** +```jsonc +{ + "rls": { + "read": { + "data.visibility": { "$ne": "private" } + } + } +} +``` + +**All tags must match ($all):** +```jsonc +{ + "rls": { + "read": { + "data.required_tags": { "$all": ["approved", "reviewed"] } + } + } +} +``` + +### Combining Logical and Field Operators + +```jsonc +{ + "rls": { + "read": { + "$and": [ + { "data.status": { "$ne": "draft" } }, + { + "$or": [ + { "created_by": "{{user.email}}" }, + { "data.visibility": "public" } + ] + } + ] + } + } +} +``` + +--- + +## Field-Level Security Examples + +Control access to specific fields within an entity. + +### Sensitive Salary Field + +```jsonc +{ + "name": "Employee", + "type": "object", + "properties": { + "name": { "type": "string" }, + "email": { "type": "string", "format": "email" }, + "salary": { + "type": "number", + "description": "Annual salary", + "rls": { + "read": { "user_condition": { "role": "hr" } }, + "write": { "user_condition": { "role": "hr" } } + } + }, + "performance_notes": { + "type": "string", + "description": "Manager notes", + "rls": { + "read": { + "$or": [ + { "user_condition": { "role": "manager" } }, + { "user_condition": { "role": "hr" } } + ] + }, + "write": { "user_condition": { "role": "manager" } } + } + } + } +} +``` + +### Admin-Only Internal Fields + +```jsonc +{ + "name": "Order", + "type": "object", + "properties": { + "order_number": { "type": "string" }, + "total": { "type": "number" }, + "internal_notes": { + "type": "string", + "description": "Internal processing notes", + "rls": { + "read": { "user_condition": { "role": "admin" } }, + "write": { "user_condition": { "role": "admin" } } + } + }, + "profit_margin": { + "type": "number", + "description": "Profit margin percentage", + "rls": { + "read": { "user_condition": { "role": "admin" } }, + "write": false + } + } + } +} +``` + +--- + +## Complex Patterns (Dashboard UI or Backend) + +Some patterns may still require the Dashboard UI or backend functions. + +### Bidirectional Relationships (e.g., Friendships, Matches) + +**Requirement:** Either party in a relationship should have access. + +**Now possible with $or:** +```jsonc +{ + "rls": { + "read": { + "$or": [ + { "data.user_a_email": "{{user.email}}" }, + { "data.user_b_email": "{{user.email}}" } + ] + } + } +} +``` + +**Alternative solutions:** +1. **Entity redesign:** Store two records per relationship (one for each party) +2. **Backend function:** Query with custom logic + +### Complex Business Logic + +**Requirement:** Access depends on multiple entity fields with complex conditions. + +**JSON Schema limitation:** While operators help, very complex business logic may still be hard to express. + +**Solution options:** +1. **Backend function:** Implement custom access logic +2. **Combine simpler rules:** Break complex rules into simpler entity-level and field-level rules + +--- + +## Best Practices + +### Security Strategy + +Use a combination of entity-level RLS and field-level security: + +| Data Type | Approach | Example | +|-----------|----------|---------| +| User-editable | Entity RLS: Owner-only | UserProfile with `created_by` check | +| Sensitive fields | Field-level RLS | Salary field with HR role check | +| Multi-role access | `$or` with user_condition | Admin OR Manager access | +| Conditional access | Field operators | `$in`, `$ne` on data fields | +| Public content | Entity RLS: `read: true` | PublicPost | +| Private content | Entity RLS: Owner-only | PrivateNote | + +### When to Use Each Approach + +| Requirement | Approach | +|-------------|----------| +| Single condition (owner, admin, department) | JSON Schema RLS | +| Multiple OR/AND conditions | JSON Schema RLS with `$or`/`$and` | +| Field value checks with `$in`/`$ne`/etc. | JSON Schema RLS for `data.*` fields | +| Field-level access control | JSON Schema FLS (field-level `rls`) | +| Complex comparison operators (`$gt`, `$lt`) | Backend functions | +| Very complex business logic | Backend functions | + +### Common Role Patterns + +| Role | Typical Access | +|------|----------------| +| `admin` | Full access to all records | +| `moderator` | Read/update access, limited delete | +| `manager` | Department-scoped access | +| `user` | Own records only | + +### Supported Operators Summary + +| Operator | Supported | Notes | +|----------|-----------|-------| +| `$or` | Yes | Combine multiple conditions | +| `$and` | Yes | All conditions must match | +| `$nor` | Yes | None of the conditions match | +| `$in` | Yes | For `data.*` fields only | +| `$nin` | Yes | For `data.*` fields only | +| `$ne` | Yes | For `data.*` fields only | +| `$all` | Yes | For `data.*` fields only | +| `$gt`, `$lt`, `$gte`, `$lte` | No | Use backend functions | +| `$regex` | No | Use backend functions | + +### Limitations Summary + +| Not Supported | Alternative | +|---------------|-------------| +| Operators on `user_condition` | Use equality only for user checks | +| Comparison operators (`$gt`, `$lt`) | Backend functions | +| Regex matching (`$regex`) | Backend functions | +| Cross-entity relationships | Backend functions | diff --git a/evals/fixtures/create-support-agent/skills/base44-cli/references/site-deploy.md b/evals/fixtures/create-support-agent/skills/base44-cli/references/site-deploy.md new file mode 100644 index 0000000..929d302 --- /dev/null +++ b/evals/fixtures/create-support-agent/skills/base44-cli/references/site-deploy.md @@ -0,0 +1,118 @@ +# base44 site deploy + +Deploy built site files to Base44 hosting. + +## Table of Contents + +- [Syntax](#syntax) +- [Authentication](#authentication) +- [Prerequisites](#prerequisites) +- [How It Works](#how-it-works) +- [Interactive Flow](#interactive-flow) +- [Typical Workflow](#typical-workflow) +- [Configuration](#configuration) +- [Error Handling](#error-handling) +- [Use Cases](#use-cases) +- [Notes](#notes) + +## Syntax + +```bash +npx base44 site deploy [options] +``` + +## Options + +| Option | Description | +| ------------ | ------------------------- | +| `-y, --yes` | Skip confirmation prompt | + +Use `-y` flag for non-interactive/automated deployments: + +```bash +npx base44 site deploy -y +``` + +## Authentication + +**Required**: Yes. If not authenticated, you'll be prompted to login first. + +## Prerequisites + +- Must be run from a Base44 project directory +- Project must have `site.outputDirectory` configured in project config +- Site must be built before deploying (run your build command first) +- **SPA only**: Base44 hosting supports Single Page Applications with a single `index.html` entry point. All routes are served from `index.html` (client-side routing). + +## How It Works + +1. Reads project configuration +2. Validates that site configuration exists +3. Prompts for deployment confirmation showing the output directory +4. Creates an archive of site files from the output directory +5. Deploys to Base44 hosting +6. Returns the app URL + +## Interactive Flow + +```bash +$ npx base44 site deploy + +Deploy site from ./dist? (yes/no) yes + +Creating archive... +Uploading to Base44... +Deploying... + +✓ Deployment successful! + +Visit your site at: https://my-app.base44.app +``` + +## Typical Workflow + +```bash +# 1. Build your site using your framework's build command +npm run build + +# 2. Deploy to Base44 +npx base44 site deploy +``` + +## Configuration + +The `site.outputDirectory` in your project configuration should point to where your framework outputs built files: + +- Vite: typically `./dist` +- Next.js: typically `./.next` or `./out` +- Create React App: typically `./build` +- Custom: whatever your build tool outputs to + +## Error Handling + +If site configuration is missing: +```bash +$ npx base44 site deploy +Error: No site configuration found in project +``` + +If you cancel the deployment: +```bash +Deploy site from ./dist? (yes/no) no +Deployment cancelled +``` + +## Use Cases + +- Deploy your site after making changes +- Push new versions of your application +- Deploy after updating content or functionality +- Part of your CI/CD pipeline + +## Notes + +- Always build your site before deploying +- The command deploys whatever is in your output directory +- Make sure your build completed successfully before deploying +- Previous deployments are preserved (versioned) in Base44 +- Deployment is immediate and updates your live site diff --git a/evals/fixtures/create-support-agent/skills/base44-sdk/SKILL.md b/evals/fixtures/create-support-agent/skills/base44-sdk/SKILL.md new file mode 100644 index 0000000..01ddd7f --- /dev/null +++ b/evals/fixtures/create-support-agent/skills/base44-sdk/SKILL.md @@ -0,0 +1,296 @@ +--- +name: base44-sdk +description: "The base44 SDK is the library to communicate with base44 services. In projects, you use it to communicate with remote resources (entities, backend functions, ai agents) and to write backend functions. This skill is the place for learning about available modules and types. When you plan or implement a feature, you must learn this skill" +--- + +# Base44 Coder + +Build apps on the Base44 platform using the Base44 JavaScript SDK. + +## ⚡ IMMEDIATE ACTION REQUIRED - Read This First + +This skill activates on ANY mention of "base44" or when a `base44/` folder exists. **DO NOT read documentation files or search the web before acting.** + +**Your first action MUST be:** +1. Check if `base44/config.jsonc` exists in the current directory +2. If **YES** (existing project scenario): + - This skill (base44-sdk) handles the request + - Implement features using Base44 SDK + - Do NOT use base44-cli unless user explicitly requests CLI commands +3. If **NO** (new project scenario): + - Transfer to base44-cli skill for project initialization + - This skill cannot help until project is initialized + +## When to Use This Skill vs base44-cli + +**Use base44-sdk when:** +- Building features in an **EXISTING** Base44 project +- `base44/config.jsonc` already exists in the project +- Base44 SDK imports are present (`@base44/sdk`) +- Writing JavaScript/TypeScript code using Base44 SDK modules +- Implementing functionality, components, or features +- User mentions: "implement", "build a feature", "add functionality", "write code for" +- User says "create a [type] app" **and** a Base44 project already exists + +**DO NOT USE base44-sdk for:** +- ❌ Initializing new Base44 projects (use `base44-cli` instead) +- ❌ Empty directories without Base44 configuration +- ❌ When user says "create a new Base44 project/app/site" and no project exists +- ❌ CLI commands like `npx base44 create`, `npx base44 deploy`, `npx base44 login` (use `base44-cli`) + +**Skill Dependencies:** +- `base44-sdk` assumes a Base44 project is **already initialized** +- `base44-cli` is a **prerequisite** for `base44-sdk` in new projects +- If user wants to "create an app" and no Base44 project exists, use `base44-cli` first + +**State Check Logic:** +Before selecting this skill, verify: +- IF (user mentions "create/build app" OR "make a project"): + - IF (directory is empty OR no `base44/config.jsonc` exists): + → Use **base44-cli** (project initialization needed) + - ELSE: + → Use **base44-sdk** (project exists, build features) + +## Quick Start + +```javascript +// In Base44-generated apps, base44 client is pre-configured and available + +// CRUD operations +const task = await base44.entities.Task.create({ title: "New task", status: "pending" }); +const tasks = await base44.entities.Task.list(); +await base44.entities.Task.update(task.id, { status: "done" }); + +// Get current user +const user = await base44.auth.me(); +``` + +```javascript +// External apps +import { createClient } from "@base44/sdk"; + +// IMPORTANT: Use 'appId' (NOT 'clientId' or 'id') +const base44 = createClient({ appId: "your-app-id" }); +await base44.auth.loginViaEmailPassword("user@example.com", "password"); +``` + +## ⚠️ CRITICAL: Do Not Hallucinate APIs + +**Before writing ANY Base44 code, verify method names against this table or [QUICK_REFERENCE.md](references/QUICK_REFERENCE.md).** + +Base44 SDK has unique method names. Do NOT assume patterns from Firebase, Supabase, or other SDKs. + +### Authentication - WRONG vs CORRECT + +| ❌ WRONG (hallucinated) | ✅ CORRECT | +|------------------------|-----------| +| `signInWithGoogle()` | `loginWithProvider('google')` | +| `signInWithProvider('google')` | `loginWithProvider('google')` | +| `auth.google()` | `loginWithProvider('google')` | +| `signInWithEmailAndPassword(email, pw)` | `loginViaEmailPassword(email, pw)` | +| `signIn(email, pw)` | `loginViaEmailPassword(email, pw)` | +| `createUser()` / `signUp()` | `register({email, password})` | +| `onAuthStateChanged()` | `me()` (no listener, call when needed) | +| `currentUser` | `await auth.me()` | + +### Functions - WRONG vs CORRECT + +| ❌ WRONG (hallucinated) | ✅ CORRECT | +|------------------------|-----------| +| `functions.call('name', data)` | `functions.invoke('name', data)` | +| `functions.run('name', data)` | `functions.invoke('name', data)` | +| `callFunction('name', data)` | `functions.invoke('name', data)` | +| `httpsCallable('name')(data)` | `functions.invoke('name', data)` | + +### Integrations - WRONG vs CORRECT + +| ❌ WRONG (hallucinated) | ✅ CORRECT | +|------------------------|-----------| +| `ai.generate(prompt)` | `integrations.Core.InvokeLLM({prompt})` | +| `openai.chat(prompt)` | `integrations.Core.InvokeLLM({prompt})` | +| `llm(prompt)` | `integrations.Core.InvokeLLM({prompt})` | +| `sendEmail(to, subject, body)` | `integrations.Core.SendEmail({to, subject, body})` | +| `email.send()` | `integrations.Core.SendEmail({to, subject, body})` | +| `uploadFile(file)` | `integrations.Core.UploadFile({file})` | +| `storage.upload(file)` | `integrations.Core.UploadFile({file})` | + +### Entities - WRONG vs CORRECT + +| ❌ WRONG (hallucinated) | ✅ CORRECT | +|------------------------|-----------| +| `entities.Task.find({...})` | `entities.Task.filter({...})` | +| `entities.Task.findOne(id)` | `entities.Task.get(id)` | +| `entities.Task.insert(data)` | `entities.Task.create(data)` | +| `entities.Task.remove(id)` | `entities.Task.delete(id)` | +| `entities.Task.onChange(cb)` | `entities.Task.subscribe(cb)` | + +## SDK Modules + +| Module | Purpose | Reference | +|--------|---------|-----------| +| `entities` | CRUD operations on data models | [entities.md](references/entities.md) | +| `auth` | Login, register, user management | [auth.md](references/auth.md) | +| `agents` | AI conversations and messages | [base44-agents.md](references/base44-agents.md) | +| `functions` | Backend function invocation | [functions.md](references/functions.md) | +| `integrations` | AI, email, file uploads, custom APIs | [integrations.md](references/integrations.md) | +| `connectors` | OAuth tokens (service role only) | [connectors.md](references/connectors.md) | +| `analytics` | Track custom events and user activity | [analytics.md](references/analytics.md) | +| `appLogs` | Log user activity in app | [app-logs.md](references/app-logs.md) | +| `users` | Invite users to the app | [users.md](references/users.md) | + +For client setup and authentication modes, see [client.md](references/client.md). + +**TypeScript Support:** Each reference file includes a "Type Definitions" section with TypeScript interfaces and types for the module's methods, parameters, and return values. + +## Installation + +Install the Base44 SDK: + +```bash +npm install @base44/sdk +``` + +**Important:** Never assume or hardcode the `@base44/sdk` package version. Always install without a version specifier to get the latest version. + +## Creating a Client (External Apps) + +When creating a client in external apps, **ALWAYS use `appId` as the parameter name**: + +```javascript +import { createClient } from "@base44/sdk"; + +// ✅ CORRECT +const base44 = createClient({ appId: "your-app-id" }); + +// ❌ WRONG - Do NOT use these: +// const base44 = createClient({ clientId: "your-app-id" }); // WRONG +// const base44 = createClient({ id: "your-app-id" }); // WRONG +``` + +**Required parameter:** `appId` (string) - Your Base44 application ID + +**Optional parameters:** +- `token` (string) - Pre-authenticated user token +- `options` (object) - Configuration options + - `options.onError` (function) - Global error handler + +**Example with error handler:** +```javascript +const base44 = createClient({ + appId: "your-app-id", + options: { + onError: (error) => { + console.error("Base44 error:", error); + } + } +}); +``` + +## Module Selection + +**Working with app data?** +- Create/read/update/delete records → `entities` +- Import data from file → `entities.importEntities()` +- Realtime updates → `entities.EntityName.subscribe()` + +**User management?** +- Login/register/logout → `auth` +- Get current user → `auth.me()` +- Update user profile → `auth.updateMe()` +- Invite users → `users.inviteUser()` + +**AI features?** +- Chat with AI agents → `agents` (requires logged-in user) +- Create new conversation → `agents.createConversation()` +- Manage conversations → `agents.getConversations()` +- Generate text/JSON with AI → `integrations.Core.InvokeLLM()` +- Generate images → `integrations.Core.GenerateImage()` + +**Custom backend logic?** +- Run server-side code → `functions.invoke()` +- Need admin access → `base44.asServiceRole.functions.invoke()` + +**External services?** +- Send emails → `integrations.Core.SendEmail()` +- Upload files → `integrations.Core.UploadFile()` +- Custom APIs → `integrations.custom.call()` +- OAuth tokens (Google, Slack) → `connectors` (backend only) + +**Tracking and analytics?** +- Track custom events → `analytics.track()` +- Log page views/activity → `appLogs.logUserInApp()` + +## Common Patterns + +### Filter and Sort Data + +```javascript +const pendingTasks = await base44.entities.Task.filter( + { status: "pending", assignedTo: userId }, // query + "-created_date", // sort (descending) + 10, // limit + 0 // skip +); +``` + +### Protected Routes (check auth) + +```javascript +const user = await base44.auth.me(); +if (!user) { + // Navigate to your custom login page + navigate('/login', { state: { returnTo: window.location.pathname } }); + return; +} +``` + +### Backend Function Call + +```javascript +// Frontend +const result = await base44.functions.invoke("processOrder", { + orderId: "123", + action: "ship" +}); + +// Backend function (Deno) +import { createClientFromRequest } from "npm:@base44/sdk"; + +Deno.serve(async (req) => { + const base44 = createClientFromRequest(req); + const { orderId, action } = await req.json(); + // Process with service role for admin access + const order = await base44.asServiceRole.entities.Orders.get(orderId); + return Response.json({ success: true }); +}); +``` + +### Service Role Access + +Use `asServiceRole` in backend functions for admin-level operations: + +```javascript +// User mode - respects permissions +const myTasks = await base44.entities.Task.list(); + +// Service role - full access (backend only) +const allTasks = await base44.asServiceRole.entities.Task.list(); +const token = await base44.asServiceRole.connectors.getAccessToken("slack"); +``` + +## Frontend vs Backend + +| Capability | Frontend | Backend | +|------------|----------|---------| +| `entities` (user's data) | Yes | Yes | +| `auth` | Yes | Yes | +| `agents` | Yes | Yes | +| `functions.invoke()` | Yes | Yes | +| `integrations` | Yes | Yes | +| `analytics` | Yes | Yes | +| `appLogs` | Yes | Yes | +| `users` | Yes | Yes | +| `asServiceRole.*` | No | Yes | +| `connectors` | No | Yes | + +Backend functions use `Deno.serve()` and `createClientFromRequest(req)` to get a properly authenticated client. diff --git a/evals/fixtures/create-support-agent/skills/base44-sdk/references/QUICK_REFERENCE.md b/evals/fixtures/create-support-agent/skills/base44-sdk/references/QUICK_REFERENCE.md new file mode 100644 index 0000000..d357384 --- /dev/null +++ b/evals/fixtures/create-support-agent/skills/base44-sdk/references/QUICK_REFERENCE.md @@ -0,0 +1,155 @@ +# Base44 SDK Quick Reference + +Compact method signatures for all SDK modules. **Verify against this before writing code.** + +--- + +## Auth (`base44.auth.*`) + +``` +loginViaEmailPassword(email, password, turnstileToken?) → Promise<{access_token, user}> +loginWithProvider('google' | 'microsoft' | 'facebook', fromUrl?) → void +me() → Promise +updateMe(data) → Promise +isAuthenticated() → Promise +logout(redirectUrl?) → void +redirectToLogin(nextUrl) → void # ⚠️ Avoid - prefer custom login UI +register({email, password, turnstile_token?, referral_code?}) → Promise +verifyOtp({email, otpCode}) → Promise +resendOtp(email) → Promise +inviteUser(userEmail, role) → Promise +resetPasswordRequest(email) → Promise +resetPassword({resetToken, newPassword}) → Promise +changePassword({userId, currentPassword, newPassword}) → Promise +setToken(token, saveToStorage?) → void +``` + +--- + +## Entities (`base44.entities.EntityName.*`) + +``` +create(data) → Promise +bulkCreate(dataArray) → Promise +list(sort?, limit?, skip?, fields?) → Promise +filter(query, sort?, limit?, skip?, fields?) → Promise +get(id) → Promise +update(id, data) → Promise +delete(id) → Promise +deleteMany(query) → Promise +importEntities(file) → Promise // frontend only +subscribe(callback) → () => void // returns unsubscribe fn +``` + +**Sort:** Use `-fieldName` for descending (e.g., `-created_date`) + +--- + +## Functions (`base44.functions.*`) + +``` +invoke(functionName, data) → Promise +``` + +**Backend:** Use `base44.asServiceRole.functions.invoke()` for admin access. + +--- + +## Integrations (`base44.integrations.Core.*`) + +``` +InvokeLLM({prompt, add_context_from_internet?, response_json_schema?, file_urls?}) → Promise +GenerateImage({prompt}) → Promise<{url}> +SendEmail({to, subject, body, from_name?}) → Promise +UploadFile({file}) → Promise<{file_url}> +UploadPrivateFile({file}) → Promise<{file_uri}> +CreateFileSignedUrl({file_uri, expires_in?}) → Promise<{signed_url}> +ExtractDataFromUploadedFile({file_url, json_schema}) → Promise +``` + +### Custom Integrations (`base44.integrations.custom.*`) + +``` +call(slug, operationId, {payload?, pathParams?, queryParams?, headers?}?) → Promise<{success, status_code, data}> +``` + +**operationId format:** `"method:/path"` (e.g., `"get:/contacts"`, `"post:/users/{id}"`) + +--- + +## Analytics (`base44.analytics.*`) + +``` +track({eventName, properties?}) → void +``` + +--- + +## App Logs (`base44.appLogs.*`) + +``` +logUserInApp(pageName) → Promise +``` + +--- + +## Users (`base44.users.*`) + +``` +inviteUser(userEmail, role) → Promise // role: 'user' | 'admin' +``` + +--- + +## Connectors (`base44.asServiceRole.connectors.*`) + +**Backend only, service role required.** + +``` +getAccessToken(integrationType) → Promise +``` + +**Types:** `'googlecalendar'`, `'googledrive'`, `'slack'`, `'notion'`, `'salesforce'`, `'hubspot'`, `'linkedin'`, `'tiktok'`, `'github'` + +--- + +## Service Role Access + +**Backend functions only.** Prefix any module with `asServiceRole` for admin access: + +```javascript +base44.asServiceRole.entities.Task.list() +base44.asServiceRole.functions.invoke('name', data) +base44.asServiceRole.connectors.getAccessToken('slack') +``` + +--- + +## Backend Function Template + +```javascript +import { createClientFromRequest } from "@base44/sdk"; + +Deno.serve(async (req) => { + const base44 = createClientFromRequest(req); + const data = await req.json(); + + // User context + const user = await base44.auth.me(); + + // Service role for admin operations + const allRecords = await base44.asServiceRole.entities.Task.list(); + + return Response.json({ success: true }); +}); +``` + +--- + +## Client Initialization (External Apps) + +```javascript +import { createClient } from "@base44/sdk"; + +const base44 = createClient({ appId: "your-app-id" }); // MUST use 'appId' +``` diff --git a/evals/fixtures/create-support-agent/skills/base44-sdk/references/analytics.md b/evals/fixtures/create-support-agent/skills/base44-sdk/references/analytics.md new file mode 100644 index 0000000..d1b2c69 --- /dev/null +++ b/evals/fixtures/create-support-agent/skills/base44-sdk/references/analytics.md @@ -0,0 +1,113 @@ +# Analytics Module + +Track custom events and user activity via `base44.analytics`. + +## Contents +- [Methods](#methods) +- [Examples](#examples) +- [Automatic Tracking](#automatic-tracking) +- [Best Practices](#best-practices) + +## Methods + +| Method | Signature | Description | +|--------|-----------|-------------| +| `track(params)` | `void` | Track a custom event | + +## Examples + +### Track Custom Event + +```javascript +// Track a simple event +base44.analytics.track({ + eventName: "button_clicked" +}); + +// Track event with properties +base44.analytics.track({ + eventName: "purchase_completed", + properties: { + product_id: "prod-123", + amount: 99.99, + currency: "USD" + } +}); +``` + +### Track User Actions + +```javascript +// Track page view +base44.analytics.track({ + eventName: "page_view", + properties: { + page: "/dashboard", + referrer: document.referrer + } +}); + +// Track feature usage +base44.analytics.track({ + eventName: "feature_used", + properties: { + feature: "export_data", + format: "csv" + } +}); +``` + +## Automatic Tracking + +The analytics module automatically tracks: +- **Initialization events**: When the app loads +- **Heartbeat events**: Periodic activity signals +- **Session duration**: Time spent in the app + +These internal events help measure user engagement without manual instrumentation. + +## Best Practices + +1. **Use descriptive event names**: `order_completed` instead of `click` +2. **Include relevant properties**: Add context that helps analyze the event +3. **Be consistent**: Use the same event names and property keys across your app +4. **Don't track sensitive data**: Avoid PII in event properties + +```javascript +// Good: Descriptive with relevant properties +base44.analytics.track({ + eventName: "subscription_started", + properties: { + plan: "pro", + billing_cycle: "annual" + } +}); + +// Avoid: Vague event name, no context +base44.analytics.track({ + eventName: "click" +}); +``` + +## Type Definitions + +```typescript +/** Properties that can be attached to a tracked event. */ +type TrackEventProperties = { + [key: string]: string | number | boolean | null | undefined; +}; + +/** Parameters for the track() method. */ +interface TrackEventParams { + /** The name of the event to track. */ + eventName: string; + /** Optional properties to attach to the event. */ + properties?: TrackEventProperties; +} + +/** The analytics module interface. */ +interface AnalyticsModule { + /** Track a custom event with optional properties. */ + track(params: TrackEventParams): void; +} +``` diff --git a/evals/fixtures/create-support-agent/skills/base44-sdk/references/app-logs.md b/evals/fixtures/create-support-agent/skills/base44-sdk/references/app-logs.md new file mode 100644 index 0000000..0439805 --- /dev/null +++ b/evals/fixtures/create-support-agent/skills/base44-sdk/references/app-logs.md @@ -0,0 +1,78 @@ +# App Logs Module + +Log user activity in your app via `base44.appLogs`. + +## Contents +- [Methods](#methods) +- [Examples](#examples) +- [Use Cases](#use-cases) + +## Methods + +| Method | Signature | Description | +|--------|-----------|-------------| +| `logUserInApp(pageName)` | `Promise` | Log user activity on a page | + +## Examples + +### Log User Activity + +```javascript +// Log when user visits a page +await base44.appLogs.logUserInApp("dashboard"); + +// Log specific page visits +await base44.appLogs.logUserInApp("settings"); +await base44.appLogs.logUserInApp("profile"); + +// Log feature usage +await base44.appLogs.logUserInApp("export-button-click"); +``` + +The page name doesn't have to be an actual page - it can be any string you want to track. + +## Use Cases + +### Track Page Views in React + +```javascript +// Log page views on route change +useEffect(() => { + base44.appLogs.logUserInApp(window.location.pathname); +}, [location.pathname]); +``` + +### Track Feature Usage + +```javascript +// Log when user uses specific features +function handleExport() { + base44.appLogs.logUserInApp("export-data"); + // ... export logic +} + +function handleSettingsChange() { + base44.appLogs.logUserInApp("settings-updated"); + // ... save settings +} +``` + +## Notes + +- Logs appear in the Analytics page of your app dashboard +- App logs track page-level and feature-level activity +- Use `analytics.track()` for custom events with properties, `appLogs.logUserInApp()` for simple page/feature tracking + +## Type Definitions + +```typescript +/** App Logs module for tracking and analyzing app usage. */ +interface AppLogsModule { + /** + * Log user activity in the app. + * @param pageName - Name of the page or section being visited. + * @returns Promise that resolves when the log is recorded. + */ + logUserInApp(pageName: string): Promise; +} +``` diff --git a/evals/fixtures/create-support-agent/skills/base44-sdk/references/auth.md b/evals/fixtures/create-support-agent/skills/base44-sdk/references/auth.md new file mode 100644 index 0000000..5195b3a --- /dev/null +++ b/evals/fixtures/create-support-agent/skills/base44-sdk/references/auth.md @@ -0,0 +1,761 @@ +# Auth Module + +User authentication, registration, and session management via `base44.auth`. + +## Contents +- [TypeScript Types](#typescript-types) +- [Methods](#methods) +- [Examples](#examples) +- [Error Handling](#error-handling) +- [Auth Providers](#auth-providers) +- [Environment Availability](#environment-availability) +- [App Visibility](#app-visibility) +- [Limitations](#limitations) + +--- + +## TypeScript Types + +### User Interface +```typescript +interface User { + id: string; + created_date: string; + updated_date: string; + email: string; + full_name: string | null; + disabled: boolean | null; + is_verified: boolean; + app_id: string; + is_service: boolean; + role: string; + [key: string]: any; // Custom schema fields +} +``` + +### LoginResponse Interface +```typescript +interface LoginResponse { + access_token: string; // JWT token + user: User; // Complete user object +} +``` + +### Parameter Interfaces + +#### RegisterParams +```typescript +interface RegisterParams { + email: string; // Required + password: string; // Required + turnstile_token?: string | null; // Optional: Cloudflare Turnstile for bot protection + referral_code?: string | null; // Optional: Referral code +} +``` + +#### VerifyOtpParams +```typescript +interface VerifyOtpParams { + email: string; // User's email + otpCode: string; // OTP code from email +} +``` + +#### ResetPasswordParams +```typescript +interface ResetPasswordParams { + resetToken: string; // Token from password reset email + newPassword: string; // New password to set +} +``` + +#### ChangePasswordParams +```typescript +interface ChangePasswordParams { + userId: string; // User ID + currentPassword: string; // Current password for verification + newPassword: string; // New password to set +} +``` + +### Provider Type +```typescript +type Provider = 'google' | 'microsoft' | 'facebook'; +``` + +--- + +## Methods + +### Module Interface +```typescript +interface AuthModule { + // User Info + me(): Promise; + updateMe(data: Partial>): Promise; + isAuthenticated(): Promise; + + // Login/Logout + loginViaEmailPassword(email: string, password: string, turnstileToken?: string): Promise; + loginWithProvider(provider: Provider, fromUrl?: string): void; + logout(redirectUrl?: string): void; + redirectToLogin(nextUrl: string): void; + + // Token Management + setToken(token: string, saveToStorage?: boolean): void; + + // Registration + register(params: RegisterParams): Promise; + verifyOtp(params: VerifyOtpParams): Promise; + resendOtp(email: string): Promise; + + // User Management + inviteUser(userEmail: string, role: string): Promise; + + // Password Management + resetPasswordRequest(email: string): Promise; + resetPassword(params: ResetPasswordParams): Promise; + changePassword(params: ChangePasswordParams): Promise; +} +``` + +### Method Reference Table + +| Method | Parameters | Return Type | Description | +|--------|-----------|-------------|-------------| +| `register()` | `params: RegisterParams` | `Promise` | Create new user account | +| `loginViaEmailPassword()` | `email: string, password: string, turnstileToken?: string` | `Promise` | Authenticate with email/password | +| `loginWithProvider()` | `provider: Provider, fromUrl?: string` | `void` | Initiate OAuth login flow | +| `me()` | None | `Promise` | Get current authenticated user | +| `updateMe()` | `data: Partial` | `Promise` | Update current user's profile | +| `logout()` | `redirectUrl?: string` | `void` | Clear session, optionally redirect | +| `redirectToLogin()` | `nextUrl: string` | `void` | ⚠️ **Avoid** - Prefer custom login UI with `loginViaEmailPassword()` or `loginWithProvider()` | +| `isAuthenticated()` | None | `Promise` | Check if user is logged in | +| `setToken()` | `token: string, saveToStorage?: boolean` | `void` | Manually set auth token | +| `inviteUser()` | `userEmail: string, role: string` | `Promise` | Send invitation email | +| `verifyOtp()` | `params: VerifyOtpParams` | `Promise` | Verify OTP code | +| `resendOtp()` | `email: string` | `Promise` | Resend OTP code | +| `resetPasswordRequest()` | `email: string` | `Promise` | Request password reset | +| `resetPassword()` | `params: ResetPasswordParams` | `Promise` | Reset password with token | +| `changePassword()` | `params: ChangePasswordParams` | `Promise` | Change user password | + +--- + +## Examples + +### Register New User (Complete Flow) + +Registration requires email verification before login. Complete flow: + +1. **Register** - Create the user account +2. **Verification email sent** - User receives an OTP code +3. **Verify OTP** - User enters code to verify email +4. **Login** - User can now log in + +```javascript +try { + // Step 1: Register the user + await base44.auth.register({ + email: "user@example.com", + password: "securePassword123", + referral_code: "OPTIONAL_CODE", // optional + turnstile_token: "CAPTCHA_TOKEN" // optional, for bot protection + }); + console.log('Registration successful. Check email for OTP code.'); + + // Step 2: User receives email with OTP code (e.g., "123456") + + // Step 3: Verify the OTP code + await base44.auth.verifyOtp({ + email: "user@example.com", + otpCode: "123456" // code from verification email + }); + console.log('Email verified successfully.'); + + // Step 4: Now the user can log in + const loginResponse = await base44.auth.loginViaEmailPassword( + "user@example.com", + "securePassword123" + ); + console.log('Login successful:', loginResponse.user); + +} catch (error) { + console.error('Registration flow failed:', error.message); + // Handle specific errors (see Error Handling section) +} +``` + +> **Important**: Users cannot log in until they complete OTP verification. Attempting to call `loginViaEmailPassword` before verification will fail. + +### Login with Email/Password + +```javascript +try { + const response = await base44.auth.loginViaEmailPassword( + "user@example.com", + "password123", + turnstileToken // optional: for bot protection + ); + + console.log('Login successful'); + console.log('User:', response.user); + console.log('Token:', response.access_token); + + // JWT is automatically stored for subsequent requests + +} catch (error) { + console.error('Login failed:', error.message); + if (error.status === 401) { + console.error('Invalid credentials'); + } else if (error.status === 403) { + console.error('Email not verified. Please check your email for OTP.'); + } +} +``` + +### Login with OAuth Provider + +```javascript +// Redirect to Google OAuth +base44.auth.loginWithProvider('google'); + +// Redirect to Google OAuth and return to current page after +base44.auth.loginWithProvider('google', window.location.href); + +// Supported providers: 'google', 'microsoft', 'facebook' +base44.auth.loginWithProvider('microsoft'); +``` + +### Get Current User + +```javascript +try { + const user = await base44.auth.me(); + + if (user) { + console.log('User ID:', user.id); + console.log('Email:', user.email); + console.log('Name:', user.full_name); + console.log('Role:', user.role); + console.log('Verified:', user.is_verified); + } else { + console.log('User not authenticated'); + } + +} catch (error) { + console.error('Failed to fetch user:', error.message); + if (error.status === 401) { + // Token expired or invalid - navigate to your custom login page + navigate('/login'); + } +} +``` + +### Update User Profile + +```javascript +try { + const updatedUser = await base44.auth.updateMe({ + full_name: "John Doe", + // Custom schema fields can be updated here + phone: "+1234567890", + preferences: { theme: "dark" } + }); + + console.log('Profile updated:', updatedUser); + +} catch (error) { + console.error('Profile update failed:', error.message); + if (error.status === 400) { + console.error('Invalid data provided'); + } else if (error.status === 401) { + console.error('Authentication required'); + navigate('/login'); + } +} +``` + +### Check Authentication Status + +```javascript +try { + const isLoggedIn = await base44.auth.isAuthenticated(); + + if (isLoggedIn) { + // Show authenticated UI + console.log('User is authenticated'); + } else { + // Show login button + console.log('User is not authenticated'); + } + +} catch (error) { + console.error('Failed to check authentication:', error.message); + // On error, treat as not authenticated +} +``` + +### Logout + +```javascript +// Simple logout +base44.auth.logout(); + +// Logout and redirect to goodbye page +base44.auth.logout("/goodbye"); + +// Logout and redirect to homepage +base44.auth.logout("/"); +``` + +### Protected Route Pattern + +```javascript +// Using a navigation function (e.g., React Router's useNavigate, Next.js router) +async function requireAuth(navigate) { + try { + const user = await base44.auth.me(); + + if (!user) { + // Navigate to your custom login page + navigate('/login', { state: { returnTo: window.location.pathname } }); + return null; + } + + return user; + + } catch (error) { + console.error('Authentication check failed:', error.message); + navigate('/login', { state: { returnTo: window.location.pathname } }); + return null; + } +} + +// Usage in your app +async function loadProtectedPage(navigate) { + const user = await requireAuth(navigate); + if (!user) { + // Will navigate to login + return; + } + + // Continue with authenticated logic + console.log('Welcome,', user.full_name); +} +``` + +### Set Authentication Token + +```javascript +// SECURITY WARNING: Never hardcode tokens or expose them in client code +// Tokens should only be received from secure authentication flows + +// Set token and save to localStorage (default) +base44.auth.setToken(receivedToken); + +// Set token without saving to localStorage (temporary session) +base44.auth.setToken(receivedToken, false); + +// Verify token was set +try { + const isAuthenticated = await base44.auth.isAuthenticated(); + if (!isAuthenticated) { + console.error('Token validation failed'); + } +} catch (error) { + console.error('Failed to set token:', error.message); +} +``` + +### Invite User to Application + +```javascript +try { + // Note: Typically requires admin privileges + const response = await base44.auth.inviteUser( + "newuser@example.com", + "user" // or "admin" + ); + + console.log('Invitation sent successfully'); + +} catch (error) { + console.error('Failed to invite user:', error.message); + if (error.status === 403) { + console.error('Insufficient permissions to invite users'); + } else if (error.status === 400) { + console.error('Invalid email or role'); + } +} +``` + +### OTP Verification + +```javascript +try { + // Verify OTP code sent to user's email + await base44.auth.verifyOtp({ + email: "user@example.com", + otpCode: "123456" + }); + + console.log('OTP verified successfully'); + +} catch (error) { + console.error('OTP verification failed:', error.message); + if (error.status === 400) { + console.error('Invalid or expired OTP code'); + } else if (error.status === 429) { + console.error('Too many attempts. Please try again later.'); + } +} + +// Resend OTP if needed +try { + await base44.auth.resendOtp("user@example.com"); + console.log('OTP resent successfully'); + +} catch (error) { + console.error('Failed to resend OTP:', error.message); + if (error.status === 429) { + console.error('Too many requests. Please wait before trying again.'); + } +} +``` + +### Password Reset Flow + +```javascript +// Step 1: Request password reset +try { + await base44.auth.resetPasswordRequest("user@example.com"); + console.log('Password reset email sent. Check your inbox.'); + +} catch (error) { + console.error('Password reset request failed:', error.message); + if (error.status === 429) { + console.error('Too many requests. Please try again later.'); + } + // Note: For security, don't reveal if email exists +} + +// Step 2: Reset password with token from email +try { + await base44.auth.resetPassword({ + resetToken: "token-from-email", + newPassword: "newSecurePassword123" + }); + + console.log('Password reset successfully. You can now log in.'); + +} catch (error) { + console.error('Password reset failed:', error.message); + if (error.status === 400) { + console.error('Invalid or expired reset token'); + } else if (error.status === 422) { + console.error('Password does not meet requirements'); + } +} +``` + +### Change Password + +```javascript +try { + const currentUser = await base44.auth.me(); + + if (!currentUser) { + throw new Error('User must be authenticated to change password'); + } + + await base44.auth.changePassword({ + userId: currentUser.id, + currentPassword: "oldPassword123", + newPassword: "newSecurePassword456" + }); + + console.log('Password changed successfully'); + +} catch (error) { + console.error('Password change failed:', error.message); + if (error.status === 401) { + console.error('Current password is incorrect'); + } else if (error.status === 422) { + console.error('New password does not meet requirements'); + } else if (error.status === 403) { + console.error('Not authorized to change this password'); + } +} +``` + +--- + +## Error Handling + +### Common Error Scenarios + +The auth module can throw various errors. Here are common scenarios and how to handle them: + +#### Authentication Errors (401/403) +```javascript +try { + const user = await base44.auth.me(); +} catch (error) { + if (error.status === 401) { + // Token expired or invalid - navigate to your custom login page + navigate('/login'); + } else if (error.status === 403) { + // Email not verified or insufficient permissions + console.error('Access denied:', error.message); + } +} +``` + +#### Validation Errors (400/422) +```javascript +try { + await base44.auth.register({ + email: "invalid-email", + password: "weak" + }); +} catch (error) { + if (error.status === 400) { + console.error('Invalid input:', error.message); + // Handle validation errors + } else if (error.status === 422) { + console.error('Data validation failed:', error.details); + } +} +``` + +#### Rate Limiting (429) +```javascript +try { + await base44.auth.resendOtp("user@example.com"); +} catch (error) { + if (error.status === 429) { + console.error('Too many requests. Please wait before trying again.'); + // Show countdown or disable button temporarily + } +} +``` + +#### Generic Error Handler +```javascript +function handleAuthError(error) { + switch (error.status) { + case 400: + return 'Invalid input. Please check your data.'; + case 401: + return 'Authentication required. Redirecting to login...'; + case 403: + return 'Access denied. You may need to verify your email.'; + case 404: + return 'Resource not found.'; + case 422: + return 'Validation failed. Please check your input.'; + case 429: + return 'Too many requests. Please try again later.'; + case 500: + return 'Server error. Please try again.'; + default: + return 'An unexpected error occurred.'; + } +} + +// Usage +try { + await base44.auth.loginViaEmailPassword(email, password); +} catch (error) { + console.error(handleAuthError(error)); +} +``` + +--- + +## Auth Providers + +Configure authentication providers in your app dashboard: + +### Available Providers + +**Built-in (All Plans):** +- **Email/Password** - Default, always enabled +- **Google** - OAuth authentication +- **Microsoft** - OAuth authentication +- **Facebook** - OAuth authentication + +**SSO Providers (Elite Plan):** +- **Okta** +- **Azure AD** +- **GitHub** + +### Using OAuth Providers + +```javascript +// Initiate OAuth login flow +base44.auth.loginWithProvider('google'); + +// Return to specific page after authentication +base44.auth.loginWithProvider('microsoft', '/dashboard'); + +// Supported values: 'google', 'microsoft', 'facebook' +``` + +--- + +## Environment Availability + +| Environment | Availability | Notes | +|------------|--------------|-------| +| **Frontend** | ✅ Yes | All methods available | +| **Backend Functions** | ✅ Yes | Use `createClientFromRequest(req)` for authenticated client | +| **Service Role** | ❌ No | Auth methods not available in service role context | + +### Frontend Usage +```javascript +// Standard browser usage +const user = await base44.auth.me(); +``` + +### Backend Functions Usage +```javascript +import { createClientFromRequest } from "@base44/sdk"; + +Deno.serve(async (req) => { + const base44 = createClientFromRequest(req); + + // Get user from request context + const user = await base44.auth.me(); + + // User operations here... + return Response.json({ user }); +}); +``` + +--- + +## App Visibility + +Control who can access your app in the app settings: + +### Public Apps +- No login required for basic access +- Users can view public content without authentication +- Authenticated users get additional features/data + +### Private Apps +- Login required to access any content +- Unauthenticated users are automatically redirected to login +- All content is protected by default + +--- + +## Limitations + +### Authentication UI Options +- **Recommended:** Build custom login/signup UI using `loginViaEmailPassword()` and `loginWithProvider()` for full control over user experience and branding +- **Alternative:** `redirectToLogin()` uses Base44's hosted authentication pages with limited customization + +### Hosted Login (via redirectToLogin) +- `redirectToLogin()` shows both login and signup options on the same page +- No separate `redirectToSignup()` method +- Users can switch between login/signup on the hosted page +- ⚠️ **Note:** Prefer building custom login UI for better user experience + +### Password Requirements +- Minimum length and complexity requirements enforced +- Requirements not exposed via API +- Validation errors returned when requirements not met + +### Rate Limiting +- OTP requests are rate-limited to prevent abuse +- Password reset requests are rate-limited +- Login attempts may be rate-limited with Turnstile protection + +### Token Management +- JWTs are automatically stored in localStorage by default +- Token expiration and refresh not exposed in API +- Call `me()` or `isAuthenticated()` to verify token validity + +--- + +## Best Practices + +### 1. Always Handle Errors +```javascript +try { + await base44.auth.loginViaEmailPassword(email, password); +} catch (error) { + // Always handle authentication errors + console.error('Login failed:', error.message); +} +``` + +### 2. Verify Authentication Before Protected Actions +```javascript +const user = await requireAuth(); +if (!user) return; // Will redirect to login +// Proceed with authenticated action +``` + +### 3. Use Type Safety with TypeScript +```typescript +import type { User, LoginResponse } from '@base44/sdk'; + +const user: User = await base44.auth.me(); +const response: LoginResponse = await base44.auth.loginViaEmailPassword(email, password); +``` + +### 4. Don't Hardcode Credentials +```javascript +// ❌ BAD +base44.auth.setToken("hardcoded-token-here"); + +// ✅ GOOD +const token = await secureAuthFlow(); +base44.auth.setToken(token); +``` + +### 5. Provide User Feedback +```javascript +try { + await base44.auth.register(params); + showSuccessMessage('Registration successful! Check your email for verification code.'); +} catch (error) { + showErrorMessage('Registration failed: ' + error.message); +} +``` + +### 6. Handle Token Expiration Gracefully +```javascript +try { + const user = await base44.auth.me(); +} catch (error) { + if (error.status === 401) { + // Clear local state and navigate to login + localStorage.clear(); + navigate('/login'); + } +} +``` + +### 7. Build Custom Login UI (Recommended) +```javascript +// ✅ RECOMMENDED - Custom login form with direct methods +const handleLogin = async (email, password) => { + try { + const { user } = await base44.auth.loginViaEmailPassword(email, password); + navigate('/dashboard'); + } catch (error) { + setError(error.message); + } +}; + +const handleGoogleLogin = () => { + base44.auth.loginWithProvider('google', '/dashboard'); +}; + +// ❌ AVOID - Redirecting to hosted login page +// base44.auth.redirectToLogin(window.location.href); +``` diff --git a/evals/fixtures/create-support-agent/skills/base44-sdk/references/base44-agents.md b/evals/fixtures/create-support-agent/skills/base44-sdk/references/base44-agents.md new file mode 100644 index 0000000..ee55dee --- /dev/null +++ b/evals/fixtures/create-support-agent/skills/base44-sdk/references/base44-agents.md @@ -0,0 +1,364 @@ +# Agents Module + +AI agent conversations and messages via `base44.agents`. + +> **Note:** This module requires a logged-in user. All agent methods work in the context of the authenticated user. + +## Contents +- [Concepts](#concepts) +- [Methods](#methods) +- [Examples](#examples) (Create, Get Conversations, List, Subscribe, Send Message, WhatsApp) +- [Message Structure](#message-structure) +- [Conversation Structure](#conversation-structure) +- [Common Patterns](#common-patterns) + +## Concepts + +- **Conversation**: A dialogue between user and an AI agent. Has unique ID, agent name, user reference, and metadata. +- **Message**: Single message in a conversation. Has role (`user`, `assistant`, `system`), content, timestamps, and optional metadata. + +## Methods + +| Method | Signature | Description | +|--------|-----------|-------------| +| `createConversation(params)` | `Promise` | Create a new conversation with an agent | +| `getConversations()` | `Promise` | Get all user's conversations | +| `getConversation(id)` | `Promise` | Get conversation with messages | +| `listConversations(filterParams)` | `Promise` | Filter/sort/paginate conversations | +| `subscribeToConversation(id, onUpdate?)` | `() => void` | Real-time updates via WebSocket (returns unsubscribe function) | +| `addMessage(conversation, message)` | `Promise` | Send a message | +| `getWhatsAppConnectURL(agentName)` | `string` | Get WhatsApp connection URL for agent | + +## Examples + +### Create Conversation + +```javascript +const conversation = await base44.agents.createConversation({ + agent_name: "support-agent", + metadata: { + order_id: "ORD-123", + category: "billing" + } +}); + +console.log(conversation.id); +``` + +### Get All Conversations + +```javascript +const conversations = await base44.agents.getConversations(); + +conversations.forEach(conv => { + console.log(conv.id, conv.agent_name, conv.created_date); +}); +``` + +### Get Single Conversation (with messages) + +```javascript +const conversation = await base44.agents.getConversation("conv-id-123"); + +console.log(conversation.messages); +``` + +### List with Filters + +```javascript +// Using filterParams object with q, sort, limit, skip, fields +const recent = await base44.agents.listConversations({ + q: { agent_name: "support-agent" }, + sort: "-created_date", + limit: 10, + skip: 0 +}); + +// Filter by metadata +const highPriority = await base44.agents.listConversations({ + q: { + agent_name: "support-agent", + "metadata.priority": "high" + }, + sort: "-updated_date", + limit: 20 +}); +``` + +### Subscribe to Updates (Real-time) + +```javascript +const unsubscribe = base44.agents.subscribeToConversation( + "conv-id-123", + (updatedConversation) => { + // Called when new messages arrive + console.log("New messages:", updatedConversation.messages); + } +); + +// Later: unsubscribe +unsubscribe(); +``` + +### Send a Message + +```javascript +const conversation = await base44.agents.getConversation("conv-id-123"); + +await base44.agents.addMessage(conversation, { + role: "user", + content: "What's the weather like today?" +}); +``` + +### Get WhatsApp Connection URL + +```javascript +const whatsappUrl = base44.agents.getWhatsAppConnectURL("support-agent"); +// Returns URL for users to connect with agent via WhatsApp +console.log(whatsappUrl); +``` + +## Message Structure + +```javascript +{ + role: "user" | "assistant" | "system", + content: "Message text or structured object", + created_date: "2024-01-15T10:30:00Z", + updated_date: "2024-01-15T10:30:00Z", + + // Optional fields + reasoning: { + content: "Agent's reasoning process", + timing: 1500 + }, + tool_calls: [{ + name: "search", + arguments: { query: "weather" }, + result: { ... }, + status: "success" + }], + file_urls: ["https://..."], + usage: { + prompt_tokens: 150, + completion_tokens: 50 + }, + metadata: { ... }, + custom_context: { ... } +} +``` + +## Conversation Structure + +```javascript +{ + id: "conv-id-123", + app_id: "app-id", + agent_name: "support-agent", + created_by_id: "user-id", + created_date: "2024-01-15T10:00:00Z", + updated_date: "2024-01-15T10:30:00Z", + messages: [ ... ], + metadata: { ... } +} +``` + +## Common Patterns + +### Chat Interface + +```javascript +// Load conversation +const conv = await base44.agents.getConversation(conversationId); +setMessages(conv.messages); + +// Subscribe to updates +const unsubscribe = base44.agents.subscribeToConversation(conversationId, (updated) => { + setMessages(updated.messages); +}); + +// Send message +async function sendMessage(text) { + await base44.agents.addMessage(conv, { role: "user", content: text }); +} + +// Cleanup on unmount +return () => unsubscribe(); +``` + +## Type Definitions + +### AgentConversation + +```typescript +/** An agent conversation containing messages exchanged with an AI agent. */ +interface AgentConversation { + /** Unique identifier for the conversation. */ + id: string; + /** Application ID. */ + app_id: string; + /** Name of the agent in this conversation. */ + agent_name: string; + /** ID of the user who created the conversation. */ + created_by_id: string; + /** When the conversation was created. */ + created_date: string; + /** When the conversation was last updated. */ + updated_date: string; + /** Array of messages in the conversation. */ + messages: AgentMessage[]; + /** Optional metadata associated with the conversation. */ + metadata?: Record; +} +``` + +### AgentMessage + +```typescript +/** A message in an agent conversation. */ +interface AgentMessage { + /** Unique identifier for the message. */ + id: string; + /** Role of the message sender. */ + role: "user" | "assistant" | "system"; + /** When the message was created. */ + created_date: string; + /** When the message was last updated. */ + updated_date: string; + /** Message content. */ + content?: string | Record; + /** Optional reasoning information for the message. */ + reasoning?: AgentMessageReasoning | null; + /** URLs to files attached to the message. */ + file_urls?: string[]; + /** Tool calls made by the agent. */ + tool_calls?: AgentMessageToolCall[]; + /** Token usage statistics. */ + usage?: AgentMessageUsage; + /** Whether the message is hidden from the user. */ + hidden?: boolean; + /** Custom context provided with the message. */ + custom_context?: AgentMessageCustomContext[]; + /** Model used to generate the message. */ + model?: string; + /** Checkpoint ID for the message. */ + checkpoint_id?: string; + /** Metadata about when and by whom the message was created. */ + metadata?: AgentMessageMetadata; +} +``` + +### Supporting Types + +```typescript +/** Reasoning information for an agent message. */ +interface AgentMessageReasoning { + /** When reasoning started. */ + start_date: string; + /** When reasoning ended. */ + end_date?: string; + /** Reasoning content. */ + content: string; +} + +/** A tool call made by the agent. */ +interface AgentMessageToolCall { + /** Tool call ID. */ + id: string; + /** Name of the tool called. */ + name: string; + /** Arguments passed to the tool as JSON string. */ + arguments_string: string; + /** Status of the tool call. */ + status: "running" | "success" | "error" | "stopped"; + /** Results from the tool call. */ + results?: string; +} + +/** Token usage statistics for an agent message. */ +interface AgentMessageUsage { + /** Number of tokens in the prompt. */ + prompt_tokens?: number; + /** Number of tokens in the completion. */ + completion_tokens?: number; +} + +/** Custom context provided with an agent message. */ +interface AgentMessageCustomContext { + /** Context message. */ + message: string; + /** Associated data for the context. */ + data: Record; + /** Type of context. */ + type: string; +} + +/** Metadata about when and by whom a message was created. */ +interface AgentMessageMetadata { + /** When the message was created. */ + created_date: string; + /** Email of the user who created the message. */ + created_by_email: string; + /** Full name of the user who created the message. */ + created_by_full_name: string; +} +``` + +### CreateConversationParams + +```typescript +/** Parameters for creating a new conversation. */ +interface CreateConversationParams { + /** The name of the agent to create a conversation with. */ + agent_name: string; + /** Optional metadata to attach to the conversation. */ + metadata?: Record; +} +``` + +### ModelFilterParams + +```typescript +/** Parameters for filtering, sorting, and paginating conversations. */ +interface ModelFilterParams { + /** Query object with field-value pairs for filtering. */ + q?: Record; + /** Sort parameter (e.g., "-created_date" for descending). */ + sort?: string | null; + /** Maximum number of results to return. */ + limit?: number | null; + /** Number of results to skip for pagination. */ + skip?: number | null; + /** Array of field names to include in the response. */ + fields?: string[] | null; +} +``` + +### AgentsModule + +```typescript +/** Agents module for managing AI agent conversations. */ +interface AgentsModule { + /** Gets all conversations from all agents in the app. */ + getConversations(): Promise; + + /** Gets a specific conversation by ID. */ + getConversation(conversationId: string): Promise; + + /** Lists conversations with filtering, sorting, and pagination. */ + listConversations(filterParams: ModelFilterParams): Promise; + + /** Creates a new conversation with an agent. */ + createConversation(conversation: CreateConversationParams): Promise; + + /** Adds a message to a conversation. */ + addMessage(conversation: AgentConversation, message: Partial): Promise; + + /** Subscribes to real-time updates for a conversation. Returns unsubscribe function. */ + subscribeToConversation(conversationId: string, onUpdate?: (conversation: AgentConversation) => void): () => void; + + /** Gets WhatsApp connection URL for an agent. */ + getWhatsAppConnectURL(agentName: string): string; +} +``` diff --git a/evals/fixtures/create-support-agent/skills/base44-sdk/references/client.md b/evals/fixtures/create-support-agent/skills/base44-sdk/references/client.md new file mode 100644 index 0000000..554bf2b --- /dev/null +++ b/evals/fixtures/create-support-agent/skills/base44-sdk/references/client.md @@ -0,0 +1,257 @@ +# Client Setup + +How to create and configure the Base44 client. + +## Contents +- [In Base44-Generated Apps](#in-base44-generated-apps) +- [In External Apps](#in-external-apps) +- [In Backend Functions](#in-backend-functions) +- [Authentication Modes](#authentication-modes) (Anonymous, User, Service Role) +- [Available Modules](#available-modules) +- [Client Methods](#client-methods) +- [Client Configuration Options](#client-configuration-options) + +## In Base44-Generated Apps + +The client is pre-configured and available as `base44`. Just use it: + +```javascript +const tasks = await base44.entities.Task.list(); +``` + +## In External Apps + +Install the SDK and create a client: + +```bash +npm install @base44/sdk +``` + +```javascript +import { createClient } from "@base44/sdk"; + +// IMPORTANT: The parameter name is 'appId' (NOT 'clientId', NOT 'id') +// IMPORTANT: onError must be nested inside 'options' object +const base44 = createClient({ + appId: "your-app-id", // Required: Use 'appId' parameter + token: "optional-user-token", // Optional: for pre-authenticated requests + options: { // Optional: configuration options + onError: (error) => { // Optional: error handler (must be in options) + console.error("Base44 error:", error); + } + } +}); +``` + +**Common Mistakes:** +- ❌ `createClient({ clientId: "..." })` - WRONG parameter name +- ❌ `createClient({ id: "..." })` - WRONG parameter name +- ❌ `createClient({ appId: "...", onError: ... })` - WRONG: onError must be in options +- ✅ `createClient({ appId: "..." })` - CORRECT parameter name +- ✅ `createClient({ appId: "...", options: { onError: ... } })` - CORRECT: onError in options + +## In Backend Functions + +Use `createClientFromRequest` to get a client with the caller's auth context: + +```javascript +import { createClientFromRequest } from "@base44/sdk"; + +Deno.serve(async (req) => { + const base44 = createClientFromRequest(req); + + // Client inherits authentication from the request + const user = await base44.auth.me(); + + return Response.json({ user }); +}); +``` + +## Authentication Modes + +| Mode | How to Get | Permissions | +|------|-----------|-------------| +| **Anonymous** | `createClient({ appId })` without token | Public data only | +| **User** | After `loginViaEmailPassword()` or via `createClientFromRequest` | User's own data | +| **Service Role** | `base44.asServiceRole.*` in backend | Full admin access | + +## Anonymous Mode + +No authentication. Can only access public resources. + +```javascript +const base44 = createClient({ appId: "your-app-id" }); + +// Only works if Task entity allows anonymous read +const publicTasks = await base44.entities.Task.list(); +``` + +## User Mode + +After user logs in, the client automatically includes their token. + +```javascript +const base44 = createClient({ appId: "your-app-id" }); + +// Login sets the token +await base44.auth.loginViaEmailPassword("user@example.com", "password"); + +// Subsequent requests are authenticated +const user = await base44.auth.me(); +const myTasks = await base44.entities.Task.list(); // filtered by permissions +``` + +## Service Role Mode + +Admin-level access. **Backend only.** + +```javascript +// Inside a backend function +Deno.serve(async (req) => { + const base44 = createClientFromRequest(req); + + // User mode - respects permissions + const myTasks = await base44.entities.Task.list(); + + // Service role - bypasses permissions + const allTasks = await base44.asServiceRole.entities.Task.list(); + const allUsers = await base44.asServiceRole.entities.User.list(); + const oauthToken = await base44.asServiceRole.connectors.getAccessToken("slack"); + + return Response.json({ myTasks, allTasks }); +}); +``` + +## Available Modules + +The client exposes these modules: + +```javascript +base44.entities // CRUD operations +base44.auth // Authentication +base44.agents // AI conversations +base44.functions // Backend function invocation +base44.integrations // Third-party services +base44.analytics // Event tracking +base44.appLogs // App usage logging +base44.users // User invitations + +// Service role only (backend) +base44.asServiceRole.entities +base44.asServiceRole.agents +base44.asServiceRole.functions +base44.asServiceRole.integrations +base44.asServiceRole.appLogs +base44.asServiceRole.connectors +``` + +## Client Methods + +The client provides these methods: + +```javascript +// Set authentication token for all subsequent requests +base44.setToken(newToken); + +// Cleanup WebSocket connections (call when done with client) +base44.cleanup(); +``` + +### setToken + +Updates the authentication token for all subsequent API requests and WebSocket connections. + +```javascript +// After receiving a token (e.g., from external auth) +base44.setToken("new-jwt-token"); +``` + +### cleanup + +Disconnects WebSocket connections. Call when you're done with the client or when the component unmounts. + +```javascript +// Cleanup on component unmount (React example) +useEffect(() => { + return () => base44.cleanup(); +}, []); +``` + +## Client Configuration Options + +```javascript +createClient({ + appId: "your-app-id", // Required: MUST use 'appId' (not 'clientId' or 'id') + token: "jwt-token", // Optional: pre-set auth token + options: { // Optional: configuration options + onError: (error) => {} // Optional: global error handler (must be in options) + } +}); +``` + +**⚠️ Critical:** +- The parameter name is `appId`, not `clientId` or `id`. Using the wrong parameter name will cause errors. +- The `onError` handler must be nested inside the `options` object, not at the top level. + +## Type Definitions + +### CreateClientConfig + +```typescript +/** Configuration for creating a Base44 client. */ +interface CreateClientConfig { + /** The Base44 app ID (required). */ + appId: string; + /** User authentication token. Used to authenticate as a specific user. */ + token?: string; + /** Service role authentication token (backend only). */ + serviceToken?: string; + /** Additional client options. */ + options?: CreateClientOptions; +} + +/** Options for creating a Base44 client. */ +interface CreateClientOptions { + /** Optional error handler called whenever an API error occurs. */ + onError?: (error: Error) => void; +} +``` + +### Base44Client + +```typescript +/** The Base44 client instance. */ +interface Base44Client { + /** Entities module for CRUD operations on your data models. */ + entities: EntitiesModule; + /** Integrations module for calling pre-built integration endpoints. */ + integrations: IntegrationsModule; + /** Auth module for user authentication and management. */ + auth: AuthModule; + /** Functions module for invoking custom backend functions. */ + functions: FunctionsModule; + /** Agents module for managing AI agent conversations. */ + agents: AgentsModule; + /** App logs module for tracking app usage. */ + appLogs: AppLogsModule; + /** Analytics module for tracking custom events. */ + analytics: AnalyticsModule; + + /** Cleanup function to disconnect WebSocket connections. */ + cleanup(): void; + + /** Sets a new authentication token for all subsequent requests. */ + setToken(newToken: string): void; + + /** Provides access to modules with elevated service role permissions (backend only). */ + readonly asServiceRole: { + entities: EntitiesModule; + integrations: IntegrationsModule; + connectors: ConnectorsModule; + functions: FunctionsModule; + agents: AgentsModule; + appLogs: AppLogsModule; + cleanup(): void; + }; +} +``` diff --git a/evals/fixtures/create-support-agent/skills/base44-sdk/references/connectors.md b/evals/fixtures/create-support-agent/skills/base44-sdk/references/connectors.md new file mode 100644 index 0000000..f3fbcb4 --- /dev/null +++ b/evals/fixtures/create-support-agent/skills/base44-sdk/references/connectors.md @@ -0,0 +1,113 @@ +# Connectors Module + +OAuth token management for external services. **Service role only** (backend functions). + +## Method + +```javascript +base44.asServiceRole.connectors.getAccessToken(integrationType): Promise +``` + +Returns a raw OAuth access token for the specified service. + +## Supported Services + +| Integration Type | Service | +|-----------------|---------| +| `"googlecalendar"` | Google Calendar | +| `"googledrive"` | Google Drive | +| `"slack"` | Slack | +| `"notion"` | Notion | +| `"salesforce"` | Salesforce | +| `"hubspot"` | HubSpot | +| `"linkedin"` | LinkedIn | +| `"tiktok"` | TikTok | +| `"github"` | GitHub | + +## Example Usage + +```javascript +// Backend function only +Deno.serve(async (req) => { + const base44 = createClientFromRequest(req); + + // Get OAuth token for Slack + const slackToken = await base44.asServiceRole.connectors.getAccessToken("slack"); + + // Use token directly with Slack API + const response = await fetch("https://slack.com/api/chat.postMessage", { + method: "POST", + headers: { + "Authorization": `Bearer ${slackToken}`, + "Content-Type": "application/json" + }, + body: JSON.stringify({ + channel: "#general", + text: "Hello from Base44!" + }) + }); + + return Response.json(await response.json()); +}); +``` + +## Google Calendar Example + +```javascript +Deno.serve(async (req) => { + const base44 = createClientFromRequest(req); + + const token = await base44.asServiceRole.connectors.getAccessToken("googlecalendar"); + + // List upcoming events + const response = await fetch( + "https://www.googleapis.com/calendar/v3/calendars/primary/events?" + + new URLSearchParams({ + maxResults: "10", + orderBy: "startTime", + singleEvents: "true", + timeMin: new Date().toISOString() + }), + { + headers: { "Authorization": `Bearer ${token}` } + } + ); + + const events = await response.json(); + return Response.json(events); +}); +``` + +## Setup Requirements + +1. **Builder plan** or higher +2. **Backend functions** enabled +3. **Connector configured** in Base44 dashboard (OAuth flow completed) + +## Important Notes + +- **One account per connector per app**: All users share the same connected account +- **Backend only**: `connectors` module not available in frontend code +- **Service role required**: Must use `base44.asServiceRole.connectors` +- **You handle the API calls**: Base44 only provides the token; you make the actual API requests +- **Token refresh**: Base44 handles token refresh automatically + +## Type Definitions + +```typescript +/** + * The type of external integration/connector. + * Examples: 'googlecalendar', 'slack', 'github', 'notion', etc. + */ +type ConnectorIntegrationType = string; + +/** Connectors module for managing OAuth tokens for external services. */ +interface ConnectorsModule { + /** + * Retrieves an OAuth access token for a specific external integration type. + * @param integrationType - The type of integration (e.g., 'googlecalendar', 'slack'). + * @returns Promise resolving to the access token string. + */ + getAccessToken(integrationType: ConnectorIntegrationType): Promise; +} +``` diff --git a/evals/fixtures/create-support-agent/skills/base44-sdk/references/entities.md b/evals/fixtures/create-support-agent/skills/base44-sdk/references/entities.md new file mode 100644 index 0000000..a7bdf35 --- /dev/null +++ b/evals/fixtures/create-support-agent/skills/base44-sdk/references/entities.md @@ -0,0 +1,253 @@ +# Entities Module + +CRUD operations on data models. Access via `base44.entities.EntityName.method()`. + +## Contents +- [Methods](#methods) +- [Examples](#examples) (Create, Bulk Create, List, Filter, Get, Update, Delete, Subscribe) +- [User Entity](#user-entity) +- [Service Role Access](#service-role-access) +- [Permissions](#permissions) + +## Methods + +| Method | Signature | Description | +|--------|-----------|-------------| +| `create(data)` | `Promise` | Create one record | +| `bulkCreate(dataArray)` | `Promise` | Create multiple records | +| `list(sort?, limit?, skip?, fields?)` | `Promise` | Get all records (paginated) | +| `filter(query, sort?, limit?, skip?, fields?)` | `Promise` | Get records matching conditions | +| `get(id)` | `Promise` | Get single record by ID | +| `update(id, data)` | `Promise` | Update record (partial update) | +| `delete(id)` | `Promise` | Delete record by ID | +| `deleteMany(query)` | `Promise` | Delete all matching records | +| `importEntities(file)` | `Promise` | Import from CSV (frontend only) | +| `subscribe(callback)` | `() => void` | Subscribe to realtime updates (returns unsubscribe function) | + +## Examples + +### Create + +```javascript +const task = await base44.entities.Task.create({ + title: "Complete documentation", + status: "pending", + dueDate: "2024-12-31" +}); +``` + +### Bulk Create + +```javascript +const tasks = await base44.entities.Task.bulkCreate([ + { title: "Task 1", status: "pending" }, + { title: "Task 2", status: "pending" } +]); +``` + +### List with Pagination + +```javascript +// Get first 10 records, sorted by created_date descending +const tasks = await base44.entities.Task.list( + "-created_date", // sort (string: prefix with - for descending) + 10, // limit + 0 // skip +); + +// Get next page +const page2 = await base44.entities.Task.list("-created_date", 10, 10); +``` + +### Filter + +```javascript +// Simple filter +const pending = await base44.entities.Task.filter({ status: "pending" }); + +// Multiple conditions +const myPending = await base44.entities.Task.filter({ + status: "pending", + assignedTo: userId +}); + +// With sort, limit, skip +const recent = await base44.entities.Task.filter( + { status: "pending" }, + "-created_date", // sort (string: prefix with - for descending) + 5, + 0 +); + +// Select specific fields +const titles = await base44.entities.Task.filter( + { status: "pending" }, + null, + null, + null, + ["id", "title"] +); +``` + +### Get by ID + +```javascript +const task = await base44.entities.Task.get("task-id-123"); +``` + +### Update + +```javascript +// Partial update - only specified fields change +await base44.entities.Task.update("task-id-123", { + status: "completed", + completedAt: new Date().toISOString() +}); +``` + +### Delete + +```javascript +// Single record +await base44.entities.Task.delete("task-id-123"); + +// Multiple records matching query +await base44.entities.Task.deleteMany({ status: "archived" }); +``` + +### Subscribe to Realtime Updates + +```javascript +// Subscribe to all changes on Task entity +const unsubscribe = base44.entities.Task.subscribe((event) => { + console.log(`Task ${event.id} was ${event.type}:`, event.data); + // event.type is "create", "update", or "delete" +}); + +// Later: unsubscribe to stop receiving updates +unsubscribe(); +``` + +**Event structure:** +```javascript +{ + type: "create" | "update" | "delete", + data: { ... }, // the entity data + id: "entity-id", // the affected entity's ID + timestamp: "2024-01-15T10:30:00Z" +} +``` + +## User Entity + +Every app has a built-in `User` entity with special rules: + +- Regular users can only read/update **their own** record +- Cannot create users via `entities.create()` - use `auth.register()` instead +- Service role has full access to all user records + +```javascript +// Get current user's record +const me = await base44.entities.User.get(currentUserId); + +// Service role: get any user +const anyUser = await base44.asServiceRole.entities.User.get(userId); +``` + +## Service Role Access + +For admin-level operations (bypass user permissions): + +```javascript +// Backend only +const allTasks = await base44.asServiceRole.entities.Task.list(); +const allUsers = await base44.asServiceRole.entities.User.list(); +``` + +## Permissions (RLS & FLS) + +Data access is controlled by **Row Level Security (RLS)** and **Field Level Security (FLS)** rules defined in entity schemas. + +1. **Authentication level**: anonymous, authenticated, or service role +2. **RLS rules**: Control which records (rows) users can create/read/update/delete +3. **FLS rules**: Control which fields users can read/write within accessible records + +Operations succeed or fail based on these rules - no partial results. + +RLS and FLS are configured in entity schema files (`base44/entities/*.jsonc`). See [entities-create.md](../../base44-cli/references/entities-create.md#row-level-security-rls) for configuration details. + +**Note:** `asServiceRole` sets the user's role to `"admin"` but does NOT bypass RLS. Your RLS rules must include admin access (e.g., `{ "user_condition": { "role": "admin" } }`) for service role operations to succeed. + +## Type Definitions + +### RealtimeEvent + +```typescript +/** Event types for realtime entity updates. */ +type RealtimeEventType = "create" | "update" | "delete"; + +/** Payload received when a realtime event occurs. */ +interface RealtimeEvent { + /** The type of change that occurred. */ + type: RealtimeEventType; + /** The entity data. */ + data: any; + /** The unique identifier of the affected entity. */ + id: string; + /** ISO 8601 timestamp of when the event occurred. */ + timestamp: string; +} + +/** Callback function invoked when a realtime event occurs. */ +type RealtimeCallback = (event: RealtimeEvent) => void; + +/** Function returned from subscribe, call it to unsubscribe. */ +type Subscription = () => void; +``` + +### EntityHandler + +```typescript +/** Entity handler providing CRUD operations for a specific entity type. */ +interface EntityHandler { + /** Lists records with optional pagination and sorting. */ + list(sort?: string, limit?: number, skip?: number, fields?: string[]): Promise; + + /** Filters records based on a query. */ + filter(query: Record, sort?: string, limit?: number, skip?: number, fields?: string[]): Promise; + + /** Gets a single record by ID. */ + get(id: string): Promise; + + /** Creates a new record. */ + create(data: Record): Promise; + + /** Updates an existing record. */ + update(id: string, data: Record): Promise; + + /** Deletes a single record by ID. */ + delete(id: string): Promise; + + /** Deletes multiple records matching a query. */ + deleteMany(query: Record): Promise; + + /** Creates multiple records in a single request. */ + bulkCreate(data: Record[]): Promise; + + /** Imports records from a file (frontend only). */ + importEntities(file: File): Promise; + + /** Subscribes to realtime updates. Returns unsubscribe function. */ + subscribe(callback: RealtimeCallback): Subscription; +} +``` + +### EntitiesModule + +```typescript +/** Entities module for managing app data. */ +interface EntitiesModule { + /** Access any entity by name dynamically. */ + [entityName: string]: EntityHandler; +} +``` diff --git a/evals/fixtures/create-support-agent/skills/base44-sdk/references/functions.md b/evals/fixtures/create-support-agent/skills/base44-sdk/references/functions.md new file mode 100644 index 0000000..8072465 --- /dev/null +++ b/evals/fixtures/create-support-agent/skills/base44-sdk/references/functions.md @@ -0,0 +1,216 @@ +# Functions Module + +Invoke custom backend functions via `base44.functions`. + +## Contents +- [Method](#method) +- [Invoking Functions](#invoking-functions) (Frontend, File Upload, Service Role, REST API) +- [Writing Backend Functions](#writing-backend-functions) (Basic, Service Role, Secrets, Errors) +- [Setup Requirements](#setup-requirements) +- [Authentication Modes](#authentication-modes) + +## Method + +```javascript +base44.functions.invoke(functionName, data): Promise +``` + +- `functionName`: Name of the backend function +- `data`: Object of parameters (sent as JSON, or multipart if contains File objects) +- Returns: Whatever the function returns + +## Invoking Functions + +### From Frontend + +```javascript +const result = await base44.functions.invoke("processOrder", { + orderId: "order-123", + action: "ship" +}); + +console.log(result); +``` + +### With File Upload + +```javascript +const fileInput = document.querySelector('input[type="file"]'); +const file = fileInput.files[0]; + +// Automatically uses multipart/form-data when File objects present +const result = await base44.functions.invoke("uploadDocument", { + file: file, + category: "invoices" +}); +``` + +### With Service Role (Backend) + +```javascript +// Inside another backend function +const result = await base44.asServiceRole.functions.invoke("adminTask", { + userId: "user-123" +}); +``` + +### Via REST API (curl) + +Functions can be called via HTTP POST to your app domain: + +```bash +curl -X POST "https:///functions/" \ + -H "Content-Type: application/json" \ + -d '{"key": "value"}' +``` + +## Writing Backend Functions + +Backend functions run on Deno. Must export using `Deno.serve()`. + +### Required Directory Structure + +Each function must be in its own subdirectory under `base44/functions/` with a configuration file: + +``` +base44/ + functions/ + process-order/ # kebab-case directory name + function.jsonc # required configuration + index.ts # entry point +``` + +**function.jsonc:** +```jsonc +{ + "name": "process-order", + "entry": "index.ts" +} +``` + +For complete setup and deployment instructions, see [functions-create.md](../../base44-cli/references/functions-create.md) in base44-cli. + +### Basic Structure + +```javascript +// base44/functions/process-order/index.ts +import { createClientFromRequest } from "npm:@base44/sdk"; + +Deno.serve(async (req) => { + // Get authenticated client from request + const base44 = createClientFromRequest(req); + + // Parse input + const { orderId, action } = await req.json(); + + // Your logic here + const order = await base44.entities.Orders.get(orderId); + + // Return response + return Response.json({ + success: true, + order: order + }); +}); +``` + +### With Service Role Access + +```javascript +import { createClientFromRequest } from "npm:@base44/sdk"; + +Deno.serve(async (req) => { + const base44 = createClientFromRequest(req); + + // Check user is authenticated + const user = await base44.auth.me(); + if (!user) { + return Response.json({ error: "Unauthorized" }, { status: 401 }); + } + + // Use service role for admin operations + const allOrders = await base44.asServiceRole.entities.Orders.list(); + + return Response.json({ orders: allOrders }); +}); +``` + +### Using Secrets + +```javascript +Deno.serve(async (req) => { + // Access environment variables (configured in app settings) + const apiKey = Deno.env.get("STRIPE_API_KEY"); + + const response = await fetch("https://api.stripe.com/v1/charges", { + headers: { + "Authorization": `Bearer ${apiKey}` + } + }); + + return Response.json(await response.json()); +}); +``` + +### Error Handling + +```javascript +import { createClientFromRequest } from "npm:@base44/sdk"; + +Deno.serve(async (req) => { + try { + const base44 = createClientFromRequest(req); + const { orderId } = await req.json(); + + const order = await base44.entities.Orders.get(orderId); + if (!order) { + return Response.json( + { error: "Order not found" }, + { status: 404 } + ); + } + + return Response.json({ order }); + + } catch (error) { + return Response.json( + { error: error.message }, + { status: 500 } + ); + } +}); +``` + +## Setup Requirements + +1. Enable Backend Functions in app settings (requires appropriate plan) +2. Create function files in `/functions` folder +3. Configure secrets via app dashboard for API keys + +## Authentication Modes + +| Mode | Context | Permissions | +|------|---------|-------------| +| User | `base44.functions.invoke()` | Runs under calling user's permissions | +| Service Role | `base44.asServiceRole.functions.invoke()` | Admin-level access | + +Inside the function, use `createClientFromRequest(req)` to get a client that inherits the caller's auth context. + +## Type Definitions + +```typescript +/** Functions module for invoking custom backend functions. */ +interface FunctionsModule { + /** + * Invokes a custom backend function by name. + * + * If any parameter is a File object, the request will automatically be + * sent as multipart/form-data. Otherwise, it will be sent as JSON. + * + * @param functionName - The name of the function to invoke. + * @param data - An object containing named parameters for the function. + * @returns Promise resolving to the function's response. + */ + invoke(functionName: string, data: Record): Promise; +} +``` diff --git a/evals/fixtures/create-support-agent/skills/base44-sdk/references/integrations.md b/evals/fixtures/create-support-agent/skills/base44-sdk/references/integrations.md new file mode 100644 index 0000000..f356a18 --- /dev/null +++ b/evals/fixtures/create-support-agent/skills/base44-sdk/references/integrations.md @@ -0,0 +1,377 @@ +# Integrations Module + +Access third-party services via `base44.integrations`. + +## Types of Integrations + +1. **Core/Built-in**: AI, email, file uploads (available by default) +2. **Catalog integrations**: Pre-built connectors from Base44 catalog +3. **Custom integrations**: Your own OpenAPI-based integrations + +## Accessing Integrations + +```javascript +// Core integrations +base44.integrations.Core.FunctionName(params) + +// Custom integrations +base44.integrations.custom.call(slug, operationId, params) +``` + +## Core Integrations + +### InvokeLLM (AI Text Generation) + +Generate text or structured JSON data using AI models. + +```javascript +// Basic prompt - returns string +const response = await base44.integrations.Core.InvokeLLM({ + prompt: "Summarize this text: ..." +}); + +// With internet context (uses Google Search, Maps, News) +const response = await base44.integrations.Core.InvokeLLM({ + prompt: "What's the current weather in NYC?", + add_context_from_internet: true +}); + +// Structured JSON response - returns object +const response = await base44.integrations.Core.InvokeLLM({ + prompt: "Analyze the sentiment of: 'Great product but slow shipping'", + response_json_schema: { + type: "object", + properties: { + sentiment: { type: "string", enum: ["positive", "negative", "mixed"] }, + score: { type: "number", description: "Score from 1-10" }, + key_points: { type: "array", items: { type: "string" } } + } + } +}); +// Returns: { sentiment: "mixed", score: 7, key_points: ["great product", "slow shipping"] } + +// With file attachments (uploaded via UploadFile) +const response = await base44.integrations.Core.InvokeLLM({ + prompt: "Describe what's in this image", + file_urls: ["https://...uploaded_image.png"] +}); +``` + +**Parameters:** +- `prompt` (string, required): The prompt text to send to the model +- `add_context_from_internet` (boolean, optional): If true, uses Google Search/Maps/News for real-time context +- `response_json_schema` (object, optional): JSON schema for structured output +- `file_urls` (string[], optional): URLs of uploaded files for context + +### GenerateImage + +Create AI-generated images from text prompts. + +```javascript +const { url } = await base44.integrations.Core.GenerateImage({ + prompt: "A serene mountain landscape with a lake" +}); +console.log(url); // https://...generated_image.png +``` + +### SendEmail + +Send emails to registered users. Every app gets this integration (no plan upgrade required). + +```javascript +await base44.integrations.Core.SendEmail({ + to: "user@example.com", + subject: "Welcome!", + body: "

Hello

Welcome to our app.

", + from_name: "My App" // optional, defaults to app name +}); +``` + +**Parameters:** +- `to` (string, required): Recipient email address +- `subject` (string, required): Email subject line +- `body` (string, required): Plain text or HTML email body +- `from_name` (string, optional): Sender name displayed to recipient + +**Limitations:** +- 1 credit per email (2 credits with custom domain) + +### UploadFile (Public) + +Upload files to public storage. + +```javascript +const fileInput = document.querySelector('input[type="file"]'); +const { file_url } = await base44.integrations.Core.UploadFile({ + file: fileInput.files[0] +}); +console.log(file_url); // https://...uploaded_file.pdf +``` + +### UploadPrivateFile + +Upload files to private storage that requires a signed URL to access. + +```javascript +const { file_uri } = await base44.integrations.Core.UploadPrivateFile({ + file: fileInput.files[0] +}); +console.log(file_uri); // "private/user123/document.pdf" + +// Create a signed URL to access the file +const { signed_url } = await base44.integrations.Core.CreateFileSignedUrl({ + file_uri, + expires_in: 3600 // URL expires in 1 hour (default: 300 seconds) +}); +console.log(signed_url); // Temporary URL to access the private file +``` + +### CreateFileSignedUrl + +Generate temporary access links for private files. + +```javascript +const { signed_url } = await base44.integrations.Core.CreateFileSignedUrl({ + file_uri: "private/user123/document.pdf", + expires_in: 7200 // 2 hours +}); +``` + +**Parameters:** +- `file_uri` (string, required): URI from UploadPrivateFile +- `expires_in` (number, optional): Expiration time in seconds (default: 300) + +### ExtractDataFromUploadedFile + +Extract structured data from uploaded files using AI. + +```javascript +// First upload the file +const { file_url } = await base44.integrations.Core.UploadFile({ file }); + +// Then extract structured data +const result = await base44.integrations.Core.ExtractDataFromUploadedFile({ + file_url, + json_schema: { + type: "object", + properties: { + invoice_number: { type: "string" }, + total_amount: { type: "number" }, + date: { type: "string" }, + vendor_name: { type: "string" } + } + } +}); +console.log(result); // { invoice_number: "INV-12345", total_amount: 1250.00, ... } +``` + +## Custom Integrations + +Custom integrations allow workspace administrators to connect any external API by importing an OpenAPI specification. Use `base44.integrations.custom.call()` to invoke them. + +### Syntax + +```javascript +const response = await base44.integrations.custom.call( + slug, // Integration identifier (set by admin) + operationId, // Endpoint in "method:path" format + params // Optional: payload, pathParams, queryParams +); +``` + +### Examples + +```javascript +// GET request with query params +const response = await base44.integrations.custom.call( + "my-crm", + "get:/contacts", + { queryParams: { limit: 10, status: "active" } } +); + +// POST request with body +const response = await base44.integrations.custom.call( + "my-crm", + "post:/contacts", + { payload: { name: "John Doe", email: "john@example.com" } } +); + +// Request with path parameters +const response = await base44.integrations.custom.call( + "github", + "post:/repos/{owner}/{repo}/issues", + { + pathParams: { owner: "myorg", repo: "myrepo" }, + payload: { title: "Bug report", body: "Something is broken" } + } +); +``` + +### Response Structure + +```javascript +{ + success: true, // Whether external API returned 2xx + status_code: 200, // HTTP status code + data: { ... } // Response from external API +} +``` + +## Requirements + +- **Core integrations**: Available on all plans +- **Catalog/Custom integrations**: Require Builder plan or higher + +## Type Definitions + +### Core Integration Parameters + +```typescript +/** Parameters for the InvokeLLM function. */ +interface InvokeLLMParams { + /** The prompt text to send to the model. */ + prompt: string; + /** If true, uses Google Search/Maps/News for real-time context. */ + add_context_from_internet?: boolean; + /** JSON schema for structured output. If provided, returns object instead of string. */ + response_json_schema?: object; + /** File URLs (from UploadFile) to provide as context. */ + file_urls?: string[]; +} + +/** Parameters for the GenerateImage function. */ +interface GenerateImageParams { + /** Description of the image to generate. */ + prompt: string; +} + +/** Result from GenerateImage. */ +interface GenerateImageResult { + /** URL of the generated image. */ + url: string; +} + +/** Parameters for the UploadFile function. */ +interface UploadFileParams { + /** The file object to upload. */ + file: File; +} + +/** Result from UploadFile. */ +interface UploadFileResult { + /** URL of the uploaded file. */ + file_url: string; +} + +/** Parameters for the SendEmail function. */ +interface SendEmailParams { + /** Recipient email address. */ + to: string; + /** Email subject line. */ + subject: string; + /** Plain text or HTML email body. */ + body: string; + /** Sender name (defaults to app name). */ + from_name?: string; +} + +/** Parameters for ExtractDataFromUploadedFile. */ +interface ExtractDataFromUploadedFileParams { + /** URL of the uploaded file. */ + file_url: string; + /** JSON schema defining fields to extract. */ + json_schema: object; +} + +/** Parameters for UploadPrivateFile. */ +interface UploadPrivateFileParams { + /** The file object to upload. */ + file: File; +} + +/** Result from UploadPrivateFile. */ +interface UploadPrivateFileResult { + /** URI of the private file (used for signed URLs). */ + file_uri: string; +} + +/** Parameters for CreateFileSignedUrl. */ +interface CreateFileSignedUrlParams { + /** URI from UploadPrivateFile. */ + file_uri: string; + /** Expiration time in seconds (default: 300). */ + expires_in?: number; +} + +/** Result from CreateFileSignedUrl. */ +interface CreateFileSignedUrlResult { + /** Temporary signed URL to access the file. */ + signed_url: string; +} +``` + +### CoreIntegrations + +```typescript +/** Core package containing built-in Base44 integration functions. */ +interface CoreIntegrations { + InvokeLLM(params: InvokeLLMParams): Promise; + GenerateImage(params: GenerateImageParams): Promise; + UploadFile(params: UploadFileParams): Promise; + SendEmail(params: SendEmailParams): Promise; + ExtractDataFromUploadedFile(params: ExtractDataFromUploadedFileParams): Promise; + UploadPrivateFile(params: UploadPrivateFileParams): Promise; + CreateFileSignedUrl(params: CreateFileSignedUrlParams): Promise; +} +``` + +### Custom Integrations + +```typescript +/** Parameters for calling a custom integration endpoint. */ +interface CustomIntegrationCallParams { + /** Request body payload. */ + payload?: Record; + /** Path parameters to substitute in the URL. */ + pathParams?: Record; + /** Query string parameters. */ + queryParams?: Record; + /** Additional headers for this request. */ + headers?: Record; +} + +/** Response from a custom integration call. */ +interface CustomIntegrationCallResponse { + /** Whether the external API returned a 2xx status code. */ + success: boolean; + /** The HTTP status code from the external API. */ + status_code: number; + /** The response data from the external API. */ + data: any; +} + +/** Module for calling custom workspace-level API integrations. */ +interface CustomIntegrationsModule { + /** + * Call a custom integration endpoint. + * @param slug - The integration's unique identifier. + * @param operationId - The operation ID from the OpenAPI spec. + * @param params - Optional payload, pathParams, queryParams, headers. + */ + call(slug: string, operationId: string, params?: CustomIntegrationCallParams): Promise; +} +``` + +### IntegrationsModule + +```typescript +/** Integrations module for calling integration endpoints. */ +type IntegrationsModule = { + /** Core package with built-in integrations. */ + Core: CoreIntegrations; + /** Custom integrations module. */ + custom: CustomIntegrationsModule; + /** Additional integration packages (dynamic). */ + [packageName: string]: any; +}; +``` diff --git a/evals/fixtures/create-support-agent/skills/base44-sdk/references/users.md b/evals/fixtures/create-support-agent/skills/base44-sdk/references/users.md new file mode 100644 index 0000000..4205b06 --- /dev/null +++ b/evals/fixtures/create-support-agent/skills/base44-sdk/references/users.md @@ -0,0 +1,82 @@ +# Users Module + +Invite users to the app via `base44.users`. + +## Contents +- [Methods](#methods) +- [Examples](#examples) (Invite User) +- [Roles](#roles) +- [Notes](#notes) + +## Methods + +| Method | Signature | Description | +|--------|-----------|-------------| +| `inviteUser(user_email, role)` | `Promise` | Invite a user to the app | + +## Examples + +### Invite User + +```javascript +// Invite a user with "user" role +await base44.users.inviteUser("newuser@example.com", "user"); + +// Invite an admin +await base44.users.inviteUser("admin@example.com", "admin"); +``` + +### Invite Multiple Users + +```javascript +const usersToInvite = [ + { email: "user1@example.com", role: "user" }, + { email: "user2@example.com", role: "user" }, + { email: "manager@example.com", role: "admin" } +]; + +for (const user of usersToInvite) { + await base44.users.inviteUser(user.email, user.role); + console.log(`Invited ${user.email} as ${user.role}`); +} +``` + +## Roles + +The `role` parameter must be one of: + +| Role | Description | +|------|-------------| +| `"user"` | Standard user with default permissions | +| `"admin"` | Administrator with elevated permissions | + +**Note:** Only `"user"` and `"admin"` are valid role values. An error will be thrown if you pass any other value. + +## Notes + +- **Email invitation**: The invited user receives an email with a link to join the app +- **Duplicate handling**: Inviting an existing user will re-send the invitation +- **Also available in auth**: `base44.auth.inviteUser()` provides the same functionality +- **Role validation**: Only `"user"` or `"admin"` are accepted + +```javascript +// These are equivalent: +await base44.users.inviteUser("newuser@example.com", "user"); +await base44.auth.inviteUser("newuser@example.com", "user"); +``` + +## Type Definitions + +```typescript +/** Users module for inviting users to the app. */ +interface UsersModule { + /** + * Invite a user to the application. + * @param user_email - User's email address. + * @param role - User's role ('user' or 'admin'). + * @returns Promise resolving when the invitation is sent. + * @throws Error if role is not 'user' or 'admin'. + */ + inviteUser(user_email: string, role: "user" | "admin"): Promise; +} +``` diff --git a/evals/fixtures/existing-app-add-feature/AGENTS.md b/evals/fixtures/existing-app-add-feature/AGENTS.md new file mode 100644 index 0000000..e3696a9 --- /dev/null +++ b/evals/fixtures/existing-app-add-feature/AGENTS.md @@ -0,0 +1,21 @@ +# Notification App + +An application that handles orders and sends email notifications. + +## Project Structure + +- `src/` - Frontend source code +- `src/lib/base44.ts` - Base44 SDK client +- `base44/` - Base44 configuration +- `base44/entities/` - Entity definitions (User, Order) +- `base44/functions/` - Backend functions (to be created) + +## Existing Entities + +- **User**: email, name, notification_preferences +- **Order**: user_id, items, status, total + +## Available Skills + +- **base44-cli**: CLI for managing Base44 entities, functions, and deployment +- **base44-sdk**: SDK for building features with Base44 APIs diff --git a/evals/fixtures/existing-app-add-feature/eval.json b/evals/fixtures/existing-app-add-feature/eval.json new file mode 100644 index 0000000..95e46ff --- /dev/null +++ b/evals/fixtures/existing-app-add-feature/eval.json @@ -0,0 +1,83 @@ +{ + "$schema": "../../eval.schema.json", + "name": "add-notification-function", + "description": "Test creating a backend function to send order status notification emails", + "prompt": "Create a backend function called 'send-order-notification' that sends an email notification when an order status changes. The function should: 1) Accept order_id and new_status in the request body, 2) Fetch the order and user details using service role, 3) Send an email to the user about their order status update using integrations.Core.SendEmail.", + "expectedSkills": ["base44-cli"], + "checks": [ + { + "type": "file-exists", + "description": "Function directory created", + "filePath": "base44/functions/send-order-notification/function.jsonc" + }, + { + "type": "file-exists", + "description": "Function entry point created", + "filePath": "base44/functions/send-order-notification/index.ts" + }, + { + "type": "valid-json", + "description": "function.jsonc is valid JSON", + "filePath": "base44/functions/send-order-notification/function.jsonc" + }, + { + "type": "file-content", + "description": "Uses Deno.serve", + "filePath": "base44/functions/send-order-notification/index.ts", + "pattern": "Deno\\.serve" + }, + { + "type": "file-content", + "description": "Uses createClientFromRequest", + "filePath": "base44/functions/send-order-notification/index.ts", + "pattern": "createClientFromRequest" + }, + { + "type": "file-content", + "description": "Uses asServiceRole", + "filePath": "base44/functions/send-order-notification/index.ts", + "pattern": "asServiceRole" + }, + { + "type": "file-content", + "description": "Uses SendEmail integration", + "filePath": "base44/functions/send-order-notification/index.ts", + "pattern": "SendEmail|integrations\\.Core" + }, + { + "type": "contains", + "description": "mentions function name", + "value": "send-order-notification" + }, + { + "type": "function-def", + "description": "Function config validates against Base44 function schema", + "filePath": "base44/functions/send-order-notification/function.jsonc", + "target": "file" + }, + { + "type": "file-content", + "description": "Imports createClientFromRequest from npm:@base44/sdk", + "filePath": "base44/functions/send-order-notification/index.ts", + "pattern": "createClientFromRequest.*from.*@base44/sdk|npm:@base44/sdk" + }, + { + "type": "file-content", + "description": "Parses request body for order_id and new_status", + "filePath": "base44/functions/send-order-notification/index.ts", + "pattern": "await\\s+req\\.json\\(\\)" + }, + { + "type": "file-content", + "description": "Uses integrations.Core.SendEmail (not just 'SendEmail')", + "filePath": "base44/functions/send-order-notification/index.ts", + "pattern": "integrations\\.Core\\.SendEmail|integrations\\.[\\w]+\\.SendEmail" + }, + { + "type": "file-content", + "description": "Returns proper Response", + "filePath": "base44/functions/send-order-notification/index.ts", + "pattern": "Response\\.json\\(" + } + ] +} diff --git a/evals/fixtures/existing-app-add-feature/project/.claude/settings.json b/evals/fixtures/existing-app-add-feature/project/.claude/settings.json new file mode 100644 index 0000000..ab60505 --- /dev/null +++ b/evals/fixtures/existing-app-add-feature/project/.claude/settings.json @@ -0,0 +1,6 @@ +{ + "permissions": { + "allow": ["Read", "Write", "Edit", "Bash", "Glob", "Grep"] + }, + "skills": ["./skills"] +} diff --git a/evals/fixtures/existing-app-add-feature/project/AGENTS.md b/evals/fixtures/existing-app-add-feature/project/AGENTS.md new file mode 100644 index 0000000..6c0f2be --- /dev/null +++ b/evals/fixtures/existing-app-add-feature/project/AGENTS.md @@ -0,0 +1,25 @@ +IMPORTANT: Prefer retrieval-led reasoning over pre-training-led reasoning. Base44 is NOT Firebase, Supabase, or any other SDK you know. Every API name below is the ONLY correct form. Always verify against this index and the referenced docs. + +[Base44 SDK Docs Index]|root: ./skills/base44-sdk/references +|entities.md: CRUD - create(data), list(), filter(query), get(id), update(id,data), delete(id), subscribe(cb) +|auth.md: Auth - loginViaEmailPassword(), loginWithProvider('google'), me(), register(), logout() +|base44-agents.md: Agents - createConversation({agent_name}), addMessage(conv,{role,content}), subscribeToConversation(id,cb) +|functions.md: Functions - invoke(name, data) +|integrations.md: Core - InvokeLLM({prompt}), SendEmail({to,subject,body}), UploadFile({file}) +|client.md: Client - createClient({appId}), import from "@base44/sdk" +|QUICK_REFERENCE.md: All method signatures + +[Base44 CLI Docs Index]|root: ./skills/base44-cli/references +|entities-create.md: Entity schema - {name:"PascalCase", type:"object", properties:{}, required:[]} +|functions-create.md: Function - function.jsonc {name,entry} + index.ts with Deno.serve + createClientFromRequest +|create.md: Project creation - npx base44 create name -p . +|deploy.md: Deploy - npx base44 deploy -y +|agents-push.md: Agent config - {name:"snake_case", description, instructions, tool_configs:[{entity_name,allowed_operations}|{function_name,description}]} + +[Common Mistakes - NEVER use these hallucinated names] +|SDK: Do NOT use find() → use filter(), findOne() → get(), insert() → create(), remove() → delete(), onChange() → subscribe() +|SDK: Do NOT use signInWithGoogle() → use loginWithProvider('google'), functions.call() → functions.invoke() +|SDK: Do NOT use ai.generate() → use integrations.Core.InvokeLLM({prompt}) +|CLI: Entity names must be PascalCase alphanumeric. File names must be kebab-case.jsonc +|CLI: Agent names must be lowercase with underscores only. Function entry field is "entry" not "entrypoint" +|CLI: Backend functions import from "npm:@base44/sdk" (Deno requires npm: prefix) diff --git a/evals/fixtures/existing-app-add-feature/project/CLAUDE.md b/evals/fixtures/existing-app-add-feature/project/CLAUDE.md new file mode 100644 index 0000000..724ce4d --- /dev/null +++ b/evals/fixtures/existing-app-add-feature/project/CLAUDE.md @@ -0,0 +1,7 @@ +# Base44 Development Guidelines + +When working with Base44 projects, first explore the existing project structure to understand what already exists. Look at existing code, config files, and patterns in use. + +Then consult the AGENTS.md index in the project root for correct API patterns. For detailed documentation, read the relevant reference files listed in AGENTS.md. + +IMPORTANT: Prefer retrieval-led reasoning over pre-training-led reasoning. Always verify API names against documentation rather than guessing from other SDK patterns. diff --git a/evals/fixtures/existing-app-add-feature/project/base44/config.jsonc b/evals/fixtures/existing-app-add-feature/project/base44/config.jsonc new file mode 100644 index 0000000..ebca7e2 --- /dev/null +++ b/evals/fixtures/existing-app-add-feature/project/base44/config.jsonc @@ -0,0 +1,6 @@ +{ + "name": "Notification App", + "description": "An application with email notifications", + "entitiesDir": "./entities", + "functionsDir": "./functions" +} diff --git a/evals/fixtures/existing-app-add-feature/project/base44/entities/order.jsonc b/evals/fixtures/existing-app-add-feature/project/base44/entities/order.jsonc new file mode 100644 index 0000000..48a6194 --- /dev/null +++ b/evals/fixtures/existing-app-add-feature/project/base44/entities/order.jsonc @@ -0,0 +1,29 @@ +{ + "name": "Order", + "type": "object", + "properties": { + "user_id": { + "type": "string" + }, + "items": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { "type": "string" }, + "quantity": { "type": "number" }, + "price": { "type": "number" } + } + } + }, + "status": { + "type": "string", + "enum": ["pending", "processing", "shipped", "delivered"], + "default": "pending" + }, + "total": { + "type": "number" + } + }, + "required": ["user_id", "items", "total"] +} diff --git a/evals/fixtures/existing-app-add-feature/project/base44/entities/user.jsonc b/evals/fixtures/existing-app-add-feature/project/base44/entities/user.jsonc new file mode 100644 index 0000000..eb7cbe7 --- /dev/null +++ b/evals/fixtures/existing-app-add-feature/project/base44/entities/user.jsonc @@ -0,0 +1,23 @@ +{ + "name": "User", + "type": "object", + "properties": { + "email": { + "type": "string", + "format": "email" + }, + "name": { + "type": "string" + }, + "notification_preferences": { + "type": "object", + "properties": { + "email_enabled": { + "type": "boolean", + "default": true + } + } + } + }, + "required": ["email", "name"] +} diff --git a/evals/fixtures/existing-app-add-feature/project/package.json b/evals/fixtures/existing-app-add-feature/project/package.json new file mode 100644 index 0000000..49e5863 --- /dev/null +++ b/evals/fixtures/existing-app-add-feature/project/package.json @@ -0,0 +1,24 @@ +{ + "name": "notification-app", + "version": "0.1.0", + "private": true, + "type": "module", + "scripts": { + "dev": "vite", + "build": "tsc && vite build", + "lint": "eslint . --ext ts,tsx" + }, + "dependencies": { + "@base44/sdk": "^1.0.0", + "react": "^18.2.0", + "react-dom": "^18.2.0" + }, + "devDependencies": { + "@types/react": "^18.2.66", + "@types/react-dom": "^18.2.22", + "base44": "^1.0.0", + "eslint": "^8.57.0", + "typescript": "^5.2.2", + "vite": "^5.2.0" + } +} diff --git a/evals/fixtures/existing-app-add-feature/project/src/lib/base44.ts b/evals/fixtures/existing-app-add-feature/project/src/lib/base44.ts new file mode 100644 index 0000000..9606ca2 --- /dev/null +++ b/evals/fixtures/existing-app-add-feature/project/src/lib/base44.ts @@ -0,0 +1,5 @@ +import { createClient } from '@base44/sdk'; + +export const base44 = createClient({ + appId: import.meta.env.VITE_BASE44_APP_ID, +}); diff --git a/evals/fixtures/existing-app-add-feature/skills/base44-cli/SKILL.md b/evals/fixtures/existing-app-add-feature/skills/base44-cli/SKILL.md new file mode 100644 index 0000000..3c33f05 --- /dev/null +++ b/evals/fixtures/existing-app-add-feature/skills/base44-cli/SKILL.md @@ -0,0 +1,384 @@ +--- +name: base44-cli +description: "The base44 CLI is used for EVERYTHING related to base44 projects: resource configuration (entities, backend functions, ai agents), initialization and actions (resource creation, deployment). This skill is the place for learning about how to configure resources. When you plan or implement a feature, you must learn this skill" +--- + +# Base44 CLI + +Create and manage Base44 apps (projects) using the Base44 CLI tool. + +## ⚡ IMMEDIATE ACTION REQUIRED - Read This First + +This skill activates on ANY mention of "base44" or when a `base44/` folder exists. **DO NOT read documentation files or search the web before acting.** + +**Your first action MUST be:** +1. Check if `base44/config.jsonc` exists in the current directory +2. If **NO** (new project scenario): + - This skill (base44-cli) handles the request + - Guide user through project initialization + - Do NOT activate base44-sdk yet +3. If **YES** (existing project scenario): + - Transfer to base44-sdk skill for implementation + - This skill only handles CLI commands (login, deploy, entities push) + +## Critical: Local Installation Only + +NEVER call `base44` directly. The CLI is installed locally as a dev dependency and must be accessed via a package manager: + +- `npx base44 ` (npm - recommended) +- `yarn base44 ` (yarn) +- `pnpm base44 ` (pnpm) + +WRONG: `base44 login` +RIGHT: `npx base44 login` + +## MANDATORY: Authentication Check at Session Start + +**CRITICAL**: At the very start of every AI session when this skill is activated, you MUST: + +1. **Check authentication status** by running: + ```bash + npx base44 whoami + ``` + +2. **If the user is logged in** (command succeeds and shows an email): + - Continue with the requested task + +3. **If the user is NOT logged in** (command fails or shows an error): + - **STOP immediately** + - **DO NOT proceed** with any CLI operations + - **Ask the user to login manually** by running: + ```bash + npx base44 login + ``` + - Wait for the user to confirm they have logged in before continuing + +**This check is mandatory and must happen before executing any other Base44 CLI commands.** + +## Overview + +The Base44 CLI provides command-line tools for authentication, creating projects, managing entities, and deploying Base44 applications. It is framework-agnostic and works with popular frontend frameworks like Vite, Next.js, and Create React App, Svelte, Vue, and more. + +## When to Use This Skill vs base44-sdk + +**Use base44-cli when:** +- Creating a **NEW** Base44 project from scratch +- Initializing a project in an empty directory +- Directory is missing `base44/config.jsonc` +- User mentions: "create a new project", "initialize project", "setup a project", "start a new Base44 app" +- Deploying, pushing entities, or authenticating via CLI +- Working with CLI commands (`npx base44 ...`) + +**Use base44-sdk when:** +- Building features in an **EXISTING** Base44 project +- `base44/config.jsonc` already exists +- Writing JavaScript/TypeScript code using Base44 SDK +- Implementing functionality, components, or features +- User mentions: "implement", "build a feature", "add functionality", "write code" + +**Skill Dependencies:** +- `base44-cli` is a **prerequisite** for `base44-sdk` in new projects +- If user wants to "create an app" and no Base44 project exists, use `base44-cli` first +- `base44-sdk` assumes a Base44 project is already initialized + +**State Check Logic:** +Before selecting a skill, check: +- IF (user mentions "create/build app" OR "make a project"): + - IF (directory is empty OR no `base44/config.jsonc` exists): + → Use **base44-cli** (project initialization needed) + - ELSE: + → Use **base44-sdk** (project exists, build features) + +## Project Structure + +A Base44 project combines a standard frontend project with a `base44/` configuration folder: + +``` +my-app/ +├── base44/ # Base44 configuration (created by CLI) +│ ├── config.jsonc # Project settings, site config +│ ├── entities/ # Entity schema definitions +│ │ ├── task.jsonc +│ │ └── board.jsonc +│ ├── functions/ # Backend functions (optional) +│ │ └── my-function/ +│ │ ├── function.jsonc +│ │ └── index.ts +│ └── agents/ # Agent configurations (optional) +│ └── support_agent.jsonc +├── src/ # Frontend source code +│ ├── api/ +│ │ └── base44Client.js # Base44 SDK client +│ ├── pages/ +│ ├── components/ +│ └── main.jsx +├── index.html # SPA entry point +├── package.json +└── vite.config.js # Or your framework's config +``` + +**Key files:** +- `base44/config.jsonc` - Project name, description, site build settings +- `base44/entities/*.jsonc` - Data model schemas (see Entity Schema section) +- `base44/agents/*.jsonc` - Agent configurations (optional) +- `src/api/base44Client.js` - Pre-configured SDK client for frontend use + +**config.jsonc example:** +```jsonc +{ + "name": "My App", // Required: project name + "description": "App description", // Optional: project description + "entitiesDir": "./entities", // Optional: default "entities" + "functionsDir": "./functions", // Optional: default "functions" + "agentsDir": "./agents", // Optional: default "agents" + "site": { // Optional: site deployment config + "installCommand": "npm install", // Optional: install dependencies + "buildCommand": "npm run build", // Optional: build command + "serveCommand": "npm run dev", // Optional: local dev server + "outputDirectory": "./dist" // Optional: build output directory + } +} +``` + +**Config properties:** + +| Property | Description | Default | +|----------|-------------|---------| +| `name` | Project name (required) | - | +| `description` | Project description | - | +| `entitiesDir` | Directory for entity schemas | `"entities"` | +| `functionsDir` | Directory for backend functions | `"functions"` | +| `agentsDir` | Directory for agent configs | `"agents"` | +| `site.installCommand` | Command to install dependencies | - | +| `site.buildCommand` | Command to build the project | - | +| `site.serveCommand` | Command to run dev server | - | +| `site.outputDirectory` | Build output directory for deployment | - | + +## Installation + +Install the Base44 CLI as a dev dependency in your project: + +```bash +npm install --save-dev base44 +``` + +**Important:** Never assume or hardcode the `base44` package version. Always install without a version specifier to get the latest version. + +Then run commands using `npx`: + +```bash +npx base44 +``` + +**Note:** All commands in this documentation use `npx base44`. You can also use `yarn base44`, or `pnpm base44` if preferred. + +## Available Commands + +### Authentication + +| Command | Description | Reference | +| --------------- | ----------------------------------------------- | ------------------------------------------- | +| `base44 login` | Authenticate with Base44 using device code flow | [auth-login.md](references/auth-login.md) | +| `base44 logout` | Logout from current device | [auth-logout.md](references/auth-logout.md) | +| `base44 whoami` | Display current authenticated user | [auth-whoami.md](references/auth-whoami.md) | + +### Project Management + +| Command | Description | Reference | +|---------|-------------|-----------| +| `base44 create` | Create a new Base44 project from a template | [create.md](references/create.md) ⚠️ **MUST READ** | +| `base44 link` | Link an existing local project to Base44 | [link.md](references/link.md) | +| `base44 dashboard` | Open the app dashboard in your browser | [dashboard.md](references/dashboard.md) | + +### Deployment + +| Command | Description | Reference | +|---------|-------------|-----------| +| `base44 deploy` | Deploy all resources (entities, functions, site) | [deploy.md](references/deploy.md) | + +### Entity Management + +| Action / Command | Description | Reference | +| ---------------------- | ------------------------------------------- | --------------------------------------------------- | +| Create Entities | Define entities in `base44/entities` folder | [entities-create.md](references/entities-create.md) | +| `base44 entities push` | Push local entities to Base44 | [entities-push.md](references/entities-push.md) | + +#### Entity Schema (Quick Reference) + +ALWAYS follow this exact structure when creating entity files: + +**File naming:** `base44/entities/{kebab-case-name}.jsonc` (e.g., `team-member.jsonc` for `TeamMember`) + +**Schema template:** +```jsonc +{ + "name": "EntityName", + "type": "object", + "properties": { + "field_name": { + "type": "string", + "description": "Field description" + } + }, + "required": ["field_name"] +} +``` + +**Field types:** `string`, `number`, `integer`, `boolean`, `array`, `object`, `binary` +**String formats:** `date`, `date-time`, `time`, `email`, `uri`, `hostname`, `ipv4`, `ipv6`, `uuid`, `file`, `regex`, `richtext` +**For enums:** Add `"enum": ["value1", "value2"]` and optionally `"default": "value1"` +**Entity names:** Must be alphanumeric only (pattern: `/^[a-zA-Z0-9]+$/`) + +For complete documentation, see [entities-create.md](references/entities-create.md). + +### Function Management + +| Action / Command | Description | Reference | +| ------------------------- | --------------------------------------------- | ------------------------------------------------------- | +| Create Functions | Define functions in `base44/functions` folder | [functions-create.md](references/functions-create.md) | +| `base44 functions deploy` | Deploy local functions to Base44 | [functions-deploy.md](references/functions-deploy.md) | + +### Agent Management + +Agents are conversational AI assistants that can interact with users, access your app's entities, and call backend functions. Use these commands to manage agent configurations. + +| Action / Command | Description | Reference | +| ----------------------- | --------------------------------------- | ----------------------------------------------- | +| Create Agents | Define agents in `base44/agents` folder | See Agent Schema below | +| `base44 agents pull` | Pull remote agents to local files | [agents-pull.md](references/agents-pull.md) | +| `base44 agents push` | Push local agents to Base44 | [agents-push.md](references/agents-push.md) | + +**Note:** Agent commands perform full synchronization - pushing replaces all remote agents with local ones, and pulling replaces all local agents with remote ones. + +#### Agent Schema (Quick Reference) + +**File naming:** `base44/agents/{agent_name}.jsonc` (e.g., `support_agent.jsonc`) + +**Schema template:** +```jsonc +{ + "name": "agent_name", + "description": "Brief description of what this agent does", + "instructions": "Detailed instructions for the agent's behavior", + "tool_configs": [ + // Entity tool - gives agent access to entity operations + { "entity_name": "tasks", "allowed_operations": ["read", "create", "update", "delete"] }, + // Backend function tool - gives agent access to a function + { "function_name": "send_email", "description": "Send an email notification" } + ], + "whatsapp_greeting": "Hello! How can I help you today?" +} +``` + +**Naming rules:** +- Agent names must match pattern: `/^[a-z0-9_]+$/` (lowercase alphanumeric with underscores, 1-100 chars) +- Valid: `support_agent`, `order_bot` +- Invalid: `Support-Agent`, `OrderBot` + +**Required fields:** `name`, `description`, `instructions` +**Optional fields:** `tool_configs` (defaults to `[]`), `whatsapp_greeting` + +**Tool config types:** +- **Entity tools**: `entity_name` + `allowed_operations` (array of: `read`, `create`, `update`, `delete`) +- **Backend function tools**: `function_name` + `description` + +### Site Deployment + +| Command | Description | Reference | +| -------------------- | ----------------------------------------- | ------------------------------------------- | +| `base44 site deploy` | Deploy built site files to Base44 hosting | [site-deploy.md](references/site-deploy.md) | + +**SPA only**: Base44 hosting supports Single Page Applications with a single `index.html` entry point. All routes are served from `index.html` (client-side routing). + +## Quick Start + +1. Install the CLI in your project: + ```bash + npm install --save-dev base44 + ``` + +2. Authenticate with Base44: + ```bash + npx base44 login + ``` + +3. Create a new project (ALWAYS provide name and `--path` flag): + ```bash + npx base44 create my-app -p . + ``` + +4. Build and deploy everything: + ```bash + npm run build + npx base44 deploy -y + ``` + +Or deploy individual resources: +- `npx base44 entities push` - Push entities only +- `npx base44 functions deploy` - Deploy functions only +- `npx base44 agents push` - Push agents only +- `npx base44 site deploy -y` - Deploy site only + +## Common Workflows + +### Creating a New Project + +**⚠️ MANDATORY: Before running `base44 create`, you MUST read [create.md](references/create.md) for:** +- **Template selection** - Choose the correct template (`backend-and-client` vs `backend-only`) +- **Correct workflow** - Different templates require different setup steps +- **Common pitfalls** - Avoid folder creation errors that cause failures + +Failure to follow the create.md instructions will result in broken project scaffolding. + +### Linking an Existing Project +```bash +# If you have base44/config.jsonc but no .app.jsonc +npx base44 link --create --name my-app +``` + +### Deploying All Changes +```bash +# Build your project first +npm run build + +# Deploy everything (entities, functions, and site) +npx base44 deploy -y +``` + +### Deploying Individual Resources +```bash +# Push only entities +npx base44 entities push + +# Deploy only functions +npx base44 functions deploy + +# Push only agents +npx base44 agents push + +# Deploy only site +npx base44 site deploy -y +``` + +### Opening the Dashboard +```bash +# Open app dashboard in browser +npx base44 dashboard +``` + +## Authentication + +Most commands require authentication. If you're not logged in, the CLI will automatically prompt you to login. Your session is stored locally and persists across CLI sessions. + +## Troubleshooting + +| Error | Solution | +| --------------------------- | ----------------------------------------------------------------------------------- | +| Not authenticated | Run `npx base44 login` first | +| No entities found | Ensure entities exist in `base44/entities/` directory | +| Entity not recognized | Ensure file uses kebab-case naming (e.g., `team-member.jsonc` not `TeamMember.jsonc`) | +| No functions found | Ensure functions exist in `base44/functions/` with valid `function.jsonc` configs | +| No agents found | Ensure agents exist in `base44/agents/` directory with valid `.jsonc` configs | +| Invalid agent name | Agent names must be lowercase alphanumeric with underscores only | +| No site configuration found | Check that `site.outputDirectory` is configured in project config | +| Site deployment fails | Ensure you ran `npm run build` first and the build succeeded | diff --git a/evals/fixtures/existing-app-add-feature/skills/base44-cli/references/agents-pull.md b/evals/fixtures/existing-app-add-feature/skills/base44-cli/references/agents-pull.md new file mode 100644 index 0000000..6a700f0 --- /dev/null +++ b/evals/fixtures/existing-app-add-feature/skills/base44-cli/references/agents-pull.md @@ -0,0 +1,73 @@ +# base44 agents pull + +Pull AI agent configurations from Base44 to local files. Agents are conversational AI assistants that can interact with users, access your app's entities, and call backend functions. + +## Syntax + +```bash +npx base44 agents pull +``` + +## Authentication + +**Required**: Yes. If not authenticated, you'll be prompted to login first. + +## What It Does + +1. Fetches all agents from Base44 +2. Writes agent files to the `base44/agents/` directory +3. Deletes local agent files that don't exist remotely +4. Reports written and deleted agents + +## Prerequisites + +- Must be run from a Base44 project directory +- Project must be linked to a Base44 app + +## Output + +```bash +$ npx base44 agents pull + +Fetching agents from Base44... +✓ Agents fetched successfully + +Writing agent files... +✓ Agent files written successfully + +Written: support_agent, order_bot +Deleted: old_agent + +Pulled 2 agents to base44/agents +``` + +## Agent Synchronization + +The pull operation synchronizes remote agents to your local files: + +- **Written**: Agent files created or updated from remote +- **Deleted**: Local agent files removed (didn't exist remotely) + +**Warning**: This operation replaces all local agent configurations with remote versions. Any local changes not pushed to Base44 will be overwritten. + +## Error Handling + +If no agents exist on Base44: +```bash +$ npx base44 agents pull +No agents found on Base44 +``` + +## Use Cases + +- Sync agent configurations to a new development machine +- Get the latest agent configurations from your team +- Restore local agent files after accidental deletion +- Start working on an existing project with agents + +## Notes + +- This command syncs agent configurations, not conversation data +- Agent files are stored as `.jsonc` in the `base44/agents/` directory +- The directory location is configurable via `agentsDir` in `config.jsonc` +- Use `base44 agents push` to upload local changes to Base44 diff --git a/evals/fixtures/existing-app-add-feature/skills/base44-cli/references/agents-push.md b/evals/fixtures/existing-app-add-feature/skills/base44-cli/references/agents-push.md new file mode 100644 index 0000000..fa2795b --- /dev/null +++ b/evals/fixtures/existing-app-add-feature/skills/base44-cli/references/agents-push.md @@ -0,0 +1,156 @@ +# base44 agents push + +Push local AI agent configurations to Base44. Agents are conversational AI assistants that can interact with users, access your app's entities, and call backend functions. + +## Syntax + +```bash +npx base44 agents push +``` + +## Authentication + +**Required**: Yes. If not authenticated, you'll be prompted to login first. + +## What It Does + +1. Reads all agent files from the `base44/agents/` directory +2. Validates agent configurations +3. Displays the count of agents to be pushed +4. Uploads agents to the Base44 backend +5. Reports the results: created, updated, and deleted agents + +## Prerequisites + +- Must be run from a Base44 project directory +- Project must have agent definitions in the `base44/agents/` folder + +## Output + +```bash +$ npx base44 agents push + +Found 2 agents to push +Pushing agents to Base44... + +Created: support_agent +Updated: order_bot +Deleted: old_agent + +✓ Agents pushed to Base44 +``` + +## Agent Synchronization + +The push operation synchronizes your local agents with Base44: + +- **Created**: New agents that didn't exist in Base44 +- **Updated**: Existing agents with modified configuration +- **Deleted**: Agents that were removed from your local configuration + +**Warning**: This is a full sync operation. Agents removed locally will be deleted from Base44. + +## Error Handling + +If no agents are found in your project: +```bash +$ npx base44 agents push +No local agents found - this will delete all remote agents +``` + +If an agent has an invalid name: +```bash +$ npx base44 agents push +Error: Agent name must be lowercase alphanumeric with underscores +``` + +## Agent Configuration Schema + +Each agent file should be a `.jsonc` file in `base44/agents/` with this structure: + +```jsonc +{ + "name": "agent_name", // Required: lowercase alphanumeric with underscores, 1-100 chars + "description": "Brief description of what this agent does", // Required: min 1 char + "instructions": "Detailed instructions for the agent's behavior", // Required: min 1 char + "tool_configs": [ // Optional: defaults to [] + // Entity tool - gives agent access to entity operations + { "entity_name": "Task", "allowed_operations": ["read", "create", "update", "delete"] }, + // Backend function tool - gives agent access to a function + { "function_name": "send_email", "description": "Send an email notification" } + ], + "whatsapp_greeting": "Hello! How can I help you today?" // Optional +} +``` + +**Naming rules:** +- **Agent names** must match pattern: `/^[a-z0-9_]+$/` (lowercase alphanumeric with underscores only, 1-100 characters) + - Valid: `support_agent`, `order_bot`, `task_helper` + - Invalid: `Support-Agent`, `OrderBot`, `task helper` +- **Agent file names** must use underscores (matching the agent name) + - Valid: `support_agent.jsonc`, `order_bot.jsonc` + - Invalid: `support-agent.jsonc` (hyphens not allowed) +- **Entity names in `tool_configs`** must use PascalCase (matching the entity's `name` field) + - Valid: `"entity_name": "Task"`, `"entity_name": "TeamMember"` + - Invalid: `"entity_name": "task"`, `"entity_name": "team_member"` + +**Required fields:** +- `name`: Required, must follow naming rules above +- `description`: Required, minimum 1 character +- `instructions`: Required, minimum 1 character +- `tool_configs`: Optional, defaults to empty array +- `whatsapp_greeting`: Optional + +### Common Mistake: Wrong tool_configs Format + +**WRONG** - Do NOT use `tools` with `type` and `entity`: +```jsonc +{ + "name": "my_agent", + "tools": [ // ❌ WRONG + { "type": "entity_query", "entity": "Task" } + ] +} +``` + +**CORRECT** - Use `tool_configs` with `entity_name` and `allowed_operations`: +```jsonc +{ + "name": "my_agent", + "tool_configs": [ // ✅ CORRECT + { "entity_name": "Task", "allowed_operations": ["read"] } + ] +} +``` + +### Best Practices for Agent Instructions + +When giving agents access to entities, be explicit in the instructions about using the tools: + +```jsonc +{ + "name": "support_agent", + "instructions": "You are a helpful support agent.\n\nIMPORTANT: You have access to customer data through entity tools. When users ask about their orders or account:\n1. ALWAYS use the Order entity tool to query their order history\n2. Use the Customer entity tool to look up account details\n3. Analyze the data and provide personalized responses\n\nAlways query the relevant entities first before answering questions about user data.", + "tool_configs": [ + { "entity_name": "Order", "allowed_operations": ["read"] }, + { "entity_name": "Customer", "allowed_operations": ["read"] } + ] +} +``` + +Without explicit instructions to use the entity tools, the agent may not proactively query user data when asked. + +## Use Cases + +- After defining new agents in your project +- When modifying existing agent configurations +- To sync agent changes before testing +- As part of your development workflow when agent behavior changes + +## Notes + +- This command syncs the agent configuration, not conversation data +- Changes are applied to your Base44 project immediately +- Make sure to test agent changes in a development environment first +- Agent definitions are located in the `base44/agents/` directory +- Use `base44 agents pull` to download agents from Base44 diff --git a/evals/fixtures/existing-app-add-feature/skills/base44-cli/references/auth-login.md b/evals/fixtures/existing-app-add-feature/skills/base44-cli/references/auth-login.md new file mode 100644 index 0000000..adebd27 --- /dev/null +++ b/evals/fixtures/existing-app-add-feature/skills/base44-cli/references/auth-login.md @@ -0,0 +1,54 @@ +# base44 login + +Authenticate with Base44 using device code flow. + +## Syntax + +```bash +npx base44 login +``` + +## Authentication + +**Required**: No (this is the login command itself) + +## How It Works + +The login command uses OAuth 2.0 device code flow for authentication: + +1. Generates a device code for authentication +2. Displays a verification code and verification URI +3. Directs you to visit the URI and enter the code +4. Polls for authentication completion (up to device code expiration) +5. Retrieves access and refresh tokens upon successful authentication +6. Fetches and displays your user information +7. Saves authentication data locally with expiration timestamp + +## Interactive Flow + +```bash +$ npx base44 login + +Please visit: https://auth.base44.com/device +Enter code: ABCD-EFGH + +Waiting for authentication... +✓ Successfully authenticated! + +Logged in as: user@example.com +``` + +## Session Management + +- Authentication tokens are stored locally on your device +- Tokens include expiration timestamps +- The session persists across CLI sessions +- Other commands will automatically use your stored credentials +- Use `npx base44 logout` to clear your session +- Use `npx base44 whoami` to check your current authentication status + +## Notes + +- You only need to login once per device +- If your session expires, you'll be prompted to login again when running authenticated commands +- The CLI automatically prompts for login when you run commands that require authentication diff --git a/evals/fixtures/existing-app-add-feature/skills/base44-cli/references/auth-logout.md b/evals/fixtures/existing-app-add-feature/skills/base44-cli/references/auth-logout.md new file mode 100644 index 0000000..33c2ddf --- /dev/null +++ b/evals/fixtures/existing-app-add-feature/skills/base44-cli/references/auth-logout.md @@ -0,0 +1,32 @@ +# base44 logout + +Logout from current device and clear stored authentication data. + +## Syntax + +```bash +npx base44 logout +``` + +## Authentication + +**Required**: No + +## What It Does + +- Deletes stored authentication data from your device +- Clears your local session +- Removes access and refresh tokens + +## Output + +```bash +$ npx base44 logout +Logged out successfully +``` + +## Notes + +- You can logout even if you're not currently logged in (no error) +- After logout, you'll need to run `npx base44 login` again to use authenticated commands +- This only affects the current device; your Base44 account remains active diff --git a/evals/fixtures/existing-app-add-feature/skills/base44-cli/references/auth-whoami.md b/evals/fixtures/existing-app-add-feature/skills/base44-cli/references/auth-whoami.md new file mode 100644 index 0000000..abe450c --- /dev/null +++ b/evals/fixtures/existing-app-add-feature/skills/base44-cli/references/auth-whoami.md @@ -0,0 +1,37 @@ +# base44 whoami + +Display the currently authenticated user. + +## Syntax + +```bash +npx base44 whoami +``` + +## Authentication + +**Required**: Yes. If not authenticated, you'll be prompted to login first. + +## What It Does + +- Reads stored authentication data +- Displays the email of the currently logged-in user + +## Output + +```bash +$ npx base44 whoami +Logged in as: user@example.com +``` + +## Use Cases + +- Verify you're logged in before running other commands +- Check which account you're currently using +- Confirm authentication is working properly +- Useful in scripts or automation to verify credentials + +## Notes + +- If you're not logged in, the command will prompt you to authenticate first +- The email displayed matches your Base44 account email diff --git a/evals/fixtures/existing-app-add-feature/skills/base44-cli/references/create.md b/evals/fixtures/existing-app-add-feature/skills/base44-cli/references/create.md new file mode 100644 index 0000000..7580645 --- /dev/null +++ b/evals/fixtures/existing-app-add-feature/skills/base44-cli/references/create.md @@ -0,0 +1,111 @@ +# base44 create + +Creates a new Base44 project from a template. This command is framework-agnostic and can either scaffold a complete project or add Base44 configuration to an existing project. + +## Critical: Non-Interactive Mode Required + +ALWAYS provide both the project name AND `--path` flag. Without both, the command opens an interactive TUI which agents cannot use properly. + +WRONG: `npx base44 create` +WRONG: `npx base44 create my-app` +RIGHT: `npx base44 create my-app -p ./my-app` + +## Syntax + +```bash +npx base44 create [name] --path [options] +``` + +## Arguments & Options + +| Argument/Option | Description | Required | +|--------|-------------|----------| +| `name` | Project name (positional argument) | Yes* | +| `-p, --path ` | Path where to create the project | Yes* | +| `-t, --template ` | Template ID (see templates below) | No | +| `--deploy` | Build and deploy the site (includes pushing entities) | No | +| `--no-skills` | Skip AI agent skills installation (skills are added by default) | No | + +*Required for non-interactive mode. Both `name` and `--path` must be provided together. + +## Template Selection (CRITICAL - Choose Appropriately) + +**You MUST select the most appropriate template based on user requirements:** + +| Template ID | When to Use | Example Scenarios | +|-------------|-------------|-------------------| +| `backend-and-client` | Creating a NEW full-stack web app from scratch | "Create a task app", "Build me a dashboard", "Make a SaaS app" | +| `backend-only` | Adding Base44 to an EXISTING project OR using a different framework (Next.js, Vue, Svelte, etc.) | "Add Base44 to my project", "I want to use Next.js", "I already have a frontend" | + +**Default Choice:** When the user asks to "create an app" or "build a project" without specifying a particular framework, use `backend-and-client` to provide a complete, production-ready application with Vite + React + Tailwind. + +## The `--path` Flag + +- **For `backend-and-client` template (new projects):** Use a new subfolder path + ```bash + npx base44 create my-app -p ./my-app -t backend-and-client + ``` +- **For `backend-only` template (existing projects):** Use `-p .` in the current directory + ```bash + npx base44 create my-app -p . + ``` + +## Workflow: Using `backend-only` with External Frameworks + +**CRITICAL: The project folder MUST exist BEFORE running `base44 create` with `backend-only`** + +The `backend-only` template only adds Base44 configuration files - it does NOT create a frontend. If you need a frontend with a specific framework: + +```bash +# Step 1: Initialize the frontend project FIRST +npm create vite@latest my-app -- --template react # or vue, svelte, etc. +# OR: npx create-next-app@latest my-app +# OR: any other framework's init command + +# Step 2: Navigate into the created folder +cd my-app + +# Step 3: Install Base44 CLI +npm install --save-dev base44 + +# Step 4: Add Base44 configuration +npx base44 create my-app -p . +``` + +**WARNING:** Do NOT: +- Create an empty folder manually, then try to run `npx create vite` inside it (will fail - folder exists) +- Run `base44 create` with `backend-only` expecting it to create a frontend (it won't) + +**DO:** +- Run the external framework's init command FIRST (it creates its own folder) +- Then run `base44 create` inside that folder with `-p .` + +## Examples + +```bash +# RECOMMENDED: Create full-stack project (for new apps) +npx base44 create my-app -p ./my-app -t backend-and-client + +# Create full-stack and deploy in one step +npx base44 create my-app -p ./my-app -t backend-and-client --deploy + +# Add Base44 to EXISTING project (must be inside the project folder) +npx base44 create my-app -p . + +# Add Base44 to existing project and deploy +npx base44 create my-app -p . --deploy + +# Create without adding AI agent skills +npx base44 create my-app -p . --no-skills +``` + +## What It Does + +1. Applies the selected template to the target path +2. Creates a `base44/` folder with configuration files +3. Registers the project with Base44 backend +4. Creates `base44/.app.jsonc` with the app ID +5. If `--deploy` is used: + - Pushes any entities defined in `base44/entities/` + - Runs install and build commands (for templates with frontend) + - Deploys the site to Base44 hosting diff --git a/evals/fixtures/existing-app-add-feature/skills/base44-cli/references/dashboard.md b/evals/fixtures/existing-app-add-feature/skills/base44-cli/references/dashboard.md new file mode 100644 index 0000000..95a534a --- /dev/null +++ b/evals/fixtures/existing-app-add-feature/skills/base44-cli/references/dashboard.md @@ -0,0 +1,35 @@ +# base44 dashboard + +Opens the Base44 app dashboard in your default web browser. + +## Syntax + +```bash +npx base44 dashboard +``` + +## Options + +This command has no options. + +## What It Does + +1. Reads the project's app ID from `base44/.app.jsonc` +2. Opens the dashboard URL in your default browser + +## Example + +```bash +# Open dashboard for current project +npx base44 dashboard +``` + +## Requirements + +- Must be run from a linked Base44 project directory (contains `base44/.app.jsonc`) +- Must be authenticated (run `npx base44 login` first) + +## Notes + +- The dashboard provides a web interface to manage your app's entities, functions, users, and settings +- If you're not in a project directory, the command will fail with an error diff --git a/evals/fixtures/existing-app-add-feature/skills/base44-cli/references/deploy.md b/evals/fixtures/existing-app-add-feature/skills/base44-cli/references/deploy.md new file mode 100644 index 0000000..500526f --- /dev/null +++ b/evals/fixtures/existing-app-add-feature/skills/base44-cli/references/deploy.md @@ -0,0 +1,86 @@ +# base44 deploy + +Deploys all project resources (entities, functions, and site) to Base44 in a single command. + +## Syntax + +```bash +npx base44 deploy [options] +``` + +## Options + +| Option | Description | +|--------|-------------| +| `-y, --yes` | Skip confirmation prompt | + +## What It Deploys + +The command automatically detects and deploys: + +1. **Entities** - All `.jsonc` files in `base44/entities/` +2. **Functions** - All functions in `base44/functions/` +3. **Agents** - All agent configurations in `base44/agents/` +4. **Site** - Built files from `site.outputDirectory` (if configured) + +## Examples + +```bash +# Interactive mode - shows what will be deployed and asks for confirmation +npx base44 deploy + +# Non-interactive - skip confirmation (for CI/CD or agent use) +npx base44 deploy -y +``` + +## Typical Workflow + +```bash +# 1. Make your changes (entities, functions, frontend code) + +# 2. Build the frontend (if you have one) +npm run build + +# 3. Deploy everything +npx base44 deploy -y +``` + +## What It Does + +1. Reads project configuration from `base44/config.jsonc` +2. Detects available resources (entities, functions, site) +3. Shows a summary of what will be deployed +4. Asks for confirmation (unless `-y` flag is used) +5. Deploys all resources in sequence: + - Pushes entity schemas + - Deploys functions + - Pushes agent configurations + - Uploads site files +6. Displays the dashboard URL and app URL (if site was deployed) + +## Requirements + +- Must be run from a linked Base44 project directory +- Must be authenticated (run `npx base44 login` first) +- For site deployment, must run `npm run build` first + +## Output + +After successful deployment: +- **Dashboard**: Link to your app's management dashboard +- **App URL**: Your deployed site's public URL (if site was included) + +## Notes + +- If no resources are found, the command exits with a message +- Use individual commands (`entities push`, `functions deploy`, `site deploy`) if you only want to deploy specific resources +- The site must be built before deployment - this command does not run `npm run build` for you + +## Related Commands + +| Command | Description | +|---------|-------------| +| `base44 entities push` | Push only entities | +| `base44 functions deploy` | Deploy only functions | +| `base44 agents push` | Push only agents | +| `base44 site deploy` | Deploy only the site | diff --git a/evals/fixtures/existing-app-add-feature/skills/base44-cli/references/entities-create.md b/evals/fixtures/existing-app-add-feature/skills/base44-cli/references/entities-create.md new file mode 100644 index 0000000..29b40aa --- /dev/null +++ b/evals/fixtures/existing-app-add-feature/skills/base44-cli/references/entities-create.md @@ -0,0 +1,552 @@ +# Creating Entities + +Base44 entities are defined locally in your project and then pushed to the Base44 backend. + +## Critical: File Naming + +Entity files MUST use kebab-case naming: `{kebab-case-name}.jsonc` + +| Entity Name | File Name | +|-------------|-----------| +| `Task` | `task.jsonc` | +| `TeamMember` | `team-member.jsonc` | +| `ActivityLog` | `activity-log.jsonc` | + +WRONG: `TeamMember.jsonc`, `teamMember.jsonc` +RIGHT: `team-member.jsonc` + +## Table of Contents + +- [Creating Entities](#creating-entities) + - [Entity Directory](#entity-directory) + - [How to Create an Entity](#how-to-create-an-entity) + - [Entity Schema Structure](#entity-schema-structure) + - [Supported Field Types](#supported-field-types) + - [Field Properties](#field-properties) + - [Complete Example](#complete-example) + - [Naming Conventions](#naming-conventions) + - [Relationships Between Entities](#relationships-between-entities) + - [Row Level Security (RLS)](#row-level-security-rls) + - [Field Level Security (FLS)](#field-level-security-fls) + - [Pushing Entities](#pushing-entities) + +## Entity Directory + +All entity definitions must be placed in the `base44/entities/` folder in your project root. Each entity is defined in its own `.jsonc` file. + +Example structure: +``` +my-app/ + base44/ + entities/ + user.jsonc + product.jsonc + order.jsonc +``` + +## How to Create an Entity + +1. Create a new `.jsonc` file in the `base44/entities/` directory +2. Define your entity schema following the structure below +3. Push the changes to Base44 using the CLI + +## Entity Schema Structure + +Each entity file follows a JSON Schema-like structure: + +```jsonc +{ + "name": "EntityName", // PascalCase entity name + "type": "object", // Always "object" + "properties": { + // Define your fields here + }, + "required": ["field1"] // Array of required field names +} +``` + +### Common Mistake: Nested Schema Property + +**WRONG** - Do NOT wrap properties in a `schema` object: +```jsonc +{ + "name": "Task", + "description": "A task entity", + "schema": { // ❌ WRONG - don't use nested "schema" + "type": "object", + "properties": { ... } + } +} +``` + +**CORRECT** - Put `type` and `properties` at the top level: +```jsonc +{ + "name": "Task", + "description": "A task entity", + "type": "object", // ✅ CORRECT - top level + "properties": { ... } // ✅ CORRECT - top level +} +``` + +This is a common mistake that will cause "Invalid schema: Schema must have a 'type' field" errors when pushing entities. + +## Supported Field Types + +### String + +Basic text field: +```jsonc +{ + "title": { + "type": "string", + "description": "Task title" + } +} +``` + +With format: +```jsonc +{ + "due_date": { + "type": "string", + "format": "date", + "description": "Due date" + } +} +``` + +Available formats: `date`, `date-time`, `time`, `email`, `uri`, `hostname`, `ipv4`, `ipv6`, `uuid`, `file`, `regex`, `richtext` + +### String with Enum + +Constrained to specific values: +```jsonc +{ + "status": { + "type": "string", + "enum": ["todo", "in_progress", "done"], + "default": "todo", + "description": "Current status" + } +} +``` + +### Number + +```jsonc +{ + "position": { + "type": "number", + "description": "Position for ordering" + } +} +``` + +### Integer + +For whole numbers only: +```jsonc +{ + "quantity": { + "type": "integer", + "description": "Item quantity", + "minimum": 0, + "maximum": 1000 + } +} +``` + +### Binary + +For file/blob data: +```jsonc +{ + "attachment": { + "type": "binary", + "description": "File attachment" + } +} +``` + +### Boolean + +```jsonc +{ + "notify_on_change": { + "type": "boolean", + "default": true, + "description": "Enable notifications" + } +} +``` + +### Array of Strings + +```jsonc +{ + "labels": { + "type": "array", + "items": { "type": "string" }, + "description": "Task labels/tags" + } +} +``` + +### Array of Objects + +```jsonc +{ + "attachments": { + "type": "array", + "description": "File attachments", + "items": { + "type": "object", + "properties": { + "name": { "type": "string" }, + "url": { "type": "string" }, + "type": { "type": "string" } + } + } + } +} +``` + +## Field Properties + +| Property | Description | +| ------------- | ---------------------------------------------------------------------------------------- | +| `type` | Data type: `string`, `number`, `integer`, `boolean`, `array`, `object`, `binary` | +| `description` | Human-readable description of the field | +| `enum` | Array of allowed values (for strings) | +| `enumNames` | Human-readable labels for enum values (same order as `enum`) | +| `default` | Default value when not provided | +| `format` | Format hint: `date`, `date-time`, `time`, `email`, `uri`, `hostname`, `ipv4`, `ipv6`, `uuid`, `file`, `regex`, `richtext` | +| `items` | Schema for array items | +| `properties` | Nested properties for object types | +| `$ref` | Reference to another schema definition | +| `minLength` | Minimum string length | +| `maxLength` | Maximum string length | +| `pattern` | Regex pattern for string validation | +| `minimum` | Minimum value for numbers | +| `maximum` | Maximum value for numbers | +| `rls` | Field-level security rules (see Field Level Security section) | + +## Complete Example + +Here's a complete entity definition for a Task: + +```jsonc +{ + "name": "Task", + "type": "object", + "properties": { + "title": { + "type": "string", + "description": "Task title" + }, + "description": { + "type": "string", + "description": "Task description" + }, + "status": { + "type": "string", + "enum": ["todo", "in_progress", "done"], + "default": "todo", + "description": "Current status of the task" + }, + "board_id": { + "type": "string", + "description": "Board this task belongs to" + }, + "assignee_email": { + "type": "string", + "description": "Email of assigned user" + }, + "priority": { + "type": "string", + "enum": ["low", "medium", "high"], + "default": "medium", + "description": "Task priority" + }, + "due_date": { + "type": "string", + "format": "date", + "description": "Due date" + }, + "labels": { + "type": "array", + "items": { "type": "string" }, + "description": "Task labels/tags" + } + }, + "required": ["title"] +} +``` + +## Naming Conventions + +- **Entity name**: Use PascalCase with alphanumeric characters only (e.g., `Task`, `TeamMember`, `ActivityLog`) + - Must match pattern: `/^[a-zA-Z0-9]+$/` + - Valid: `Task`, `TeamMember`, `Order123` + - Invalid: `Team_Member`, `Team-Member`, `Team Member` +- **File name**: Use kebab-case matching the entity (e.g., `task.jsonc`, `team-member.jsonc`, `activity-log.jsonc`) +- **Field names**: Use snake_case (e.g., `board_id`, `user_email`, `due_date`) + +## Relationships Between Entities + +To create relationships between entities, use ID reference fields: + +```jsonc +{ + "board_id": { + "type": "string", + "description": "Board this task belongs to" + }, + "team_id": { + "type": "string", + "description": "Associated team ID" + } +} +``` + +## Row Level Security (RLS) + +Row Level Security (RLS) controls which records users can access based on their identity and attributes. RLS rules are defined per entity inside the `rls` field of the schema. + +**Important:** If no RLS is defined, all records are accessible to all users. + +### RLS Operations + +RLS supports five operations: + +| Operation | Description | +|-----------|-------------| +| `create` | Control who can add new records | +| `read` | Control who can view records | +| `update` | Control who can modify records | +| `delete` | Control who can remove records | +| `write` | Shorthand for `create`, `update`, and `delete` combined | + +### Permission Values + +Each operation accepts one of the following values: + +1. **`true`** - Allow all users (including anonymous/unauthenticated) +2. **`false`** - Block all users +3. **Condition object** - Allow users matching the condition + +### Template Variables + +Use template variables to reference the current user's attributes: + +| Template | Description | +|----------|-------------| +| `{{user.id}}` | The user's ID | +| `{{user.email}}` | The user's email | +| `{{user.role}}` | The user's role | +| `{{user.data.field_name}}` | Custom field from the user's `data` object | + +### Built-in Entity Attributes + +Every entity record has these built-in attributes available for RLS rules: + +| Attribute | Description | +|-----------|-------------| +| `id` | Unique record identifier | +| `created_date` | Timestamp when record was created | +| `updated_date` | Timestamp when record was last updated | +| `created_by` | Email of the user who created the record | + +### Rule Types + +There are two condition types you can use: + +**1. Entity-to-user comparison** - Compare record fields to the current user's values: +```jsonc +{ + "created_by": "{{user.email}}" +} +``` + +**2. User condition check** - Check user properties directly using `user_condition`: +```jsonc +{ + "user_condition": { "role": "admin" } +} +``` + +**Important limitations:** +- `user_condition` only supports **simple equality** (e.g., `{ "role": "admin" }`) +- For `data.*` field comparisons, you can use operators: `$in`, `$nin`, `$ne`, `$all` +- Logical operators `$or`, `$and`, `$nor` are available for combining conditions +- You cannot filter by entity field values directly (e.g., `{"status": "published"}`) +- Only user-related conditions are allowed + +### RLS Examples + +**Owner-only access:** +```jsonc +{ + "created_by": "{{user.email}}" +} +``` + +**Department-based access:** +```jsonc +{ + "data.department": "{{user.data.department}}" +} +``` + +**Admin-only access:** +```jsonc +{ + "user_condition": { "role": "admin" } +} +``` + +**Complete RLS configuration:** +```jsonc +{ + "name": "Task", + "type": "object", + "properties": { + "title": { + "type": "string", + "description": "Task title" + }, + "status": { + "type": "string", + "enum": ["todo", "in_progress", "done"], + "default": "todo" + } + }, + "required": ["title"], + "rls": { + "create": true, + "read": { "created_by": "{{user.email}}" }, + "update": { "created_by": "{{user.email}}" }, + "delete": { "created_by": "{{user.email}}" } + } +} +``` + +### Common RLS Patterns + +**Public create, admin-only management (e.g., contact forms, waitlists):** +```jsonc +{ + "rls": { + "create": true, + "read": { "user_condition": { "role": "admin" } }, + "update": { "user_condition": { "role": "admin" } }, + "delete": { "user_condition": { "role": "admin" } } + } +} +``` + +**Owner-only access:** +```jsonc +{ + "rls": { + "create": true, + "read": { "created_by": "{{user.email}}" }, + "update": { "created_by": "{{user.email}}" }, + "delete": { "created_by": "{{user.email}}" } + } +} +``` + +**Logged-in users only:** +```jsonc +{ + "rls": { + "create": { "user_condition": { "id": "{{user.id}}" } }, + "read": true, + "update": { "created_by": "{{user.email}}" }, + "delete": { "created_by": "{{user.email}}" } + } +} +``` + +### Limitations + +- **user_condition is equality only:** `user_condition` only supports exact match (e.g., `{ "role": "admin" }`) - no operators +- **No comparison operators on user_condition:** `$gt`, `$lt`, `$regex`, `$expr`, `$where` are NOT supported for user conditions +- **No entity field filtering:** Cannot filter by entity field values (e.g., `{"status": "published"}`) +- **No deeply nested templates:** Templates like `{{user.data.profile.department}}` may not work + +**Supported operators:** +- **Logical operators:** `$or`, `$and`, `$nor` for combining multiple conditions +- **Field operators (for `data.*` fields only):** `$in`, `$nin`, `$ne`, `$all` + +### Complex Access Patterns + +For complex access patterns that require multiple conditions (e.g., "owner OR admin"), you have two options: + +1. **Use the Base44 Dashboard UI** - The dashboard allows adding multiple rules per operation with OR logic +2. **Use separate entities** - Split data into multiple entities with different access rules +3. **Use backend functions** - Implement custom access logic in backend functions + +## Field Level Security (FLS) + +Field Level Security allows you to control access to individual fields within an entity. FLS rules are defined within each field's schema using the `rls` property. + +### FLS Operations + +FLS supports the same operations as entity-level RLS: + +| Operation | Description | +|-----------|-------------| +| `create` | Control who can set this field when creating records | +| `read` | Control who can view this field | +| `update` | Control who can modify this field | +| `delete` | Control who can clear this field | +| `write` | Shorthand for `create`, `update`, and `delete` combined | + +### FLS Example + +```jsonc +{ + "name": "Employee", + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "Employee name" + }, + "salary": { + "type": "number", + "description": "Employee salary", + "rls": { + "read": { "user_condition": { "role": "hr" } }, + "update": { "user_condition": { "role": "hr" } } + } + }, + "department": { + "type": "string", + "description": "Department name" + } + }, + "required": ["name"] +} +``` + +In this example, only users with the `hr` role can read or update the `salary` field. All users with access to the entity can read/update other fields. + +### FLS Notes + +- If no field-level RLS is defined, the field inherits the entity-level RLS rules +- FLS rules follow the same condition format as entity-level RLS +- Use FLS for sensitive fields like salary, SSN, or internal notes + +## Pushing Entities + +The `entities push` command will push all entities that exist in the `base44/entities` folder. + +```bash +npx base44 entities push +``` + +For more details on the push command, see [entities-push.md](entities-push.md). diff --git a/evals/fixtures/existing-app-add-feature/skills/base44-cli/references/entities-push.md b/evals/fixtures/existing-app-add-feature/skills/base44-cli/references/entities-push.md new file mode 100644 index 0000000..63b92d7 --- /dev/null +++ b/evals/fixtures/existing-app-add-feature/skills/base44-cli/references/entities-push.md @@ -0,0 +1,71 @@ +# base44 entities push + +Push local entity definitions to Base44. + +## Syntax + +```bash +npx base44 entities push +``` + +## Authentication + +**Required**: Yes. If not authenticated, you'll be prompted to login first. + +## What It Does + +1. Pushes all entities that exist in the `base44/entities` folder +2. Validates that entities exist in the folder +3. Displays the count of entities to be pushed +4. Uploads entities to the Base44 backend +5. Reports the results: created, updated, and deleted entities + +## Prerequisites + +- Must be run from a Base44 project directory +- Project must have entity definitions in the `base44/entities` folder + +## Output + +```bash +$ npx base44 entities push + +Found 3 entities to push +Pushing entities to Base44... + +Created: User, Post +Updated: Comment +Deleted: OldEntity + +✓ Entities pushed successfully +``` + +## Entity Synchronization + +The push operation synchronizes your local entity schema with Base44: + +- **Created**: New entities that didn't exist in Base44 +- **Updated**: Existing entities with modified schema or configuration +- **Deleted**: Entities that were removed from your local configuration + +## Error Handling + +If no entities are found in your project: +```bash +$ npx base44 entities push +No entities found in project +``` + +## Use Cases + +- After defining new entities in your project +- When modifying existing entity schemas +- To sync entity changes before deploying +- As part of your development workflow when data models change + +## Notes + +- This command syncs the entity schema/structure, not the actual data +- Changes are applied to your Base44 project immediately +- Make sure to test entity changes in a development environment first +- Entity definitions are located in the `base44/entities/` directory diff --git a/evals/fixtures/existing-app-add-feature/skills/base44-cli/references/functions-create.md b/evals/fixtures/existing-app-add-feature/skills/base44-cli/references/functions-create.md new file mode 100644 index 0000000..c523cdc --- /dev/null +++ b/evals/fixtures/existing-app-add-feature/skills/base44-cli/references/functions-create.md @@ -0,0 +1,229 @@ +# Creating Functions + +Base44 functions are serverless backend functions that run on Deno. They are defined locally in your project and deployed to the Base44 backend. + +## Function Directory + +All function definitions must be placed in the `base44/functions/` folder in your project. Each function lives in its own subdirectory with a configuration file and entry point. + +Example structure: +``` +my-app/ + base44/ + functions/ + process-order/ + function.jsonc + index.ts + send-notification/ + function.jsonc + index.ts +``` + +## How to Create a Function + +1. Create a new directory in `base44/functions/` with your function name (use kebab-case) +2. Create a `function.jsonc` configuration file in the directory +3. Create the entry point file (e.g., `index.ts`) +4. Deploy the function using the CLI + +## Function Configuration + +Each function requires a `function.jsonc` configuration file: + +```jsonc +{ + "name": "my-function", + "entry": "index.ts" +} +``` + +### Configuration Properties + +| Property | Description | Required | +|----------|-------------|----------| +| `name` | Function name (must match `/^[^.]+$/` - no dots allowed) | Yes | +| `entry` | Entry point file path relative to the function directory (min 1 char) | Yes | + +## Entry Point File + +Functions run on Deno and must export using `Deno.serve()`. Use `npm:` prefix for npm packages. + +```typescript +import { createClientFromRequest } from "npm:@base44/sdk"; + +Deno.serve(async (req) => { + // Get authenticated client from request + const base44 = createClientFromRequest(req); + + // Parse input + const { orderId, action } = await req.json(); + + // Your logic here + const order = await base44.entities.Orders.get(orderId); + + // Return response + return Response.json({ + success: true, + order: order + }); +}); +``` + +### Request Object + +The function receives a standard Deno `Request` object: +- `req.json()` - Parse JSON body +- `req.text()` - Get raw text body +- `req.headers` - Access request headers +- `req.method` - HTTP method + +### Response Object + +Return using `Response.json()` for JSON responses: + +```typescript +// Success response +return Response.json({ data: result }); + +// Error response with status code +return Response.json({ error: "Something went wrong" }, { status: 400 }); + +// Not found +return Response.json({ error: "Order not found" }, { status: 404 }); +``` + +## Complete Example + +### Directory Structure +``` +base44/ + functions/ + process-order/ + function.jsonc + index.ts +``` + +### function.jsonc +```jsonc +{ + "name": "process-order", + "entry": "index.ts" +} +``` + +### index.ts +```typescript +import { createClientFromRequest } from "npm:@base44/sdk"; + +Deno.serve(async (req) => { + try { + const base44 = createClientFromRequest(req); + const { orderId } = await req.json(); + + // Validate input + if (!orderId) { + return Response.json( + { error: "Order ID is required" }, + { status: 400 } + ); + } + + // Fetch and process the order + const order = await base44.entities.Orders.get(orderId); + if (!order) { + return Response.json( + { error: "Order not found" }, + { status: 404 } + ); + } + + return Response.json({ + success: true, + orderId: order.id, + processedAt: new Date().toISOString() + }); + + } catch (error) { + return Response.json( + { error: error.message }, + { status: 500 } + ); + } +}); +``` + +## Using Service Role Access + +For admin-level operations, use `asServiceRole`: + +```typescript +import { createClientFromRequest } from "npm:@base44/sdk"; + +Deno.serve(async (req) => { + const base44 = createClientFromRequest(req); + + // Check user is authenticated + const user = await base44.auth.me(); + if (!user) { + return Response.json({ error: "Unauthorized" }, { status: 401 }); + } + + // Use service role for admin operations + const allOrders = await base44.asServiceRole.entities.Orders.list(); + + return Response.json({ orders: allOrders }); +}); +``` + +## Using Secrets + +Access environment variables configured in the app dashboard: + +```typescript +Deno.serve(async (req) => { + // Access environment variables (configured in app settings) + const apiKey = Deno.env.get("STRIPE_API_KEY"); + + const response = await fetch("https://api.stripe.com/v1/charges", { + headers: { + "Authorization": `Bearer ${apiKey}` + } + }); + + return Response.json(await response.json()); +}); +``` + +## Naming Conventions + +- **Directory name**: Use kebab-case (e.g., `process-order`, `send-notification`) +- **Function name**: Match the directory name, must match pattern `/^[^.]+$/` (no dots allowed) + - Valid: `process-order`, `send_notification`, `myFunction` + - Invalid: `process.order`, `send.notification.v2` +- **Entry file**: Typically `index.ts` or `index.js` + +## Deploying Functions + +After creating your function, deploy it to Base44: + +```bash +npx base44 functions deploy +``` + +For more details on deploying, see [functions-deploy.md](functions-deploy.md). + +## Notes + +- Functions run on Deno runtime, not Node.js +- Use `npm:` prefix for npm packages (e.g., `npm:@base44/sdk`) +- Use `createClientFromRequest(req)` to get a client that inherits the caller's auth context +- Configure secrets via app dashboard for API keys +- Make sure to handle errors gracefully and return appropriate HTTP status codes + +## Common Mistakes + +| Wrong | Correct | Why | +|-------|---------|-----| +| `functions/myFunction.js` (single file) | `functions/my-function/index.ts` + `function.jsonc` | Functions require subdirectory with config | +| `import { ... } from "@base44/sdk"` | `import { ... } from "npm:@base44/sdk"` | Deno requires `npm:` prefix for npm packages | +| `MyFunction` or `myFunction` directory | `my-function` directory | Use kebab-case for directory names | diff --git a/evals/fixtures/existing-app-add-feature/skills/base44-cli/references/functions-deploy.md b/evals/fixtures/existing-app-add-feature/skills/base44-cli/references/functions-deploy.md new file mode 100644 index 0000000..f89d796 --- /dev/null +++ b/evals/fixtures/existing-app-add-feature/skills/base44-cli/references/functions-deploy.md @@ -0,0 +1,78 @@ +# base44 functions deploy + +Deploy local function definitions to Base44. + +## Syntax + +```bash +npx base44 functions deploy +``` + +## Authentication + +**Required**: Yes. If not authenticated, you'll be prompted to login first. + +## What It Does + +1. Scans the `base44/functions/` directory for function definitions +2. Validates that functions exist and have valid configurations +3. Displays the count of functions to be deployed +4. Uploads function code and configuration to Base44 +5. Reports the results: deployed and deleted functions + +## Prerequisites + +- Must be run from a Base44 project directory +- Project must have function definitions in the `base44/functions/` folder +- Each function must have a valid `function.jsonc` config file + +## Output + +```bash +$ npx base44 functions deploy + +Found 2 functions to deploy +Deploying functions to Base44... + +Deployed: process-order, send-notification +Deleted: old-function + +✓ Functions deployed successfully +``` + +## Function Synchronization + +The deploy operation synchronizes your local functions with Base44: + +- **Deployed**: Functions that were created or updated +- **Deleted**: Functions that were removed from your local configuration + +## Error Handling + +If no functions are found in your project: +```bash +$ npx base44 functions deploy +No functions found. Create functions in the 'functions' directory. +``` + +If a function has configuration errors: +```bash +$ npx base44 functions deploy +Function deployment errors: +'my-function' function: Entry point cannot be empty +``` + +## Use Cases + +- After creating new functions in your project +- When modifying existing function code or configuration +- To sync function changes before testing +- As part of your development workflow when backend logic changes + +## Notes + +- This command deploys the function code and configuration +- Changes are applied to your Base44 project immediately +- Make sure to test functions in a development environment first +- Function definitions are located in the `base44/functions/` directory +- For how to create functions, see [functions-create.md](functions-create.md) diff --git a/evals/fixtures/existing-app-add-feature/skills/base44-cli/references/link.md b/evals/fixtures/existing-app-add-feature/skills/base44-cli/references/link.md new file mode 100644 index 0000000..0b009ce --- /dev/null +++ b/evals/fixtures/existing-app-add-feature/skills/base44-cli/references/link.md @@ -0,0 +1,81 @@ +# base44 link + +Links an existing local Base44 project to a Base44 app in the cloud. Use this when you have a `base44/config.jsonc` but haven't connected it to a Base44 app yet. + +## Critical: When to Use Link vs Create + +| Scenario | Command | +|----------|---------| +| Starting fresh, no `base44/` folder | `npx base44 create` | +| Have `base44/config.jsonc` but no `.app.jsonc` | `npx base44 link` | +| Project already linked (has `.app.jsonc`) | Already done, use `deploy` | + +## Syntax + +```bash +npx base44 link [options] +``` + +## Options + +| Option | Description | Required | +|--------|-------------|----------| +| `-c, --create` | Create a new project (skip selection prompt) | No | +| `-n, --name ` | Project name (required when `--create` is used) | With `--create` | +| `-d, --description ` | Project description | No | +| `-p, --projectId ` | Project ID to link to an existing project (skip selection prompt) | No | + +## Non-Interactive Mode + +For CI/CD or agent use: + +**Create a new project:** +```bash +npx base44 link --create --name my-app +``` + +**Link to an existing project:** +```bash +npx base44 link --projectId +``` + +WRONG: `npx base44 link --create` (missing --name) +WRONG: `npx base44 link --create --projectId ` (cannot use both) +RIGHT: `npx base44 link --create --name my-app` +RIGHT: `npx base44 link --projectId ` + +## Examples + +```bash +# Interactive mode - prompts for project details +npx base44 link + +# Non-interactive - create and link in one step +npx base44 link --create --name my-app + +# With description +npx base44 link --create --name my-app --description "My awesome app" + +# Link to a specific existing project by ID +npx base44 link --projectId abc123 +``` + +## What It Does + +1. Finds the `base44/config.jsonc` in the current directory (or parent directories) +2. Verifies no `.app.jsonc` exists (project not already linked) +3. Either: + - Creates a new Base44 app in the cloud (with `--create`), OR + - Links to an existing project (with `--projectId` or interactive selection) +4. Writes the app ID to `base44/.app.jsonc` + +## Requirements + +- Must have `base44/config.jsonc` in the project +- Must NOT have `base44/.app.jsonc` (use `deploy` if already linked) +- Must be authenticated (run `npx base44 login` first) + +## Notes + +- After linking, you can deploy resources with `npx base44 deploy` +- The `.app.jsonc` file should be git-ignored (contains your app ID) diff --git a/evals/fixtures/existing-app-add-feature/skills/base44-cli/references/rls-examples.md b/evals/fixtures/existing-app-add-feature/skills/base44-cli/references/rls-examples.md new file mode 100644 index 0000000..7d3ef42 --- /dev/null +++ b/evals/fixtures/existing-app-add-feature/skills/base44-cli/references/rls-examples.md @@ -0,0 +1,463 @@ +# RLS Examples + +Practical Row-Level Security patterns for common application types. + +**Important:** Base44 RLS supports: +- **Logical operators:** `$or`, `$and`, `$nor` for combining conditions +- **Field operators (for `data.*` fields):** `$in`, `$nin`, `$ne`, `$all` +- **user_condition:** Equality only (no operators) + +## Contents +- [Simple Patterns (JSON Schema)](#simple-patterns-json-schema) +- [Using Operators](#using-operators) +- [Field-Level Security Examples](#field-level-security-examples) +- [Complex Patterns (Dashboard UI or Backend)](#complex-patterns-dashboard-ui-or-backend) +- [Best Practices](#best-practices) + +--- + +## Simple Patterns (JSON Schema) + +These patterns work with the JSON schema RLS format. + +### Todo App - Owner-only access + +Users see and manage only their own tasks. + +```jsonc +{ + "name": "Task", + "type": "object", + "properties": { + "title": { "type": "string" }, + "description": { "type": "string" }, + "completed": { "type": "boolean" }, + "priority": { "type": "string", "enum": ["low", "medium", "high"] }, + "due_date": { "type": "string", "format": "date" } + }, + "rls": { + "create": true, + "read": { "created_by": "{{user.email}}" }, + "update": { "created_by": "{{user.email}}" }, + "delete": { "created_by": "{{user.email}}" } + } +} +``` + +### Contact Form - Public create, admin-only read + +Anyone can submit, only admins can view submissions. + +```jsonc +{ + "name": "ContactSubmission", + "type": "object", + "properties": { + "name": { "type": "string" }, + "email": { "type": "string", "format": "email" }, + "message": { "type": "string" } + }, + "rls": { + "create": true, + "read": { "user_condition": { "role": "admin" } }, + "update": { "user_condition": { "role": "admin" } }, + "delete": { "user_condition": { "role": "admin" } } + } +} +``` + +### User Profile - Self-management + +Users can only access their own profile. + +```jsonc +{ + "name": "UserProfile", + "type": "object", + "properties": { + "name": { "type": "string" }, + "avatar_url": { "type": "string" }, + "bio": { "type": "string" }, + "preferences": { "type": "object" } + }, + "rls": { + "create": true, + "read": { "created_by": "{{user.email}}" }, + "update": { "created_by": "{{user.email}}" }, + "delete": { "created_by": "{{user.email}}" } + } +} +``` + +### Department Data - Same department access + +Users can only see records from their department. + +```jsonc +{ + "name": "DepartmentAnnouncement", + "type": "object", + "properties": { + "title": { "type": "string" }, + "content": { "type": "string" }, + "department": { "type": "string" } + }, + "rls": { + "create": { "user_condition": { "role": "manager" } }, + "read": { "data.department": "{{user.data.department}}" }, + "update": { "user_condition": { "role": "manager" } }, + "delete": { "user_condition": { "role": "admin" } } + } +} +``` + +### Subscription - Admin-managed, user-readable via email field + +```jsonc +{ + "name": "Subscription", + "type": "object", + "properties": { + "user_email": { "type": "string" }, + "tier": { "type": "string", "enum": ["free", "basic", "pro", "enterprise"] }, + "credits": { "type": "number" }, + "renewal_date": { "type": "string", "format": "date" } + }, + "rls": { + "create": { "user_condition": { "role": "admin" } }, + "read": { "data.user_email": "{{user.email}}" }, + "update": { "user_condition": { "role": "admin" } }, + "delete": { "user_condition": { "role": "admin" } } + } +} +``` + +**Note:** This pattern only allows users to read their own subscription. Admins need to use the Dashboard UI to configure additional read access for themselves. + +### Private Data - Owner-only + +```jsonc +{ + "name": "PrivateNotes", + "type": "object", + "properties": { + "title": { "type": "string" }, + "content": { "type": "string" }, + "tags": { "type": "array", "items": { "type": "string" } } + }, + "rls": { + "create": true, + "read": { "created_by": "{{user.email}}" }, + "update": { "created_by": "{{user.email}}" }, + "delete": { "created_by": "{{user.email}}" } + } +} +``` + +### Public Read, Authenticated Write + +Anyone can read, only logged-in users can create/edit their own records. + +```jsonc +{ + "name": "BlogPost", + "type": "object", + "properties": { + "title": { "type": "string" }, + "content": { "type": "string" }, + "author_email": { "type": "string" } + }, + "rls": { + "create": true, + "read": true, + "update": { "created_by": "{{user.email}}" }, + "delete": { "created_by": "{{user.email}}" } + } +} +``` + +--- + +## Using Operators + +### Logical Operators + +Combine multiple conditions using `$or`, `$and`, or `$nor`: + +**Owner OR Admin access:** +```jsonc +{ + "name": "Document", + "type": "object", + "properties": { + "title": { "type": "string" }, + "content": { "type": "string" } + }, + "rls": { + "create": true, + "read": { + "$or": [ + { "created_by": "{{user.email}}" }, + { "user_condition": { "role": "admin" } } + ] + }, + "update": { + "$or": [ + { "created_by": "{{user.email}}" }, + { "user_condition": { "role": "admin" } } + ] + }, + "delete": { "user_condition": { "role": "admin" } } + } +} +``` + +**Multiple roles with $or:** +```jsonc +{ + "rls": { + "read": { + "$or": [ + { "user_condition": { "role": "admin" } }, + { "user_condition": { "role": "manager" } }, + { "user_condition": { "role": "hr" } } + ] + } + } +} +``` + +### Field Operators for data.* Fields + +Use `$in`, `$nin`, `$ne`, `$all` for comparing entity data fields: + +**Access based on tags ($in):** +```jsonc +{ + "rls": { + "read": { + "data.category": { "$in": ["public", "shared"] } + } + } +} +``` + +**Exclude specific statuses ($nin):** +```jsonc +{ + "rls": { + "read": { + "data.status": { "$nin": ["archived", "deleted"] } + } + } +} +``` + +**Not equal ($ne):** +```jsonc +{ + "rls": { + "read": { + "data.visibility": { "$ne": "private" } + } + } +} +``` + +**All tags must match ($all):** +```jsonc +{ + "rls": { + "read": { + "data.required_tags": { "$all": ["approved", "reviewed"] } + } + } +} +``` + +### Combining Logical and Field Operators + +```jsonc +{ + "rls": { + "read": { + "$and": [ + { "data.status": { "$ne": "draft" } }, + { + "$or": [ + { "created_by": "{{user.email}}" }, + { "data.visibility": "public" } + ] + } + ] + } + } +} +``` + +--- + +## Field-Level Security Examples + +Control access to specific fields within an entity. + +### Sensitive Salary Field + +```jsonc +{ + "name": "Employee", + "type": "object", + "properties": { + "name": { "type": "string" }, + "email": { "type": "string", "format": "email" }, + "salary": { + "type": "number", + "description": "Annual salary", + "rls": { + "read": { "user_condition": { "role": "hr" } }, + "write": { "user_condition": { "role": "hr" } } + } + }, + "performance_notes": { + "type": "string", + "description": "Manager notes", + "rls": { + "read": { + "$or": [ + { "user_condition": { "role": "manager" } }, + { "user_condition": { "role": "hr" } } + ] + }, + "write": { "user_condition": { "role": "manager" } } + } + } + } +} +``` + +### Admin-Only Internal Fields + +```jsonc +{ + "name": "Order", + "type": "object", + "properties": { + "order_number": { "type": "string" }, + "total": { "type": "number" }, + "internal_notes": { + "type": "string", + "description": "Internal processing notes", + "rls": { + "read": { "user_condition": { "role": "admin" } }, + "write": { "user_condition": { "role": "admin" } } + } + }, + "profit_margin": { + "type": "number", + "description": "Profit margin percentage", + "rls": { + "read": { "user_condition": { "role": "admin" } }, + "write": false + } + } + } +} +``` + +--- + +## Complex Patterns (Dashboard UI or Backend) + +Some patterns may still require the Dashboard UI or backend functions. + +### Bidirectional Relationships (e.g., Friendships, Matches) + +**Requirement:** Either party in a relationship should have access. + +**Now possible with $or:** +```jsonc +{ + "rls": { + "read": { + "$or": [ + { "data.user_a_email": "{{user.email}}" }, + { "data.user_b_email": "{{user.email}}" } + ] + } + } +} +``` + +**Alternative solutions:** +1. **Entity redesign:** Store two records per relationship (one for each party) +2. **Backend function:** Query with custom logic + +### Complex Business Logic + +**Requirement:** Access depends on multiple entity fields with complex conditions. + +**JSON Schema limitation:** While operators help, very complex business logic may still be hard to express. + +**Solution options:** +1. **Backend function:** Implement custom access logic +2. **Combine simpler rules:** Break complex rules into simpler entity-level and field-level rules + +--- + +## Best Practices + +### Security Strategy + +Use a combination of entity-level RLS and field-level security: + +| Data Type | Approach | Example | +|-----------|----------|---------| +| User-editable | Entity RLS: Owner-only | UserProfile with `created_by` check | +| Sensitive fields | Field-level RLS | Salary field with HR role check | +| Multi-role access | `$or` with user_condition | Admin OR Manager access | +| Conditional access | Field operators | `$in`, `$ne` on data fields | +| Public content | Entity RLS: `read: true` | PublicPost | +| Private content | Entity RLS: Owner-only | PrivateNote | + +### When to Use Each Approach + +| Requirement | Approach | +|-------------|----------| +| Single condition (owner, admin, department) | JSON Schema RLS | +| Multiple OR/AND conditions | JSON Schema RLS with `$or`/`$and` | +| Field value checks with `$in`/`$ne`/etc. | JSON Schema RLS for `data.*` fields | +| Field-level access control | JSON Schema FLS (field-level `rls`) | +| Complex comparison operators (`$gt`, `$lt`) | Backend functions | +| Very complex business logic | Backend functions | + +### Common Role Patterns + +| Role | Typical Access | +|------|----------------| +| `admin` | Full access to all records | +| `moderator` | Read/update access, limited delete | +| `manager` | Department-scoped access | +| `user` | Own records only | + +### Supported Operators Summary + +| Operator | Supported | Notes | +|----------|-----------|-------| +| `$or` | Yes | Combine multiple conditions | +| `$and` | Yes | All conditions must match | +| `$nor` | Yes | None of the conditions match | +| `$in` | Yes | For `data.*` fields only | +| `$nin` | Yes | For `data.*` fields only | +| `$ne` | Yes | For `data.*` fields only | +| `$all` | Yes | For `data.*` fields only | +| `$gt`, `$lt`, `$gte`, `$lte` | No | Use backend functions | +| `$regex` | No | Use backend functions | + +### Limitations Summary + +| Not Supported | Alternative | +|---------------|-------------| +| Operators on `user_condition` | Use equality only for user checks | +| Comparison operators (`$gt`, `$lt`) | Backend functions | +| Regex matching (`$regex`) | Backend functions | +| Cross-entity relationships | Backend functions | diff --git a/evals/fixtures/existing-app-add-feature/skills/base44-cli/references/site-deploy.md b/evals/fixtures/existing-app-add-feature/skills/base44-cli/references/site-deploy.md new file mode 100644 index 0000000..929d302 --- /dev/null +++ b/evals/fixtures/existing-app-add-feature/skills/base44-cli/references/site-deploy.md @@ -0,0 +1,118 @@ +# base44 site deploy + +Deploy built site files to Base44 hosting. + +## Table of Contents + +- [Syntax](#syntax) +- [Authentication](#authentication) +- [Prerequisites](#prerequisites) +- [How It Works](#how-it-works) +- [Interactive Flow](#interactive-flow) +- [Typical Workflow](#typical-workflow) +- [Configuration](#configuration) +- [Error Handling](#error-handling) +- [Use Cases](#use-cases) +- [Notes](#notes) + +## Syntax + +```bash +npx base44 site deploy [options] +``` + +## Options + +| Option | Description | +| ------------ | ------------------------- | +| `-y, --yes` | Skip confirmation prompt | + +Use `-y` flag for non-interactive/automated deployments: + +```bash +npx base44 site deploy -y +``` + +## Authentication + +**Required**: Yes. If not authenticated, you'll be prompted to login first. + +## Prerequisites + +- Must be run from a Base44 project directory +- Project must have `site.outputDirectory` configured in project config +- Site must be built before deploying (run your build command first) +- **SPA only**: Base44 hosting supports Single Page Applications with a single `index.html` entry point. All routes are served from `index.html` (client-side routing). + +## How It Works + +1. Reads project configuration +2. Validates that site configuration exists +3. Prompts for deployment confirmation showing the output directory +4. Creates an archive of site files from the output directory +5. Deploys to Base44 hosting +6. Returns the app URL + +## Interactive Flow + +```bash +$ npx base44 site deploy + +Deploy site from ./dist? (yes/no) yes + +Creating archive... +Uploading to Base44... +Deploying... + +✓ Deployment successful! + +Visit your site at: https://my-app.base44.app +``` + +## Typical Workflow + +```bash +# 1. Build your site using your framework's build command +npm run build + +# 2. Deploy to Base44 +npx base44 site deploy +``` + +## Configuration + +The `site.outputDirectory` in your project configuration should point to where your framework outputs built files: + +- Vite: typically `./dist` +- Next.js: typically `./.next` or `./out` +- Create React App: typically `./build` +- Custom: whatever your build tool outputs to + +## Error Handling + +If site configuration is missing: +```bash +$ npx base44 site deploy +Error: No site configuration found in project +``` + +If you cancel the deployment: +```bash +Deploy site from ./dist? (yes/no) no +Deployment cancelled +``` + +## Use Cases + +- Deploy your site after making changes +- Push new versions of your application +- Deploy after updating content or functionality +- Part of your CI/CD pipeline + +## Notes + +- Always build your site before deploying +- The command deploys whatever is in your output directory +- Make sure your build completed successfully before deploying +- Previous deployments are preserved (versioned) in Base44 +- Deployment is immediate and updates your live site diff --git a/evals/fixtures/existing-app-add-feature/skills/base44-sdk/SKILL.md b/evals/fixtures/existing-app-add-feature/skills/base44-sdk/SKILL.md new file mode 100644 index 0000000..01ddd7f --- /dev/null +++ b/evals/fixtures/existing-app-add-feature/skills/base44-sdk/SKILL.md @@ -0,0 +1,296 @@ +--- +name: base44-sdk +description: "The base44 SDK is the library to communicate with base44 services. In projects, you use it to communicate with remote resources (entities, backend functions, ai agents) and to write backend functions. This skill is the place for learning about available modules and types. When you plan or implement a feature, you must learn this skill" +--- + +# Base44 Coder + +Build apps on the Base44 platform using the Base44 JavaScript SDK. + +## ⚡ IMMEDIATE ACTION REQUIRED - Read This First + +This skill activates on ANY mention of "base44" or when a `base44/` folder exists. **DO NOT read documentation files or search the web before acting.** + +**Your first action MUST be:** +1. Check if `base44/config.jsonc` exists in the current directory +2. If **YES** (existing project scenario): + - This skill (base44-sdk) handles the request + - Implement features using Base44 SDK + - Do NOT use base44-cli unless user explicitly requests CLI commands +3. If **NO** (new project scenario): + - Transfer to base44-cli skill for project initialization + - This skill cannot help until project is initialized + +## When to Use This Skill vs base44-cli + +**Use base44-sdk when:** +- Building features in an **EXISTING** Base44 project +- `base44/config.jsonc` already exists in the project +- Base44 SDK imports are present (`@base44/sdk`) +- Writing JavaScript/TypeScript code using Base44 SDK modules +- Implementing functionality, components, or features +- User mentions: "implement", "build a feature", "add functionality", "write code for" +- User says "create a [type] app" **and** a Base44 project already exists + +**DO NOT USE base44-sdk for:** +- ❌ Initializing new Base44 projects (use `base44-cli` instead) +- ❌ Empty directories without Base44 configuration +- ❌ When user says "create a new Base44 project/app/site" and no project exists +- ❌ CLI commands like `npx base44 create`, `npx base44 deploy`, `npx base44 login` (use `base44-cli`) + +**Skill Dependencies:** +- `base44-sdk` assumes a Base44 project is **already initialized** +- `base44-cli` is a **prerequisite** for `base44-sdk` in new projects +- If user wants to "create an app" and no Base44 project exists, use `base44-cli` first + +**State Check Logic:** +Before selecting this skill, verify: +- IF (user mentions "create/build app" OR "make a project"): + - IF (directory is empty OR no `base44/config.jsonc` exists): + → Use **base44-cli** (project initialization needed) + - ELSE: + → Use **base44-sdk** (project exists, build features) + +## Quick Start + +```javascript +// In Base44-generated apps, base44 client is pre-configured and available + +// CRUD operations +const task = await base44.entities.Task.create({ title: "New task", status: "pending" }); +const tasks = await base44.entities.Task.list(); +await base44.entities.Task.update(task.id, { status: "done" }); + +// Get current user +const user = await base44.auth.me(); +``` + +```javascript +// External apps +import { createClient } from "@base44/sdk"; + +// IMPORTANT: Use 'appId' (NOT 'clientId' or 'id') +const base44 = createClient({ appId: "your-app-id" }); +await base44.auth.loginViaEmailPassword("user@example.com", "password"); +``` + +## ⚠️ CRITICAL: Do Not Hallucinate APIs + +**Before writing ANY Base44 code, verify method names against this table or [QUICK_REFERENCE.md](references/QUICK_REFERENCE.md).** + +Base44 SDK has unique method names. Do NOT assume patterns from Firebase, Supabase, or other SDKs. + +### Authentication - WRONG vs CORRECT + +| ❌ WRONG (hallucinated) | ✅ CORRECT | +|------------------------|-----------| +| `signInWithGoogle()` | `loginWithProvider('google')` | +| `signInWithProvider('google')` | `loginWithProvider('google')` | +| `auth.google()` | `loginWithProvider('google')` | +| `signInWithEmailAndPassword(email, pw)` | `loginViaEmailPassword(email, pw)` | +| `signIn(email, pw)` | `loginViaEmailPassword(email, pw)` | +| `createUser()` / `signUp()` | `register({email, password})` | +| `onAuthStateChanged()` | `me()` (no listener, call when needed) | +| `currentUser` | `await auth.me()` | + +### Functions - WRONG vs CORRECT + +| ❌ WRONG (hallucinated) | ✅ CORRECT | +|------------------------|-----------| +| `functions.call('name', data)` | `functions.invoke('name', data)` | +| `functions.run('name', data)` | `functions.invoke('name', data)` | +| `callFunction('name', data)` | `functions.invoke('name', data)` | +| `httpsCallable('name')(data)` | `functions.invoke('name', data)` | + +### Integrations - WRONG vs CORRECT + +| ❌ WRONG (hallucinated) | ✅ CORRECT | +|------------------------|-----------| +| `ai.generate(prompt)` | `integrations.Core.InvokeLLM({prompt})` | +| `openai.chat(prompt)` | `integrations.Core.InvokeLLM({prompt})` | +| `llm(prompt)` | `integrations.Core.InvokeLLM({prompt})` | +| `sendEmail(to, subject, body)` | `integrations.Core.SendEmail({to, subject, body})` | +| `email.send()` | `integrations.Core.SendEmail({to, subject, body})` | +| `uploadFile(file)` | `integrations.Core.UploadFile({file})` | +| `storage.upload(file)` | `integrations.Core.UploadFile({file})` | + +### Entities - WRONG vs CORRECT + +| ❌ WRONG (hallucinated) | ✅ CORRECT | +|------------------------|-----------| +| `entities.Task.find({...})` | `entities.Task.filter({...})` | +| `entities.Task.findOne(id)` | `entities.Task.get(id)` | +| `entities.Task.insert(data)` | `entities.Task.create(data)` | +| `entities.Task.remove(id)` | `entities.Task.delete(id)` | +| `entities.Task.onChange(cb)` | `entities.Task.subscribe(cb)` | + +## SDK Modules + +| Module | Purpose | Reference | +|--------|---------|-----------| +| `entities` | CRUD operations on data models | [entities.md](references/entities.md) | +| `auth` | Login, register, user management | [auth.md](references/auth.md) | +| `agents` | AI conversations and messages | [base44-agents.md](references/base44-agents.md) | +| `functions` | Backend function invocation | [functions.md](references/functions.md) | +| `integrations` | AI, email, file uploads, custom APIs | [integrations.md](references/integrations.md) | +| `connectors` | OAuth tokens (service role only) | [connectors.md](references/connectors.md) | +| `analytics` | Track custom events and user activity | [analytics.md](references/analytics.md) | +| `appLogs` | Log user activity in app | [app-logs.md](references/app-logs.md) | +| `users` | Invite users to the app | [users.md](references/users.md) | + +For client setup and authentication modes, see [client.md](references/client.md). + +**TypeScript Support:** Each reference file includes a "Type Definitions" section with TypeScript interfaces and types for the module's methods, parameters, and return values. + +## Installation + +Install the Base44 SDK: + +```bash +npm install @base44/sdk +``` + +**Important:** Never assume or hardcode the `@base44/sdk` package version. Always install without a version specifier to get the latest version. + +## Creating a Client (External Apps) + +When creating a client in external apps, **ALWAYS use `appId` as the parameter name**: + +```javascript +import { createClient } from "@base44/sdk"; + +// ✅ CORRECT +const base44 = createClient({ appId: "your-app-id" }); + +// ❌ WRONG - Do NOT use these: +// const base44 = createClient({ clientId: "your-app-id" }); // WRONG +// const base44 = createClient({ id: "your-app-id" }); // WRONG +``` + +**Required parameter:** `appId` (string) - Your Base44 application ID + +**Optional parameters:** +- `token` (string) - Pre-authenticated user token +- `options` (object) - Configuration options + - `options.onError` (function) - Global error handler + +**Example with error handler:** +```javascript +const base44 = createClient({ + appId: "your-app-id", + options: { + onError: (error) => { + console.error("Base44 error:", error); + } + } +}); +``` + +## Module Selection + +**Working with app data?** +- Create/read/update/delete records → `entities` +- Import data from file → `entities.importEntities()` +- Realtime updates → `entities.EntityName.subscribe()` + +**User management?** +- Login/register/logout → `auth` +- Get current user → `auth.me()` +- Update user profile → `auth.updateMe()` +- Invite users → `users.inviteUser()` + +**AI features?** +- Chat with AI agents → `agents` (requires logged-in user) +- Create new conversation → `agents.createConversation()` +- Manage conversations → `agents.getConversations()` +- Generate text/JSON with AI → `integrations.Core.InvokeLLM()` +- Generate images → `integrations.Core.GenerateImage()` + +**Custom backend logic?** +- Run server-side code → `functions.invoke()` +- Need admin access → `base44.asServiceRole.functions.invoke()` + +**External services?** +- Send emails → `integrations.Core.SendEmail()` +- Upload files → `integrations.Core.UploadFile()` +- Custom APIs → `integrations.custom.call()` +- OAuth tokens (Google, Slack) → `connectors` (backend only) + +**Tracking and analytics?** +- Track custom events → `analytics.track()` +- Log page views/activity → `appLogs.logUserInApp()` + +## Common Patterns + +### Filter and Sort Data + +```javascript +const pendingTasks = await base44.entities.Task.filter( + { status: "pending", assignedTo: userId }, // query + "-created_date", // sort (descending) + 10, // limit + 0 // skip +); +``` + +### Protected Routes (check auth) + +```javascript +const user = await base44.auth.me(); +if (!user) { + // Navigate to your custom login page + navigate('/login', { state: { returnTo: window.location.pathname } }); + return; +} +``` + +### Backend Function Call + +```javascript +// Frontend +const result = await base44.functions.invoke("processOrder", { + orderId: "123", + action: "ship" +}); + +// Backend function (Deno) +import { createClientFromRequest } from "npm:@base44/sdk"; + +Deno.serve(async (req) => { + const base44 = createClientFromRequest(req); + const { orderId, action } = await req.json(); + // Process with service role for admin access + const order = await base44.asServiceRole.entities.Orders.get(orderId); + return Response.json({ success: true }); +}); +``` + +### Service Role Access + +Use `asServiceRole` in backend functions for admin-level operations: + +```javascript +// User mode - respects permissions +const myTasks = await base44.entities.Task.list(); + +// Service role - full access (backend only) +const allTasks = await base44.asServiceRole.entities.Task.list(); +const token = await base44.asServiceRole.connectors.getAccessToken("slack"); +``` + +## Frontend vs Backend + +| Capability | Frontend | Backend | +|------------|----------|---------| +| `entities` (user's data) | Yes | Yes | +| `auth` | Yes | Yes | +| `agents` | Yes | Yes | +| `functions.invoke()` | Yes | Yes | +| `integrations` | Yes | Yes | +| `analytics` | Yes | Yes | +| `appLogs` | Yes | Yes | +| `users` | Yes | Yes | +| `asServiceRole.*` | No | Yes | +| `connectors` | No | Yes | + +Backend functions use `Deno.serve()` and `createClientFromRequest(req)` to get a properly authenticated client. diff --git a/evals/fixtures/existing-app-add-feature/skills/base44-sdk/references/QUICK_REFERENCE.md b/evals/fixtures/existing-app-add-feature/skills/base44-sdk/references/QUICK_REFERENCE.md new file mode 100644 index 0000000..d357384 --- /dev/null +++ b/evals/fixtures/existing-app-add-feature/skills/base44-sdk/references/QUICK_REFERENCE.md @@ -0,0 +1,155 @@ +# Base44 SDK Quick Reference + +Compact method signatures for all SDK modules. **Verify against this before writing code.** + +--- + +## Auth (`base44.auth.*`) + +``` +loginViaEmailPassword(email, password, turnstileToken?) → Promise<{access_token, user}> +loginWithProvider('google' | 'microsoft' | 'facebook', fromUrl?) → void +me() → Promise +updateMe(data) → Promise +isAuthenticated() → Promise +logout(redirectUrl?) → void +redirectToLogin(nextUrl) → void # ⚠️ Avoid - prefer custom login UI +register({email, password, turnstile_token?, referral_code?}) → Promise +verifyOtp({email, otpCode}) → Promise +resendOtp(email) → Promise +inviteUser(userEmail, role) → Promise +resetPasswordRequest(email) → Promise +resetPassword({resetToken, newPassword}) → Promise +changePassword({userId, currentPassword, newPassword}) → Promise +setToken(token, saveToStorage?) → void +``` + +--- + +## Entities (`base44.entities.EntityName.*`) + +``` +create(data) → Promise +bulkCreate(dataArray) → Promise +list(sort?, limit?, skip?, fields?) → Promise +filter(query, sort?, limit?, skip?, fields?) → Promise +get(id) → Promise +update(id, data) → Promise +delete(id) → Promise +deleteMany(query) → Promise +importEntities(file) → Promise // frontend only +subscribe(callback) → () => void // returns unsubscribe fn +``` + +**Sort:** Use `-fieldName` for descending (e.g., `-created_date`) + +--- + +## Functions (`base44.functions.*`) + +``` +invoke(functionName, data) → Promise +``` + +**Backend:** Use `base44.asServiceRole.functions.invoke()` for admin access. + +--- + +## Integrations (`base44.integrations.Core.*`) + +``` +InvokeLLM({prompt, add_context_from_internet?, response_json_schema?, file_urls?}) → Promise +GenerateImage({prompt}) → Promise<{url}> +SendEmail({to, subject, body, from_name?}) → Promise +UploadFile({file}) → Promise<{file_url}> +UploadPrivateFile({file}) → Promise<{file_uri}> +CreateFileSignedUrl({file_uri, expires_in?}) → Promise<{signed_url}> +ExtractDataFromUploadedFile({file_url, json_schema}) → Promise +``` + +### Custom Integrations (`base44.integrations.custom.*`) + +``` +call(slug, operationId, {payload?, pathParams?, queryParams?, headers?}?) → Promise<{success, status_code, data}> +``` + +**operationId format:** `"method:/path"` (e.g., `"get:/contacts"`, `"post:/users/{id}"`) + +--- + +## Analytics (`base44.analytics.*`) + +``` +track({eventName, properties?}) → void +``` + +--- + +## App Logs (`base44.appLogs.*`) + +``` +logUserInApp(pageName) → Promise +``` + +--- + +## Users (`base44.users.*`) + +``` +inviteUser(userEmail, role) → Promise // role: 'user' | 'admin' +``` + +--- + +## Connectors (`base44.asServiceRole.connectors.*`) + +**Backend only, service role required.** + +``` +getAccessToken(integrationType) → Promise +``` + +**Types:** `'googlecalendar'`, `'googledrive'`, `'slack'`, `'notion'`, `'salesforce'`, `'hubspot'`, `'linkedin'`, `'tiktok'`, `'github'` + +--- + +## Service Role Access + +**Backend functions only.** Prefix any module with `asServiceRole` for admin access: + +```javascript +base44.asServiceRole.entities.Task.list() +base44.asServiceRole.functions.invoke('name', data) +base44.asServiceRole.connectors.getAccessToken('slack') +``` + +--- + +## Backend Function Template + +```javascript +import { createClientFromRequest } from "@base44/sdk"; + +Deno.serve(async (req) => { + const base44 = createClientFromRequest(req); + const data = await req.json(); + + // User context + const user = await base44.auth.me(); + + // Service role for admin operations + const allRecords = await base44.asServiceRole.entities.Task.list(); + + return Response.json({ success: true }); +}); +``` + +--- + +## Client Initialization (External Apps) + +```javascript +import { createClient } from "@base44/sdk"; + +const base44 = createClient({ appId: "your-app-id" }); // MUST use 'appId' +``` diff --git a/evals/fixtures/existing-app-add-feature/skills/base44-sdk/references/analytics.md b/evals/fixtures/existing-app-add-feature/skills/base44-sdk/references/analytics.md new file mode 100644 index 0000000..d1b2c69 --- /dev/null +++ b/evals/fixtures/existing-app-add-feature/skills/base44-sdk/references/analytics.md @@ -0,0 +1,113 @@ +# Analytics Module + +Track custom events and user activity via `base44.analytics`. + +## Contents +- [Methods](#methods) +- [Examples](#examples) +- [Automatic Tracking](#automatic-tracking) +- [Best Practices](#best-practices) + +## Methods + +| Method | Signature | Description | +|--------|-----------|-------------| +| `track(params)` | `void` | Track a custom event | + +## Examples + +### Track Custom Event + +```javascript +// Track a simple event +base44.analytics.track({ + eventName: "button_clicked" +}); + +// Track event with properties +base44.analytics.track({ + eventName: "purchase_completed", + properties: { + product_id: "prod-123", + amount: 99.99, + currency: "USD" + } +}); +``` + +### Track User Actions + +```javascript +// Track page view +base44.analytics.track({ + eventName: "page_view", + properties: { + page: "/dashboard", + referrer: document.referrer + } +}); + +// Track feature usage +base44.analytics.track({ + eventName: "feature_used", + properties: { + feature: "export_data", + format: "csv" + } +}); +``` + +## Automatic Tracking + +The analytics module automatically tracks: +- **Initialization events**: When the app loads +- **Heartbeat events**: Periodic activity signals +- **Session duration**: Time spent in the app + +These internal events help measure user engagement without manual instrumentation. + +## Best Practices + +1. **Use descriptive event names**: `order_completed` instead of `click` +2. **Include relevant properties**: Add context that helps analyze the event +3. **Be consistent**: Use the same event names and property keys across your app +4. **Don't track sensitive data**: Avoid PII in event properties + +```javascript +// Good: Descriptive with relevant properties +base44.analytics.track({ + eventName: "subscription_started", + properties: { + plan: "pro", + billing_cycle: "annual" + } +}); + +// Avoid: Vague event name, no context +base44.analytics.track({ + eventName: "click" +}); +``` + +## Type Definitions + +```typescript +/** Properties that can be attached to a tracked event. */ +type TrackEventProperties = { + [key: string]: string | number | boolean | null | undefined; +}; + +/** Parameters for the track() method. */ +interface TrackEventParams { + /** The name of the event to track. */ + eventName: string; + /** Optional properties to attach to the event. */ + properties?: TrackEventProperties; +} + +/** The analytics module interface. */ +interface AnalyticsModule { + /** Track a custom event with optional properties. */ + track(params: TrackEventParams): void; +} +``` diff --git a/evals/fixtures/existing-app-add-feature/skills/base44-sdk/references/app-logs.md b/evals/fixtures/existing-app-add-feature/skills/base44-sdk/references/app-logs.md new file mode 100644 index 0000000..0439805 --- /dev/null +++ b/evals/fixtures/existing-app-add-feature/skills/base44-sdk/references/app-logs.md @@ -0,0 +1,78 @@ +# App Logs Module + +Log user activity in your app via `base44.appLogs`. + +## Contents +- [Methods](#methods) +- [Examples](#examples) +- [Use Cases](#use-cases) + +## Methods + +| Method | Signature | Description | +|--------|-----------|-------------| +| `logUserInApp(pageName)` | `Promise` | Log user activity on a page | + +## Examples + +### Log User Activity + +```javascript +// Log when user visits a page +await base44.appLogs.logUserInApp("dashboard"); + +// Log specific page visits +await base44.appLogs.logUserInApp("settings"); +await base44.appLogs.logUserInApp("profile"); + +// Log feature usage +await base44.appLogs.logUserInApp("export-button-click"); +``` + +The page name doesn't have to be an actual page - it can be any string you want to track. + +## Use Cases + +### Track Page Views in React + +```javascript +// Log page views on route change +useEffect(() => { + base44.appLogs.logUserInApp(window.location.pathname); +}, [location.pathname]); +``` + +### Track Feature Usage + +```javascript +// Log when user uses specific features +function handleExport() { + base44.appLogs.logUserInApp("export-data"); + // ... export logic +} + +function handleSettingsChange() { + base44.appLogs.logUserInApp("settings-updated"); + // ... save settings +} +``` + +## Notes + +- Logs appear in the Analytics page of your app dashboard +- App logs track page-level and feature-level activity +- Use `analytics.track()` for custom events with properties, `appLogs.logUserInApp()` for simple page/feature tracking + +## Type Definitions + +```typescript +/** App Logs module for tracking and analyzing app usage. */ +interface AppLogsModule { + /** + * Log user activity in the app. + * @param pageName - Name of the page or section being visited. + * @returns Promise that resolves when the log is recorded. + */ + logUserInApp(pageName: string): Promise; +} +``` diff --git a/evals/fixtures/existing-app-add-feature/skills/base44-sdk/references/auth.md b/evals/fixtures/existing-app-add-feature/skills/base44-sdk/references/auth.md new file mode 100644 index 0000000..5195b3a --- /dev/null +++ b/evals/fixtures/existing-app-add-feature/skills/base44-sdk/references/auth.md @@ -0,0 +1,761 @@ +# Auth Module + +User authentication, registration, and session management via `base44.auth`. + +## Contents +- [TypeScript Types](#typescript-types) +- [Methods](#methods) +- [Examples](#examples) +- [Error Handling](#error-handling) +- [Auth Providers](#auth-providers) +- [Environment Availability](#environment-availability) +- [App Visibility](#app-visibility) +- [Limitations](#limitations) + +--- + +## TypeScript Types + +### User Interface +```typescript +interface User { + id: string; + created_date: string; + updated_date: string; + email: string; + full_name: string | null; + disabled: boolean | null; + is_verified: boolean; + app_id: string; + is_service: boolean; + role: string; + [key: string]: any; // Custom schema fields +} +``` + +### LoginResponse Interface +```typescript +interface LoginResponse { + access_token: string; // JWT token + user: User; // Complete user object +} +``` + +### Parameter Interfaces + +#### RegisterParams +```typescript +interface RegisterParams { + email: string; // Required + password: string; // Required + turnstile_token?: string | null; // Optional: Cloudflare Turnstile for bot protection + referral_code?: string | null; // Optional: Referral code +} +``` + +#### VerifyOtpParams +```typescript +interface VerifyOtpParams { + email: string; // User's email + otpCode: string; // OTP code from email +} +``` + +#### ResetPasswordParams +```typescript +interface ResetPasswordParams { + resetToken: string; // Token from password reset email + newPassword: string; // New password to set +} +``` + +#### ChangePasswordParams +```typescript +interface ChangePasswordParams { + userId: string; // User ID + currentPassword: string; // Current password for verification + newPassword: string; // New password to set +} +``` + +### Provider Type +```typescript +type Provider = 'google' | 'microsoft' | 'facebook'; +``` + +--- + +## Methods + +### Module Interface +```typescript +interface AuthModule { + // User Info + me(): Promise; + updateMe(data: Partial>): Promise; + isAuthenticated(): Promise; + + // Login/Logout + loginViaEmailPassword(email: string, password: string, turnstileToken?: string): Promise; + loginWithProvider(provider: Provider, fromUrl?: string): void; + logout(redirectUrl?: string): void; + redirectToLogin(nextUrl: string): void; + + // Token Management + setToken(token: string, saveToStorage?: boolean): void; + + // Registration + register(params: RegisterParams): Promise; + verifyOtp(params: VerifyOtpParams): Promise; + resendOtp(email: string): Promise; + + // User Management + inviteUser(userEmail: string, role: string): Promise; + + // Password Management + resetPasswordRequest(email: string): Promise; + resetPassword(params: ResetPasswordParams): Promise; + changePassword(params: ChangePasswordParams): Promise; +} +``` + +### Method Reference Table + +| Method | Parameters | Return Type | Description | +|--------|-----------|-------------|-------------| +| `register()` | `params: RegisterParams` | `Promise` | Create new user account | +| `loginViaEmailPassword()` | `email: string, password: string, turnstileToken?: string` | `Promise` | Authenticate with email/password | +| `loginWithProvider()` | `provider: Provider, fromUrl?: string` | `void` | Initiate OAuth login flow | +| `me()` | None | `Promise` | Get current authenticated user | +| `updateMe()` | `data: Partial` | `Promise` | Update current user's profile | +| `logout()` | `redirectUrl?: string` | `void` | Clear session, optionally redirect | +| `redirectToLogin()` | `nextUrl: string` | `void` | ⚠️ **Avoid** - Prefer custom login UI with `loginViaEmailPassword()` or `loginWithProvider()` | +| `isAuthenticated()` | None | `Promise` | Check if user is logged in | +| `setToken()` | `token: string, saveToStorage?: boolean` | `void` | Manually set auth token | +| `inviteUser()` | `userEmail: string, role: string` | `Promise` | Send invitation email | +| `verifyOtp()` | `params: VerifyOtpParams` | `Promise` | Verify OTP code | +| `resendOtp()` | `email: string` | `Promise` | Resend OTP code | +| `resetPasswordRequest()` | `email: string` | `Promise` | Request password reset | +| `resetPassword()` | `params: ResetPasswordParams` | `Promise` | Reset password with token | +| `changePassword()` | `params: ChangePasswordParams` | `Promise` | Change user password | + +--- + +## Examples + +### Register New User (Complete Flow) + +Registration requires email verification before login. Complete flow: + +1. **Register** - Create the user account +2. **Verification email sent** - User receives an OTP code +3. **Verify OTP** - User enters code to verify email +4. **Login** - User can now log in + +```javascript +try { + // Step 1: Register the user + await base44.auth.register({ + email: "user@example.com", + password: "securePassword123", + referral_code: "OPTIONAL_CODE", // optional + turnstile_token: "CAPTCHA_TOKEN" // optional, for bot protection + }); + console.log('Registration successful. Check email for OTP code.'); + + // Step 2: User receives email with OTP code (e.g., "123456") + + // Step 3: Verify the OTP code + await base44.auth.verifyOtp({ + email: "user@example.com", + otpCode: "123456" // code from verification email + }); + console.log('Email verified successfully.'); + + // Step 4: Now the user can log in + const loginResponse = await base44.auth.loginViaEmailPassword( + "user@example.com", + "securePassword123" + ); + console.log('Login successful:', loginResponse.user); + +} catch (error) { + console.error('Registration flow failed:', error.message); + // Handle specific errors (see Error Handling section) +} +``` + +> **Important**: Users cannot log in until they complete OTP verification. Attempting to call `loginViaEmailPassword` before verification will fail. + +### Login with Email/Password + +```javascript +try { + const response = await base44.auth.loginViaEmailPassword( + "user@example.com", + "password123", + turnstileToken // optional: for bot protection + ); + + console.log('Login successful'); + console.log('User:', response.user); + console.log('Token:', response.access_token); + + // JWT is automatically stored for subsequent requests + +} catch (error) { + console.error('Login failed:', error.message); + if (error.status === 401) { + console.error('Invalid credentials'); + } else if (error.status === 403) { + console.error('Email not verified. Please check your email for OTP.'); + } +} +``` + +### Login with OAuth Provider + +```javascript +// Redirect to Google OAuth +base44.auth.loginWithProvider('google'); + +// Redirect to Google OAuth and return to current page after +base44.auth.loginWithProvider('google', window.location.href); + +// Supported providers: 'google', 'microsoft', 'facebook' +base44.auth.loginWithProvider('microsoft'); +``` + +### Get Current User + +```javascript +try { + const user = await base44.auth.me(); + + if (user) { + console.log('User ID:', user.id); + console.log('Email:', user.email); + console.log('Name:', user.full_name); + console.log('Role:', user.role); + console.log('Verified:', user.is_verified); + } else { + console.log('User not authenticated'); + } + +} catch (error) { + console.error('Failed to fetch user:', error.message); + if (error.status === 401) { + // Token expired or invalid - navigate to your custom login page + navigate('/login'); + } +} +``` + +### Update User Profile + +```javascript +try { + const updatedUser = await base44.auth.updateMe({ + full_name: "John Doe", + // Custom schema fields can be updated here + phone: "+1234567890", + preferences: { theme: "dark" } + }); + + console.log('Profile updated:', updatedUser); + +} catch (error) { + console.error('Profile update failed:', error.message); + if (error.status === 400) { + console.error('Invalid data provided'); + } else if (error.status === 401) { + console.error('Authentication required'); + navigate('/login'); + } +} +``` + +### Check Authentication Status + +```javascript +try { + const isLoggedIn = await base44.auth.isAuthenticated(); + + if (isLoggedIn) { + // Show authenticated UI + console.log('User is authenticated'); + } else { + // Show login button + console.log('User is not authenticated'); + } + +} catch (error) { + console.error('Failed to check authentication:', error.message); + // On error, treat as not authenticated +} +``` + +### Logout + +```javascript +// Simple logout +base44.auth.logout(); + +// Logout and redirect to goodbye page +base44.auth.logout("/goodbye"); + +// Logout and redirect to homepage +base44.auth.logout("/"); +``` + +### Protected Route Pattern + +```javascript +// Using a navigation function (e.g., React Router's useNavigate, Next.js router) +async function requireAuth(navigate) { + try { + const user = await base44.auth.me(); + + if (!user) { + // Navigate to your custom login page + navigate('/login', { state: { returnTo: window.location.pathname } }); + return null; + } + + return user; + + } catch (error) { + console.error('Authentication check failed:', error.message); + navigate('/login', { state: { returnTo: window.location.pathname } }); + return null; + } +} + +// Usage in your app +async function loadProtectedPage(navigate) { + const user = await requireAuth(navigate); + if (!user) { + // Will navigate to login + return; + } + + // Continue with authenticated logic + console.log('Welcome,', user.full_name); +} +``` + +### Set Authentication Token + +```javascript +// SECURITY WARNING: Never hardcode tokens or expose them in client code +// Tokens should only be received from secure authentication flows + +// Set token and save to localStorage (default) +base44.auth.setToken(receivedToken); + +// Set token without saving to localStorage (temporary session) +base44.auth.setToken(receivedToken, false); + +// Verify token was set +try { + const isAuthenticated = await base44.auth.isAuthenticated(); + if (!isAuthenticated) { + console.error('Token validation failed'); + } +} catch (error) { + console.error('Failed to set token:', error.message); +} +``` + +### Invite User to Application + +```javascript +try { + // Note: Typically requires admin privileges + const response = await base44.auth.inviteUser( + "newuser@example.com", + "user" // or "admin" + ); + + console.log('Invitation sent successfully'); + +} catch (error) { + console.error('Failed to invite user:', error.message); + if (error.status === 403) { + console.error('Insufficient permissions to invite users'); + } else if (error.status === 400) { + console.error('Invalid email or role'); + } +} +``` + +### OTP Verification + +```javascript +try { + // Verify OTP code sent to user's email + await base44.auth.verifyOtp({ + email: "user@example.com", + otpCode: "123456" + }); + + console.log('OTP verified successfully'); + +} catch (error) { + console.error('OTP verification failed:', error.message); + if (error.status === 400) { + console.error('Invalid or expired OTP code'); + } else if (error.status === 429) { + console.error('Too many attempts. Please try again later.'); + } +} + +// Resend OTP if needed +try { + await base44.auth.resendOtp("user@example.com"); + console.log('OTP resent successfully'); + +} catch (error) { + console.error('Failed to resend OTP:', error.message); + if (error.status === 429) { + console.error('Too many requests. Please wait before trying again.'); + } +} +``` + +### Password Reset Flow + +```javascript +// Step 1: Request password reset +try { + await base44.auth.resetPasswordRequest("user@example.com"); + console.log('Password reset email sent. Check your inbox.'); + +} catch (error) { + console.error('Password reset request failed:', error.message); + if (error.status === 429) { + console.error('Too many requests. Please try again later.'); + } + // Note: For security, don't reveal if email exists +} + +// Step 2: Reset password with token from email +try { + await base44.auth.resetPassword({ + resetToken: "token-from-email", + newPassword: "newSecurePassword123" + }); + + console.log('Password reset successfully. You can now log in.'); + +} catch (error) { + console.error('Password reset failed:', error.message); + if (error.status === 400) { + console.error('Invalid or expired reset token'); + } else if (error.status === 422) { + console.error('Password does not meet requirements'); + } +} +``` + +### Change Password + +```javascript +try { + const currentUser = await base44.auth.me(); + + if (!currentUser) { + throw new Error('User must be authenticated to change password'); + } + + await base44.auth.changePassword({ + userId: currentUser.id, + currentPassword: "oldPassword123", + newPassword: "newSecurePassword456" + }); + + console.log('Password changed successfully'); + +} catch (error) { + console.error('Password change failed:', error.message); + if (error.status === 401) { + console.error('Current password is incorrect'); + } else if (error.status === 422) { + console.error('New password does not meet requirements'); + } else if (error.status === 403) { + console.error('Not authorized to change this password'); + } +} +``` + +--- + +## Error Handling + +### Common Error Scenarios + +The auth module can throw various errors. Here are common scenarios and how to handle them: + +#### Authentication Errors (401/403) +```javascript +try { + const user = await base44.auth.me(); +} catch (error) { + if (error.status === 401) { + // Token expired or invalid - navigate to your custom login page + navigate('/login'); + } else if (error.status === 403) { + // Email not verified or insufficient permissions + console.error('Access denied:', error.message); + } +} +``` + +#### Validation Errors (400/422) +```javascript +try { + await base44.auth.register({ + email: "invalid-email", + password: "weak" + }); +} catch (error) { + if (error.status === 400) { + console.error('Invalid input:', error.message); + // Handle validation errors + } else if (error.status === 422) { + console.error('Data validation failed:', error.details); + } +} +``` + +#### Rate Limiting (429) +```javascript +try { + await base44.auth.resendOtp("user@example.com"); +} catch (error) { + if (error.status === 429) { + console.error('Too many requests. Please wait before trying again.'); + // Show countdown or disable button temporarily + } +} +``` + +#### Generic Error Handler +```javascript +function handleAuthError(error) { + switch (error.status) { + case 400: + return 'Invalid input. Please check your data.'; + case 401: + return 'Authentication required. Redirecting to login...'; + case 403: + return 'Access denied. You may need to verify your email.'; + case 404: + return 'Resource not found.'; + case 422: + return 'Validation failed. Please check your input.'; + case 429: + return 'Too many requests. Please try again later.'; + case 500: + return 'Server error. Please try again.'; + default: + return 'An unexpected error occurred.'; + } +} + +// Usage +try { + await base44.auth.loginViaEmailPassword(email, password); +} catch (error) { + console.error(handleAuthError(error)); +} +``` + +--- + +## Auth Providers + +Configure authentication providers in your app dashboard: + +### Available Providers + +**Built-in (All Plans):** +- **Email/Password** - Default, always enabled +- **Google** - OAuth authentication +- **Microsoft** - OAuth authentication +- **Facebook** - OAuth authentication + +**SSO Providers (Elite Plan):** +- **Okta** +- **Azure AD** +- **GitHub** + +### Using OAuth Providers + +```javascript +// Initiate OAuth login flow +base44.auth.loginWithProvider('google'); + +// Return to specific page after authentication +base44.auth.loginWithProvider('microsoft', '/dashboard'); + +// Supported values: 'google', 'microsoft', 'facebook' +``` + +--- + +## Environment Availability + +| Environment | Availability | Notes | +|------------|--------------|-------| +| **Frontend** | ✅ Yes | All methods available | +| **Backend Functions** | ✅ Yes | Use `createClientFromRequest(req)` for authenticated client | +| **Service Role** | ❌ No | Auth methods not available in service role context | + +### Frontend Usage +```javascript +// Standard browser usage +const user = await base44.auth.me(); +``` + +### Backend Functions Usage +```javascript +import { createClientFromRequest } from "@base44/sdk"; + +Deno.serve(async (req) => { + const base44 = createClientFromRequest(req); + + // Get user from request context + const user = await base44.auth.me(); + + // User operations here... + return Response.json({ user }); +}); +``` + +--- + +## App Visibility + +Control who can access your app in the app settings: + +### Public Apps +- No login required for basic access +- Users can view public content without authentication +- Authenticated users get additional features/data + +### Private Apps +- Login required to access any content +- Unauthenticated users are automatically redirected to login +- All content is protected by default + +--- + +## Limitations + +### Authentication UI Options +- **Recommended:** Build custom login/signup UI using `loginViaEmailPassword()` and `loginWithProvider()` for full control over user experience and branding +- **Alternative:** `redirectToLogin()` uses Base44's hosted authentication pages with limited customization + +### Hosted Login (via redirectToLogin) +- `redirectToLogin()` shows both login and signup options on the same page +- No separate `redirectToSignup()` method +- Users can switch between login/signup on the hosted page +- ⚠️ **Note:** Prefer building custom login UI for better user experience + +### Password Requirements +- Minimum length and complexity requirements enforced +- Requirements not exposed via API +- Validation errors returned when requirements not met + +### Rate Limiting +- OTP requests are rate-limited to prevent abuse +- Password reset requests are rate-limited +- Login attempts may be rate-limited with Turnstile protection + +### Token Management +- JWTs are automatically stored in localStorage by default +- Token expiration and refresh not exposed in API +- Call `me()` or `isAuthenticated()` to verify token validity + +--- + +## Best Practices + +### 1. Always Handle Errors +```javascript +try { + await base44.auth.loginViaEmailPassword(email, password); +} catch (error) { + // Always handle authentication errors + console.error('Login failed:', error.message); +} +``` + +### 2. Verify Authentication Before Protected Actions +```javascript +const user = await requireAuth(); +if (!user) return; // Will redirect to login +// Proceed with authenticated action +``` + +### 3. Use Type Safety with TypeScript +```typescript +import type { User, LoginResponse } from '@base44/sdk'; + +const user: User = await base44.auth.me(); +const response: LoginResponse = await base44.auth.loginViaEmailPassword(email, password); +``` + +### 4. Don't Hardcode Credentials +```javascript +// ❌ BAD +base44.auth.setToken("hardcoded-token-here"); + +// ✅ GOOD +const token = await secureAuthFlow(); +base44.auth.setToken(token); +``` + +### 5. Provide User Feedback +```javascript +try { + await base44.auth.register(params); + showSuccessMessage('Registration successful! Check your email for verification code.'); +} catch (error) { + showErrorMessage('Registration failed: ' + error.message); +} +``` + +### 6. Handle Token Expiration Gracefully +```javascript +try { + const user = await base44.auth.me(); +} catch (error) { + if (error.status === 401) { + // Clear local state and navigate to login + localStorage.clear(); + navigate('/login'); + } +} +``` + +### 7. Build Custom Login UI (Recommended) +```javascript +// ✅ RECOMMENDED - Custom login form with direct methods +const handleLogin = async (email, password) => { + try { + const { user } = await base44.auth.loginViaEmailPassword(email, password); + navigate('/dashboard'); + } catch (error) { + setError(error.message); + } +}; + +const handleGoogleLogin = () => { + base44.auth.loginWithProvider('google', '/dashboard'); +}; + +// ❌ AVOID - Redirecting to hosted login page +// base44.auth.redirectToLogin(window.location.href); +``` diff --git a/evals/fixtures/existing-app-add-feature/skills/base44-sdk/references/base44-agents.md b/evals/fixtures/existing-app-add-feature/skills/base44-sdk/references/base44-agents.md new file mode 100644 index 0000000..ee55dee --- /dev/null +++ b/evals/fixtures/existing-app-add-feature/skills/base44-sdk/references/base44-agents.md @@ -0,0 +1,364 @@ +# Agents Module + +AI agent conversations and messages via `base44.agents`. + +> **Note:** This module requires a logged-in user. All agent methods work in the context of the authenticated user. + +## Contents +- [Concepts](#concepts) +- [Methods](#methods) +- [Examples](#examples) (Create, Get Conversations, List, Subscribe, Send Message, WhatsApp) +- [Message Structure](#message-structure) +- [Conversation Structure](#conversation-structure) +- [Common Patterns](#common-patterns) + +## Concepts + +- **Conversation**: A dialogue between user and an AI agent. Has unique ID, agent name, user reference, and metadata. +- **Message**: Single message in a conversation. Has role (`user`, `assistant`, `system`), content, timestamps, and optional metadata. + +## Methods + +| Method | Signature | Description | +|--------|-----------|-------------| +| `createConversation(params)` | `Promise` | Create a new conversation with an agent | +| `getConversations()` | `Promise` | Get all user's conversations | +| `getConversation(id)` | `Promise` | Get conversation with messages | +| `listConversations(filterParams)` | `Promise` | Filter/sort/paginate conversations | +| `subscribeToConversation(id, onUpdate?)` | `() => void` | Real-time updates via WebSocket (returns unsubscribe function) | +| `addMessage(conversation, message)` | `Promise` | Send a message | +| `getWhatsAppConnectURL(agentName)` | `string` | Get WhatsApp connection URL for agent | + +## Examples + +### Create Conversation + +```javascript +const conversation = await base44.agents.createConversation({ + agent_name: "support-agent", + metadata: { + order_id: "ORD-123", + category: "billing" + } +}); + +console.log(conversation.id); +``` + +### Get All Conversations + +```javascript +const conversations = await base44.agents.getConversations(); + +conversations.forEach(conv => { + console.log(conv.id, conv.agent_name, conv.created_date); +}); +``` + +### Get Single Conversation (with messages) + +```javascript +const conversation = await base44.agents.getConversation("conv-id-123"); + +console.log(conversation.messages); +``` + +### List with Filters + +```javascript +// Using filterParams object with q, sort, limit, skip, fields +const recent = await base44.agents.listConversations({ + q: { agent_name: "support-agent" }, + sort: "-created_date", + limit: 10, + skip: 0 +}); + +// Filter by metadata +const highPriority = await base44.agents.listConversations({ + q: { + agent_name: "support-agent", + "metadata.priority": "high" + }, + sort: "-updated_date", + limit: 20 +}); +``` + +### Subscribe to Updates (Real-time) + +```javascript +const unsubscribe = base44.agents.subscribeToConversation( + "conv-id-123", + (updatedConversation) => { + // Called when new messages arrive + console.log("New messages:", updatedConversation.messages); + } +); + +// Later: unsubscribe +unsubscribe(); +``` + +### Send a Message + +```javascript +const conversation = await base44.agents.getConversation("conv-id-123"); + +await base44.agents.addMessage(conversation, { + role: "user", + content: "What's the weather like today?" +}); +``` + +### Get WhatsApp Connection URL + +```javascript +const whatsappUrl = base44.agents.getWhatsAppConnectURL("support-agent"); +// Returns URL for users to connect with agent via WhatsApp +console.log(whatsappUrl); +``` + +## Message Structure + +```javascript +{ + role: "user" | "assistant" | "system", + content: "Message text or structured object", + created_date: "2024-01-15T10:30:00Z", + updated_date: "2024-01-15T10:30:00Z", + + // Optional fields + reasoning: { + content: "Agent's reasoning process", + timing: 1500 + }, + tool_calls: [{ + name: "search", + arguments: { query: "weather" }, + result: { ... }, + status: "success" + }], + file_urls: ["https://..."], + usage: { + prompt_tokens: 150, + completion_tokens: 50 + }, + metadata: { ... }, + custom_context: { ... } +} +``` + +## Conversation Structure + +```javascript +{ + id: "conv-id-123", + app_id: "app-id", + agent_name: "support-agent", + created_by_id: "user-id", + created_date: "2024-01-15T10:00:00Z", + updated_date: "2024-01-15T10:30:00Z", + messages: [ ... ], + metadata: { ... } +} +``` + +## Common Patterns + +### Chat Interface + +```javascript +// Load conversation +const conv = await base44.agents.getConversation(conversationId); +setMessages(conv.messages); + +// Subscribe to updates +const unsubscribe = base44.agents.subscribeToConversation(conversationId, (updated) => { + setMessages(updated.messages); +}); + +// Send message +async function sendMessage(text) { + await base44.agents.addMessage(conv, { role: "user", content: text }); +} + +// Cleanup on unmount +return () => unsubscribe(); +``` + +## Type Definitions + +### AgentConversation + +```typescript +/** An agent conversation containing messages exchanged with an AI agent. */ +interface AgentConversation { + /** Unique identifier for the conversation. */ + id: string; + /** Application ID. */ + app_id: string; + /** Name of the agent in this conversation. */ + agent_name: string; + /** ID of the user who created the conversation. */ + created_by_id: string; + /** When the conversation was created. */ + created_date: string; + /** When the conversation was last updated. */ + updated_date: string; + /** Array of messages in the conversation. */ + messages: AgentMessage[]; + /** Optional metadata associated with the conversation. */ + metadata?: Record; +} +``` + +### AgentMessage + +```typescript +/** A message in an agent conversation. */ +interface AgentMessage { + /** Unique identifier for the message. */ + id: string; + /** Role of the message sender. */ + role: "user" | "assistant" | "system"; + /** When the message was created. */ + created_date: string; + /** When the message was last updated. */ + updated_date: string; + /** Message content. */ + content?: string | Record; + /** Optional reasoning information for the message. */ + reasoning?: AgentMessageReasoning | null; + /** URLs to files attached to the message. */ + file_urls?: string[]; + /** Tool calls made by the agent. */ + tool_calls?: AgentMessageToolCall[]; + /** Token usage statistics. */ + usage?: AgentMessageUsage; + /** Whether the message is hidden from the user. */ + hidden?: boolean; + /** Custom context provided with the message. */ + custom_context?: AgentMessageCustomContext[]; + /** Model used to generate the message. */ + model?: string; + /** Checkpoint ID for the message. */ + checkpoint_id?: string; + /** Metadata about when and by whom the message was created. */ + metadata?: AgentMessageMetadata; +} +``` + +### Supporting Types + +```typescript +/** Reasoning information for an agent message. */ +interface AgentMessageReasoning { + /** When reasoning started. */ + start_date: string; + /** When reasoning ended. */ + end_date?: string; + /** Reasoning content. */ + content: string; +} + +/** A tool call made by the agent. */ +interface AgentMessageToolCall { + /** Tool call ID. */ + id: string; + /** Name of the tool called. */ + name: string; + /** Arguments passed to the tool as JSON string. */ + arguments_string: string; + /** Status of the tool call. */ + status: "running" | "success" | "error" | "stopped"; + /** Results from the tool call. */ + results?: string; +} + +/** Token usage statistics for an agent message. */ +interface AgentMessageUsage { + /** Number of tokens in the prompt. */ + prompt_tokens?: number; + /** Number of tokens in the completion. */ + completion_tokens?: number; +} + +/** Custom context provided with an agent message. */ +interface AgentMessageCustomContext { + /** Context message. */ + message: string; + /** Associated data for the context. */ + data: Record; + /** Type of context. */ + type: string; +} + +/** Metadata about when and by whom a message was created. */ +interface AgentMessageMetadata { + /** When the message was created. */ + created_date: string; + /** Email of the user who created the message. */ + created_by_email: string; + /** Full name of the user who created the message. */ + created_by_full_name: string; +} +``` + +### CreateConversationParams + +```typescript +/** Parameters for creating a new conversation. */ +interface CreateConversationParams { + /** The name of the agent to create a conversation with. */ + agent_name: string; + /** Optional metadata to attach to the conversation. */ + metadata?: Record; +} +``` + +### ModelFilterParams + +```typescript +/** Parameters for filtering, sorting, and paginating conversations. */ +interface ModelFilterParams { + /** Query object with field-value pairs for filtering. */ + q?: Record; + /** Sort parameter (e.g., "-created_date" for descending). */ + sort?: string | null; + /** Maximum number of results to return. */ + limit?: number | null; + /** Number of results to skip for pagination. */ + skip?: number | null; + /** Array of field names to include in the response. */ + fields?: string[] | null; +} +``` + +### AgentsModule + +```typescript +/** Agents module for managing AI agent conversations. */ +interface AgentsModule { + /** Gets all conversations from all agents in the app. */ + getConversations(): Promise; + + /** Gets a specific conversation by ID. */ + getConversation(conversationId: string): Promise; + + /** Lists conversations with filtering, sorting, and pagination. */ + listConversations(filterParams: ModelFilterParams): Promise; + + /** Creates a new conversation with an agent. */ + createConversation(conversation: CreateConversationParams): Promise; + + /** Adds a message to a conversation. */ + addMessage(conversation: AgentConversation, message: Partial): Promise; + + /** Subscribes to real-time updates for a conversation. Returns unsubscribe function. */ + subscribeToConversation(conversationId: string, onUpdate?: (conversation: AgentConversation) => void): () => void; + + /** Gets WhatsApp connection URL for an agent. */ + getWhatsAppConnectURL(agentName: string): string; +} +``` diff --git a/evals/fixtures/existing-app-add-feature/skills/base44-sdk/references/client.md b/evals/fixtures/existing-app-add-feature/skills/base44-sdk/references/client.md new file mode 100644 index 0000000..554bf2b --- /dev/null +++ b/evals/fixtures/existing-app-add-feature/skills/base44-sdk/references/client.md @@ -0,0 +1,257 @@ +# Client Setup + +How to create and configure the Base44 client. + +## Contents +- [In Base44-Generated Apps](#in-base44-generated-apps) +- [In External Apps](#in-external-apps) +- [In Backend Functions](#in-backend-functions) +- [Authentication Modes](#authentication-modes) (Anonymous, User, Service Role) +- [Available Modules](#available-modules) +- [Client Methods](#client-methods) +- [Client Configuration Options](#client-configuration-options) + +## In Base44-Generated Apps + +The client is pre-configured and available as `base44`. Just use it: + +```javascript +const tasks = await base44.entities.Task.list(); +``` + +## In External Apps + +Install the SDK and create a client: + +```bash +npm install @base44/sdk +``` + +```javascript +import { createClient } from "@base44/sdk"; + +// IMPORTANT: The parameter name is 'appId' (NOT 'clientId', NOT 'id') +// IMPORTANT: onError must be nested inside 'options' object +const base44 = createClient({ + appId: "your-app-id", // Required: Use 'appId' parameter + token: "optional-user-token", // Optional: for pre-authenticated requests + options: { // Optional: configuration options + onError: (error) => { // Optional: error handler (must be in options) + console.error("Base44 error:", error); + } + } +}); +``` + +**Common Mistakes:** +- ❌ `createClient({ clientId: "..." })` - WRONG parameter name +- ❌ `createClient({ id: "..." })` - WRONG parameter name +- ❌ `createClient({ appId: "...", onError: ... })` - WRONG: onError must be in options +- ✅ `createClient({ appId: "..." })` - CORRECT parameter name +- ✅ `createClient({ appId: "...", options: { onError: ... } })` - CORRECT: onError in options + +## In Backend Functions + +Use `createClientFromRequest` to get a client with the caller's auth context: + +```javascript +import { createClientFromRequest } from "@base44/sdk"; + +Deno.serve(async (req) => { + const base44 = createClientFromRequest(req); + + // Client inherits authentication from the request + const user = await base44.auth.me(); + + return Response.json({ user }); +}); +``` + +## Authentication Modes + +| Mode | How to Get | Permissions | +|------|-----------|-------------| +| **Anonymous** | `createClient({ appId })` without token | Public data only | +| **User** | After `loginViaEmailPassword()` or via `createClientFromRequest` | User's own data | +| **Service Role** | `base44.asServiceRole.*` in backend | Full admin access | + +## Anonymous Mode + +No authentication. Can only access public resources. + +```javascript +const base44 = createClient({ appId: "your-app-id" }); + +// Only works if Task entity allows anonymous read +const publicTasks = await base44.entities.Task.list(); +``` + +## User Mode + +After user logs in, the client automatically includes their token. + +```javascript +const base44 = createClient({ appId: "your-app-id" }); + +// Login sets the token +await base44.auth.loginViaEmailPassword("user@example.com", "password"); + +// Subsequent requests are authenticated +const user = await base44.auth.me(); +const myTasks = await base44.entities.Task.list(); // filtered by permissions +``` + +## Service Role Mode + +Admin-level access. **Backend only.** + +```javascript +// Inside a backend function +Deno.serve(async (req) => { + const base44 = createClientFromRequest(req); + + // User mode - respects permissions + const myTasks = await base44.entities.Task.list(); + + // Service role - bypasses permissions + const allTasks = await base44.asServiceRole.entities.Task.list(); + const allUsers = await base44.asServiceRole.entities.User.list(); + const oauthToken = await base44.asServiceRole.connectors.getAccessToken("slack"); + + return Response.json({ myTasks, allTasks }); +}); +``` + +## Available Modules + +The client exposes these modules: + +```javascript +base44.entities // CRUD operations +base44.auth // Authentication +base44.agents // AI conversations +base44.functions // Backend function invocation +base44.integrations // Third-party services +base44.analytics // Event tracking +base44.appLogs // App usage logging +base44.users // User invitations + +// Service role only (backend) +base44.asServiceRole.entities +base44.asServiceRole.agents +base44.asServiceRole.functions +base44.asServiceRole.integrations +base44.asServiceRole.appLogs +base44.asServiceRole.connectors +``` + +## Client Methods + +The client provides these methods: + +```javascript +// Set authentication token for all subsequent requests +base44.setToken(newToken); + +// Cleanup WebSocket connections (call when done with client) +base44.cleanup(); +``` + +### setToken + +Updates the authentication token for all subsequent API requests and WebSocket connections. + +```javascript +// After receiving a token (e.g., from external auth) +base44.setToken("new-jwt-token"); +``` + +### cleanup + +Disconnects WebSocket connections. Call when you're done with the client or when the component unmounts. + +```javascript +// Cleanup on component unmount (React example) +useEffect(() => { + return () => base44.cleanup(); +}, []); +``` + +## Client Configuration Options + +```javascript +createClient({ + appId: "your-app-id", // Required: MUST use 'appId' (not 'clientId' or 'id') + token: "jwt-token", // Optional: pre-set auth token + options: { // Optional: configuration options + onError: (error) => {} // Optional: global error handler (must be in options) + } +}); +``` + +**⚠️ Critical:** +- The parameter name is `appId`, not `clientId` or `id`. Using the wrong parameter name will cause errors. +- The `onError` handler must be nested inside the `options` object, not at the top level. + +## Type Definitions + +### CreateClientConfig + +```typescript +/** Configuration for creating a Base44 client. */ +interface CreateClientConfig { + /** The Base44 app ID (required). */ + appId: string; + /** User authentication token. Used to authenticate as a specific user. */ + token?: string; + /** Service role authentication token (backend only). */ + serviceToken?: string; + /** Additional client options. */ + options?: CreateClientOptions; +} + +/** Options for creating a Base44 client. */ +interface CreateClientOptions { + /** Optional error handler called whenever an API error occurs. */ + onError?: (error: Error) => void; +} +``` + +### Base44Client + +```typescript +/** The Base44 client instance. */ +interface Base44Client { + /** Entities module for CRUD operations on your data models. */ + entities: EntitiesModule; + /** Integrations module for calling pre-built integration endpoints. */ + integrations: IntegrationsModule; + /** Auth module for user authentication and management. */ + auth: AuthModule; + /** Functions module for invoking custom backend functions. */ + functions: FunctionsModule; + /** Agents module for managing AI agent conversations. */ + agents: AgentsModule; + /** App logs module for tracking app usage. */ + appLogs: AppLogsModule; + /** Analytics module for tracking custom events. */ + analytics: AnalyticsModule; + + /** Cleanup function to disconnect WebSocket connections. */ + cleanup(): void; + + /** Sets a new authentication token for all subsequent requests. */ + setToken(newToken: string): void; + + /** Provides access to modules with elevated service role permissions (backend only). */ + readonly asServiceRole: { + entities: EntitiesModule; + integrations: IntegrationsModule; + connectors: ConnectorsModule; + functions: FunctionsModule; + agents: AgentsModule; + appLogs: AppLogsModule; + cleanup(): void; + }; +} +``` diff --git a/evals/fixtures/existing-app-add-feature/skills/base44-sdk/references/connectors.md b/evals/fixtures/existing-app-add-feature/skills/base44-sdk/references/connectors.md new file mode 100644 index 0000000..f3fbcb4 --- /dev/null +++ b/evals/fixtures/existing-app-add-feature/skills/base44-sdk/references/connectors.md @@ -0,0 +1,113 @@ +# Connectors Module + +OAuth token management for external services. **Service role only** (backend functions). + +## Method + +```javascript +base44.asServiceRole.connectors.getAccessToken(integrationType): Promise +``` + +Returns a raw OAuth access token for the specified service. + +## Supported Services + +| Integration Type | Service | +|-----------------|---------| +| `"googlecalendar"` | Google Calendar | +| `"googledrive"` | Google Drive | +| `"slack"` | Slack | +| `"notion"` | Notion | +| `"salesforce"` | Salesforce | +| `"hubspot"` | HubSpot | +| `"linkedin"` | LinkedIn | +| `"tiktok"` | TikTok | +| `"github"` | GitHub | + +## Example Usage + +```javascript +// Backend function only +Deno.serve(async (req) => { + const base44 = createClientFromRequest(req); + + // Get OAuth token for Slack + const slackToken = await base44.asServiceRole.connectors.getAccessToken("slack"); + + // Use token directly with Slack API + const response = await fetch("https://slack.com/api/chat.postMessage", { + method: "POST", + headers: { + "Authorization": `Bearer ${slackToken}`, + "Content-Type": "application/json" + }, + body: JSON.stringify({ + channel: "#general", + text: "Hello from Base44!" + }) + }); + + return Response.json(await response.json()); +}); +``` + +## Google Calendar Example + +```javascript +Deno.serve(async (req) => { + const base44 = createClientFromRequest(req); + + const token = await base44.asServiceRole.connectors.getAccessToken("googlecalendar"); + + // List upcoming events + const response = await fetch( + "https://www.googleapis.com/calendar/v3/calendars/primary/events?" + + new URLSearchParams({ + maxResults: "10", + orderBy: "startTime", + singleEvents: "true", + timeMin: new Date().toISOString() + }), + { + headers: { "Authorization": `Bearer ${token}` } + } + ); + + const events = await response.json(); + return Response.json(events); +}); +``` + +## Setup Requirements + +1. **Builder plan** or higher +2. **Backend functions** enabled +3. **Connector configured** in Base44 dashboard (OAuth flow completed) + +## Important Notes + +- **One account per connector per app**: All users share the same connected account +- **Backend only**: `connectors` module not available in frontend code +- **Service role required**: Must use `base44.asServiceRole.connectors` +- **You handle the API calls**: Base44 only provides the token; you make the actual API requests +- **Token refresh**: Base44 handles token refresh automatically + +## Type Definitions + +```typescript +/** + * The type of external integration/connector. + * Examples: 'googlecalendar', 'slack', 'github', 'notion', etc. + */ +type ConnectorIntegrationType = string; + +/** Connectors module for managing OAuth tokens for external services. */ +interface ConnectorsModule { + /** + * Retrieves an OAuth access token for a specific external integration type. + * @param integrationType - The type of integration (e.g., 'googlecalendar', 'slack'). + * @returns Promise resolving to the access token string. + */ + getAccessToken(integrationType: ConnectorIntegrationType): Promise; +} +``` diff --git a/evals/fixtures/existing-app-add-feature/skills/base44-sdk/references/entities.md b/evals/fixtures/existing-app-add-feature/skills/base44-sdk/references/entities.md new file mode 100644 index 0000000..a7bdf35 --- /dev/null +++ b/evals/fixtures/existing-app-add-feature/skills/base44-sdk/references/entities.md @@ -0,0 +1,253 @@ +# Entities Module + +CRUD operations on data models. Access via `base44.entities.EntityName.method()`. + +## Contents +- [Methods](#methods) +- [Examples](#examples) (Create, Bulk Create, List, Filter, Get, Update, Delete, Subscribe) +- [User Entity](#user-entity) +- [Service Role Access](#service-role-access) +- [Permissions](#permissions) + +## Methods + +| Method | Signature | Description | +|--------|-----------|-------------| +| `create(data)` | `Promise` | Create one record | +| `bulkCreate(dataArray)` | `Promise` | Create multiple records | +| `list(sort?, limit?, skip?, fields?)` | `Promise` | Get all records (paginated) | +| `filter(query, sort?, limit?, skip?, fields?)` | `Promise` | Get records matching conditions | +| `get(id)` | `Promise` | Get single record by ID | +| `update(id, data)` | `Promise` | Update record (partial update) | +| `delete(id)` | `Promise` | Delete record by ID | +| `deleteMany(query)` | `Promise` | Delete all matching records | +| `importEntities(file)` | `Promise` | Import from CSV (frontend only) | +| `subscribe(callback)` | `() => void` | Subscribe to realtime updates (returns unsubscribe function) | + +## Examples + +### Create + +```javascript +const task = await base44.entities.Task.create({ + title: "Complete documentation", + status: "pending", + dueDate: "2024-12-31" +}); +``` + +### Bulk Create + +```javascript +const tasks = await base44.entities.Task.bulkCreate([ + { title: "Task 1", status: "pending" }, + { title: "Task 2", status: "pending" } +]); +``` + +### List with Pagination + +```javascript +// Get first 10 records, sorted by created_date descending +const tasks = await base44.entities.Task.list( + "-created_date", // sort (string: prefix with - for descending) + 10, // limit + 0 // skip +); + +// Get next page +const page2 = await base44.entities.Task.list("-created_date", 10, 10); +``` + +### Filter + +```javascript +// Simple filter +const pending = await base44.entities.Task.filter({ status: "pending" }); + +// Multiple conditions +const myPending = await base44.entities.Task.filter({ + status: "pending", + assignedTo: userId +}); + +// With sort, limit, skip +const recent = await base44.entities.Task.filter( + { status: "pending" }, + "-created_date", // sort (string: prefix with - for descending) + 5, + 0 +); + +// Select specific fields +const titles = await base44.entities.Task.filter( + { status: "pending" }, + null, + null, + null, + ["id", "title"] +); +``` + +### Get by ID + +```javascript +const task = await base44.entities.Task.get("task-id-123"); +``` + +### Update + +```javascript +// Partial update - only specified fields change +await base44.entities.Task.update("task-id-123", { + status: "completed", + completedAt: new Date().toISOString() +}); +``` + +### Delete + +```javascript +// Single record +await base44.entities.Task.delete("task-id-123"); + +// Multiple records matching query +await base44.entities.Task.deleteMany({ status: "archived" }); +``` + +### Subscribe to Realtime Updates + +```javascript +// Subscribe to all changes on Task entity +const unsubscribe = base44.entities.Task.subscribe((event) => { + console.log(`Task ${event.id} was ${event.type}:`, event.data); + // event.type is "create", "update", or "delete" +}); + +// Later: unsubscribe to stop receiving updates +unsubscribe(); +``` + +**Event structure:** +```javascript +{ + type: "create" | "update" | "delete", + data: { ... }, // the entity data + id: "entity-id", // the affected entity's ID + timestamp: "2024-01-15T10:30:00Z" +} +``` + +## User Entity + +Every app has a built-in `User` entity with special rules: + +- Regular users can only read/update **their own** record +- Cannot create users via `entities.create()` - use `auth.register()` instead +- Service role has full access to all user records + +```javascript +// Get current user's record +const me = await base44.entities.User.get(currentUserId); + +// Service role: get any user +const anyUser = await base44.asServiceRole.entities.User.get(userId); +``` + +## Service Role Access + +For admin-level operations (bypass user permissions): + +```javascript +// Backend only +const allTasks = await base44.asServiceRole.entities.Task.list(); +const allUsers = await base44.asServiceRole.entities.User.list(); +``` + +## Permissions (RLS & FLS) + +Data access is controlled by **Row Level Security (RLS)** and **Field Level Security (FLS)** rules defined in entity schemas. + +1. **Authentication level**: anonymous, authenticated, or service role +2. **RLS rules**: Control which records (rows) users can create/read/update/delete +3. **FLS rules**: Control which fields users can read/write within accessible records + +Operations succeed or fail based on these rules - no partial results. + +RLS and FLS are configured in entity schema files (`base44/entities/*.jsonc`). See [entities-create.md](../../base44-cli/references/entities-create.md#row-level-security-rls) for configuration details. + +**Note:** `asServiceRole` sets the user's role to `"admin"` but does NOT bypass RLS. Your RLS rules must include admin access (e.g., `{ "user_condition": { "role": "admin" } }`) for service role operations to succeed. + +## Type Definitions + +### RealtimeEvent + +```typescript +/** Event types for realtime entity updates. */ +type RealtimeEventType = "create" | "update" | "delete"; + +/** Payload received when a realtime event occurs. */ +interface RealtimeEvent { + /** The type of change that occurred. */ + type: RealtimeEventType; + /** The entity data. */ + data: any; + /** The unique identifier of the affected entity. */ + id: string; + /** ISO 8601 timestamp of when the event occurred. */ + timestamp: string; +} + +/** Callback function invoked when a realtime event occurs. */ +type RealtimeCallback = (event: RealtimeEvent) => void; + +/** Function returned from subscribe, call it to unsubscribe. */ +type Subscription = () => void; +``` + +### EntityHandler + +```typescript +/** Entity handler providing CRUD operations for a specific entity type. */ +interface EntityHandler { + /** Lists records with optional pagination and sorting. */ + list(sort?: string, limit?: number, skip?: number, fields?: string[]): Promise; + + /** Filters records based on a query. */ + filter(query: Record, sort?: string, limit?: number, skip?: number, fields?: string[]): Promise; + + /** Gets a single record by ID. */ + get(id: string): Promise; + + /** Creates a new record. */ + create(data: Record): Promise; + + /** Updates an existing record. */ + update(id: string, data: Record): Promise; + + /** Deletes a single record by ID. */ + delete(id: string): Promise; + + /** Deletes multiple records matching a query. */ + deleteMany(query: Record): Promise; + + /** Creates multiple records in a single request. */ + bulkCreate(data: Record[]): Promise; + + /** Imports records from a file (frontend only). */ + importEntities(file: File): Promise; + + /** Subscribes to realtime updates. Returns unsubscribe function. */ + subscribe(callback: RealtimeCallback): Subscription; +} +``` + +### EntitiesModule + +```typescript +/** Entities module for managing app data. */ +interface EntitiesModule { + /** Access any entity by name dynamically. */ + [entityName: string]: EntityHandler; +} +``` diff --git a/evals/fixtures/existing-app-add-feature/skills/base44-sdk/references/functions.md b/evals/fixtures/existing-app-add-feature/skills/base44-sdk/references/functions.md new file mode 100644 index 0000000..8072465 --- /dev/null +++ b/evals/fixtures/existing-app-add-feature/skills/base44-sdk/references/functions.md @@ -0,0 +1,216 @@ +# Functions Module + +Invoke custom backend functions via `base44.functions`. + +## Contents +- [Method](#method) +- [Invoking Functions](#invoking-functions) (Frontend, File Upload, Service Role, REST API) +- [Writing Backend Functions](#writing-backend-functions) (Basic, Service Role, Secrets, Errors) +- [Setup Requirements](#setup-requirements) +- [Authentication Modes](#authentication-modes) + +## Method + +```javascript +base44.functions.invoke(functionName, data): Promise +``` + +- `functionName`: Name of the backend function +- `data`: Object of parameters (sent as JSON, or multipart if contains File objects) +- Returns: Whatever the function returns + +## Invoking Functions + +### From Frontend + +```javascript +const result = await base44.functions.invoke("processOrder", { + orderId: "order-123", + action: "ship" +}); + +console.log(result); +``` + +### With File Upload + +```javascript +const fileInput = document.querySelector('input[type="file"]'); +const file = fileInput.files[0]; + +// Automatically uses multipart/form-data when File objects present +const result = await base44.functions.invoke("uploadDocument", { + file: file, + category: "invoices" +}); +``` + +### With Service Role (Backend) + +```javascript +// Inside another backend function +const result = await base44.asServiceRole.functions.invoke("adminTask", { + userId: "user-123" +}); +``` + +### Via REST API (curl) + +Functions can be called via HTTP POST to your app domain: + +```bash +curl -X POST "https:///functions/" \ + -H "Content-Type: application/json" \ + -d '{"key": "value"}' +``` + +## Writing Backend Functions + +Backend functions run on Deno. Must export using `Deno.serve()`. + +### Required Directory Structure + +Each function must be in its own subdirectory under `base44/functions/` with a configuration file: + +``` +base44/ + functions/ + process-order/ # kebab-case directory name + function.jsonc # required configuration + index.ts # entry point +``` + +**function.jsonc:** +```jsonc +{ + "name": "process-order", + "entry": "index.ts" +} +``` + +For complete setup and deployment instructions, see [functions-create.md](../../base44-cli/references/functions-create.md) in base44-cli. + +### Basic Structure + +```javascript +// base44/functions/process-order/index.ts +import { createClientFromRequest } from "npm:@base44/sdk"; + +Deno.serve(async (req) => { + // Get authenticated client from request + const base44 = createClientFromRequest(req); + + // Parse input + const { orderId, action } = await req.json(); + + // Your logic here + const order = await base44.entities.Orders.get(orderId); + + // Return response + return Response.json({ + success: true, + order: order + }); +}); +``` + +### With Service Role Access + +```javascript +import { createClientFromRequest } from "npm:@base44/sdk"; + +Deno.serve(async (req) => { + const base44 = createClientFromRequest(req); + + // Check user is authenticated + const user = await base44.auth.me(); + if (!user) { + return Response.json({ error: "Unauthorized" }, { status: 401 }); + } + + // Use service role for admin operations + const allOrders = await base44.asServiceRole.entities.Orders.list(); + + return Response.json({ orders: allOrders }); +}); +``` + +### Using Secrets + +```javascript +Deno.serve(async (req) => { + // Access environment variables (configured in app settings) + const apiKey = Deno.env.get("STRIPE_API_KEY"); + + const response = await fetch("https://api.stripe.com/v1/charges", { + headers: { + "Authorization": `Bearer ${apiKey}` + } + }); + + return Response.json(await response.json()); +}); +``` + +### Error Handling + +```javascript +import { createClientFromRequest } from "npm:@base44/sdk"; + +Deno.serve(async (req) => { + try { + const base44 = createClientFromRequest(req); + const { orderId } = await req.json(); + + const order = await base44.entities.Orders.get(orderId); + if (!order) { + return Response.json( + { error: "Order not found" }, + { status: 404 } + ); + } + + return Response.json({ order }); + + } catch (error) { + return Response.json( + { error: error.message }, + { status: 500 } + ); + } +}); +``` + +## Setup Requirements + +1. Enable Backend Functions in app settings (requires appropriate plan) +2. Create function files in `/functions` folder +3. Configure secrets via app dashboard for API keys + +## Authentication Modes + +| Mode | Context | Permissions | +|------|---------|-------------| +| User | `base44.functions.invoke()` | Runs under calling user's permissions | +| Service Role | `base44.asServiceRole.functions.invoke()` | Admin-level access | + +Inside the function, use `createClientFromRequest(req)` to get a client that inherits the caller's auth context. + +## Type Definitions + +```typescript +/** Functions module for invoking custom backend functions. */ +interface FunctionsModule { + /** + * Invokes a custom backend function by name. + * + * If any parameter is a File object, the request will automatically be + * sent as multipart/form-data. Otherwise, it will be sent as JSON. + * + * @param functionName - The name of the function to invoke. + * @param data - An object containing named parameters for the function. + * @returns Promise resolving to the function's response. + */ + invoke(functionName: string, data: Record): Promise; +} +``` diff --git a/evals/fixtures/existing-app-add-feature/skills/base44-sdk/references/integrations.md b/evals/fixtures/existing-app-add-feature/skills/base44-sdk/references/integrations.md new file mode 100644 index 0000000..f356a18 --- /dev/null +++ b/evals/fixtures/existing-app-add-feature/skills/base44-sdk/references/integrations.md @@ -0,0 +1,377 @@ +# Integrations Module + +Access third-party services via `base44.integrations`. + +## Types of Integrations + +1. **Core/Built-in**: AI, email, file uploads (available by default) +2. **Catalog integrations**: Pre-built connectors from Base44 catalog +3. **Custom integrations**: Your own OpenAPI-based integrations + +## Accessing Integrations + +```javascript +// Core integrations +base44.integrations.Core.FunctionName(params) + +// Custom integrations +base44.integrations.custom.call(slug, operationId, params) +``` + +## Core Integrations + +### InvokeLLM (AI Text Generation) + +Generate text or structured JSON data using AI models. + +```javascript +// Basic prompt - returns string +const response = await base44.integrations.Core.InvokeLLM({ + prompt: "Summarize this text: ..." +}); + +// With internet context (uses Google Search, Maps, News) +const response = await base44.integrations.Core.InvokeLLM({ + prompt: "What's the current weather in NYC?", + add_context_from_internet: true +}); + +// Structured JSON response - returns object +const response = await base44.integrations.Core.InvokeLLM({ + prompt: "Analyze the sentiment of: 'Great product but slow shipping'", + response_json_schema: { + type: "object", + properties: { + sentiment: { type: "string", enum: ["positive", "negative", "mixed"] }, + score: { type: "number", description: "Score from 1-10" }, + key_points: { type: "array", items: { type: "string" } } + } + } +}); +// Returns: { sentiment: "mixed", score: 7, key_points: ["great product", "slow shipping"] } + +// With file attachments (uploaded via UploadFile) +const response = await base44.integrations.Core.InvokeLLM({ + prompt: "Describe what's in this image", + file_urls: ["https://...uploaded_image.png"] +}); +``` + +**Parameters:** +- `prompt` (string, required): The prompt text to send to the model +- `add_context_from_internet` (boolean, optional): If true, uses Google Search/Maps/News for real-time context +- `response_json_schema` (object, optional): JSON schema for structured output +- `file_urls` (string[], optional): URLs of uploaded files for context + +### GenerateImage + +Create AI-generated images from text prompts. + +```javascript +const { url } = await base44.integrations.Core.GenerateImage({ + prompt: "A serene mountain landscape with a lake" +}); +console.log(url); // https://...generated_image.png +``` + +### SendEmail + +Send emails to registered users. Every app gets this integration (no plan upgrade required). + +```javascript +await base44.integrations.Core.SendEmail({ + to: "user@example.com", + subject: "Welcome!", + body: "

Hello

Welcome to our app.

", + from_name: "My App" // optional, defaults to app name +}); +``` + +**Parameters:** +- `to` (string, required): Recipient email address +- `subject` (string, required): Email subject line +- `body` (string, required): Plain text or HTML email body +- `from_name` (string, optional): Sender name displayed to recipient + +**Limitations:** +- 1 credit per email (2 credits with custom domain) + +### UploadFile (Public) + +Upload files to public storage. + +```javascript +const fileInput = document.querySelector('input[type="file"]'); +const { file_url } = await base44.integrations.Core.UploadFile({ + file: fileInput.files[0] +}); +console.log(file_url); // https://...uploaded_file.pdf +``` + +### UploadPrivateFile + +Upload files to private storage that requires a signed URL to access. + +```javascript +const { file_uri } = await base44.integrations.Core.UploadPrivateFile({ + file: fileInput.files[0] +}); +console.log(file_uri); // "private/user123/document.pdf" + +// Create a signed URL to access the file +const { signed_url } = await base44.integrations.Core.CreateFileSignedUrl({ + file_uri, + expires_in: 3600 // URL expires in 1 hour (default: 300 seconds) +}); +console.log(signed_url); // Temporary URL to access the private file +``` + +### CreateFileSignedUrl + +Generate temporary access links for private files. + +```javascript +const { signed_url } = await base44.integrations.Core.CreateFileSignedUrl({ + file_uri: "private/user123/document.pdf", + expires_in: 7200 // 2 hours +}); +``` + +**Parameters:** +- `file_uri` (string, required): URI from UploadPrivateFile +- `expires_in` (number, optional): Expiration time in seconds (default: 300) + +### ExtractDataFromUploadedFile + +Extract structured data from uploaded files using AI. + +```javascript +// First upload the file +const { file_url } = await base44.integrations.Core.UploadFile({ file }); + +// Then extract structured data +const result = await base44.integrations.Core.ExtractDataFromUploadedFile({ + file_url, + json_schema: { + type: "object", + properties: { + invoice_number: { type: "string" }, + total_amount: { type: "number" }, + date: { type: "string" }, + vendor_name: { type: "string" } + } + } +}); +console.log(result); // { invoice_number: "INV-12345", total_amount: 1250.00, ... } +``` + +## Custom Integrations + +Custom integrations allow workspace administrators to connect any external API by importing an OpenAPI specification. Use `base44.integrations.custom.call()` to invoke them. + +### Syntax + +```javascript +const response = await base44.integrations.custom.call( + slug, // Integration identifier (set by admin) + operationId, // Endpoint in "method:path" format + params // Optional: payload, pathParams, queryParams +); +``` + +### Examples + +```javascript +// GET request with query params +const response = await base44.integrations.custom.call( + "my-crm", + "get:/contacts", + { queryParams: { limit: 10, status: "active" } } +); + +// POST request with body +const response = await base44.integrations.custom.call( + "my-crm", + "post:/contacts", + { payload: { name: "John Doe", email: "john@example.com" } } +); + +// Request with path parameters +const response = await base44.integrations.custom.call( + "github", + "post:/repos/{owner}/{repo}/issues", + { + pathParams: { owner: "myorg", repo: "myrepo" }, + payload: { title: "Bug report", body: "Something is broken" } + } +); +``` + +### Response Structure + +```javascript +{ + success: true, // Whether external API returned 2xx + status_code: 200, // HTTP status code + data: { ... } // Response from external API +} +``` + +## Requirements + +- **Core integrations**: Available on all plans +- **Catalog/Custom integrations**: Require Builder plan or higher + +## Type Definitions + +### Core Integration Parameters + +```typescript +/** Parameters for the InvokeLLM function. */ +interface InvokeLLMParams { + /** The prompt text to send to the model. */ + prompt: string; + /** If true, uses Google Search/Maps/News for real-time context. */ + add_context_from_internet?: boolean; + /** JSON schema for structured output. If provided, returns object instead of string. */ + response_json_schema?: object; + /** File URLs (from UploadFile) to provide as context. */ + file_urls?: string[]; +} + +/** Parameters for the GenerateImage function. */ +interface GenerateImageParams { + /** Description of the image to generate. */ + prompt: string; +} + +/** Result from GenerateImage. */ +interface GenerateImageResult { + /** URL of the generated image. */ + url: string; +} + +/** Parameters for the UploadFile function. */ +interface UploadFileParams { + /** The file object to upload. */ + file: File; +} + +/** Result from UploadFile. */ +interface UploadFileResult { + /** URL of the uploaded file. */ + file_url: string; +} + +/** Parameters for the SendEmail function. */ +interface SendEmailParams { + /** Recipient email address. */ + to: string; + /** Email subject line. */ + subject: string; + /** Plain text or HTML email body. */ + body: string; + /** Sender name (defaults to app name). */ + from_name?: string; +} + +/** Parameters for ExtractDataFromUploadedFile. */ +interface ExtractDataFromUploadedFileParams { + /** URL of the uploaded file. */ + file_url: string; + /** JSON schema defining fields to extract. */ + json_schema: object; +} + +/** Parameters for UploadPrivateFile. */ +interface UploadPrivateFileParams { + /** The file object to upload. */ + file: File; +} + +/** Result from UploadPrivateFile. */ +interface UploadPrivateFileResult { + /** URI of the private file (used for signed URLs). */ + file_uri: string; +} + +/** Parameters for CreateFileSignedUrl. */ +interface CreateFileSignedUrlParams { + /** URI from UploadPrivateFile. */ + file_uri: string; + /** Expiration time in seconds (default: 300). */ + expires_in?: number; +} + +/** Result from CreateFileSignedUrl. */ +interface CreateFileSignedUrlResult { + /** Temporary signed URL to access the file. */ + signed_url: string; +} +``` + +### CoreIntegrations + +```typescript +/** Core package containing built-in Base44 integration functions. */ +interface CoreIntegrations { + InvokeLLM(params: InvokeLLMParams): Promise; + GenerateImage(params: GenerateImageParams): Promise; + UploadFile(params: UploadFileParams): Promise; + SendEmail(params: SendEmailParams): Promise; + ExtractDataFromUploadedFile(params: ExtractDataFromUploadedFileParams): Promise; + UploadPrivateFile(params: UploadPrivateFileParams): Promise; + CreateFileSignedUrl(params: CreateFileSignedUrlParams): Promise; +} +``` + +### Custom Integrations + +```typescript +/** Parameters for calling a custom integration endpoint. */ +interface CustomIntegrationCallParams { + /** Request body payload. */ + payload?: Record; + /** Path parameters to substitute in the URL. */ + pathParams?: Record; + /** Query string parameters. */ + queryParams?: Record; + /** Additional headers for this request. */ + headers?: Record; +} + +/** Response from a custom integration call. */ +interface CustomIntegrationCallResponse { + /** Whether the external API returned a 2xx status code. */ + success: boolean; + /** The HTTP status code from the external API. */ + status_code: number; + /** The response data from the external API. */ + data: any; +} + +/** Module for calling custom workspace-level API integrations. */ +interface CustomIntegrationsModule { + /** + * Call a custom integration endpoint. + * @param slug - The integration's unique identifier. + * @param operationId - The operation ID from the OpenAPI spec. + * @param params - Optional payload, pathParams, queryParams, headers. + */ + call(slug: string, operationId: string, params?: CustomIntegrationCallParams): Promise; +} +``` + +### IntegrationsModule + +```typescript +/** Integrations module for calling integration endpoints. */ +type IntegrationsModule = { + /** Core package with built-in integrations. */ + Core: CoreIntegrations; + /** Custom integrations module. */ + custom: CustomIntegrationsModule; + /** Additional integration packages (dynamic). */ + [packageName: string]: any; +}; +``` diff --git a/evals/fixtures/existing-app-add-feature/skills/base44-sdk/references/users.md b/evals/fixtures/existing-app-add-feature/skills/base44-sdk/references/users.md new file mode 100644 index 0000000..4205b06 --- /dev/null +++ b/evals/fixtures/existing-app-add-feature/skills/base44-sdk/references/users.md @@ -0,0 +1,82 @@ +# Users Module + +Invite users to the app via `base44.users`. + +## Contents +- [Methods](#methods) +- [Examples](#examples) (Invite User) +- [Roles](#roles) +- [Notes](#notes) + +## Methods + +| Method | Signature | Description | +|--------|-----------|-------------| +| `inviteUser(user_email, role)` | `Promise` | Invite a user to the app | + +## Examples + +### Invite User + +```javascript +// Invite a user with "user" role +await base44.users.inviteUser("newuser@example.com", "user"); + +// Invite an admin +await base44.users.inviteUser("admin@example.com", "admin"); +``` + +### Invite Multiple Users + +```javascript +const usersToInvite = [ + { email: "user1@example.com", role: "user" }, + { email: "user2@example.com", role: "user" }, + { email: "manager@example.com", role: "admin" } +]; + +for (const user of usersToInvite) { + await base44.users.inviteUser(user.email, user.role); + console.log(`Invited ${user.email} as ${user.role}`); +} +``` + +## Roles + +The `role` parameter must be one of: + +| Role | Description | +|------|-------------| +| `"user"` | Standard user with default permissions | +| `"admin"` | Administrator with elevated permissions | + +**Note:** Only `"user"` and `"admin"` are valid role values. An error will be thrown if you pass any other value. + +## Notes + +- **Email invitation**: The invited user receives an email with a link to join the app +- **Duplicate handling**: Inviting an existing user will re-send the invitation +- **Also available in auth**: `base44.auth.inviteUser()` provides the same functionality +- **Role validation**: Only `"user"` or `"admin"` are accepted + +```javascript +// These are equivalent: +await base44.users.inviteUser("newuser@example.com", "user"); +await base44.auth.inviteUser("newuser@example.com", "user"); +``` + +## Type Definitions + +```typescript +/** Users module for inviting users to the app. */ +interface UsersModule { + /** + * Invite a user to the application. + * @param user_email - User's email address. + * @param role - User's role ('user' or 'admin'). + * @returns Promise resolving when the invitation is sent. + * @throws Error if role is not 'user' or 'admin'. + */ + inviteUser(user_email: string, role: "user" | "admin"): Promise; +} +``` diff --git a/evals/fixtures/fullstack/eval.json b/evals/fixtures/fullstack/eval.json new file mode 100644 index 0000000..0f0bc1a --- /dev/null +++ b/evals/fixtures/fullstack/eval.json @@ -0,0 +1,241 @@ +{ + "$schema": "../../eval.schema.json", + "name": "fullstack", + "description": "Test full-stack scenarios: CRUD app, agent chat, function invocation, email notification", + "prompts": [ + { + "name": "fullstack-crud-app", + "description": "Build a full CRUD interface for an entity", + "prompt": "Create a Task entity schema at base44/entities/task.jsonc with fields: title (string, required), description (string), status (string enum: pending/in_progress/completed, default pending), and due_date (string with date format). Then create a TaskManager component at src/components/TaskManager.jsx that provides full CRUD: list all tasks, create new tasks, update task status, and delete tasks using the Base44 SDK.", + "expectedSkills": [ + "base44-sdk", + "base44-cli" + ], + "checks": [ + { + "type": "file-exists", + "filePath": "base44/entities/task.jsonc", + "description": "base44/entities/task.jsonc exists" + }, + { + "type": "file-exists", + "filePath": "src/components/TaskManager.jsx", + "description": "src/components/TaskManager.jsx exists" + }, + { + "type": "valid-json", + "filePath": "base44/entities/task.jsonc", + "description": "base44/entities/task.jsonc is valid JSON" + }, + { + "type": "entity-config", + "filePath": "base44/entities/task.jsonc", + "target": "file", + "description": "Entity config validates (base44/entities/task.jsonc)" + }, + { + "type": "file-content", + "filePath": "src/components/TaskManager.jsx", + "pattern": "entities\\.Task\\.list\\(|entities\\.Task\\.filter\\(", + "description": "Uses entities.Task.list" + }, + { + "type": "file-content", + "filePath": "src/components/TaskManager.jsx", + "pattern": "entities\\.Task\\.create\\(", + "description": "Uses entities.Task.create" + }, + { + "type": "file-content", + "filePath": "src/components/TaskManager.jsx", + "pattern": "entities\\.Task\\.update\\(", + "description": "Uses entities.Task.update" + }, + { + "type": "file-content", + "filePath": "src/components/TaskManager.jsx", + "pattern": "entities\\.Task\\.delete\\(", + "description": "Uses entities.Task.delete" + }, + { + "type": "file-content", + "filePath": "src/components/TaskManager.jsx", + "pattern": "import.*base44|from ['\"].*base44", + "description": "Imports base44 client" + } + ] + }, + { + "name": "fullstack-agent-chat", + "description": "Build agent chat with entity and conversation", + "prompt": "Create an agent configuration at base44/agents/task_helper.jsonc that has read access to a Task entity and helps users manage their tasks. Then create a TaskChat component at src/components/TaskChat.jsx that lets users chat with this agent using agents.createConversation(), agents.addMessage(), and agents.subscribeToConversation().", + "expectedSkills": [ + "base44-sdk", + "base44-cli" + ], + "checks": [ + { + "type": "file-exists", + "filePath": "base44/agents/task_helper.jsonc", + "description": "base44/agents/task_helper.jsonc exists" + }, + { + "type": "file-exists", + "filePath": "src/components/TaskChat.jsx", + "description": "src/components/TaskChat.jsx exists" + }, + { + "type": "valid-json", + "filePath": "base44/agents/task_helper.jsonc", + "description": "base44/agents/task_helper.jsonc is valid JSON" + }, + { + "type": "agent-config", + "filePath": "base44/agents/task_helper.jsonc", + "target": "file", + "description": "Agent config validates (base44/agents/task_helper.jsonc)" + }, + { + "type": "file-content", + "filePath": "src/components/TaskChat.jsx", + "pattern": "createConversation", + "description": "Matches expected pattern in TaskChat.jsx" + }, + { + "type": "file-content", + "filePath": "src/components/TaskChat.jsx", + "pattern": "addMessage", + "description": "Matches expected pattern in TaskChat.jsx" + }, + { + "type": "file-content", + "filePath": "src/components/TaskChat.jsx", + "pattern": "import.*base44|from ['\"].*base44", + "description": "Imports base44 client" + } + ] + }, + { + "name": "fullstack-function-invoke", + "description": "Create and invoke a backend function", + "prompt": "Create a backend function called 'process-payment' at base44/functions/process-payment/ with function.jsonc and index.ts. The function should accept amount and currency in the request body, process the payment using service role, and return a confirmation. Then create a PaymentForm component at src/components/PaymentForm.jsx that invokes this function using functions.invoke().", + "expectedSkills": [ + "base44-sdk", + "base44-cli" + ], + "checks": [ + { + "type": "file-exists", + "filePath": "base44/functions/process-payment/function.jsonc", + "description": "base44/functions/process-payment/function.jsonc exists" + }, + { + "type": "file-exists", + "filePath": "base44/functions/process-payment/index.ts", + "description": "base44/functions/process-payment/index.ts exists" + }, + { + "type": "file-exists", + "filePath": "src/components/PaymentForm.jsx", + "description": "src/components/PaymentForm.jsx exists" + }, + { + "type": "valid-json", + "filePath": "base44/functions/process-payment/function.jsonc", + "description": "base44/functions/process-payment/function.jsonc is valid JSON" + }, + { + "type": "function-def", + "filePath": "base44/functions/process-payment/function.jsonc", + "target": "file", + "description": "Function config validates (base44/functions/process-payment/function.jsonc)" + }, + { + "type": "file-content", + "filePath": "base44/functions/process-payment/index.ts", + "pattern": "Deno\\.serve", + "description": "Uses Deno.serve()" + }, + { + "type": "file-content", + "filePath": "base44/functions/process-payment/index.ts", + "pattern": "createClientFromRequest", + "description": "Imports base44 client" + }, + { + "type": "file-content", + "filePath": "src/components/PaymentForm.jsx", + "pattern": "functions\\.invoke\\(", + "description": "Uses functions.invoke" + }, + { + "type": "file-content", + "filePath": "src/components/PaymentForm.jsx", + "pattern": "process-payment", + "description": "Matches expected pattern in PaymentForm.jsx" + } + ] + }, + { + "name": "fullstack-email-notify", + "description": "Create email notification via backend function", + "prompt": "Create a backend function called 'send-notification' at base44/functions/send-notification/ that accepts recipient, subject, and message in the request body, and sends an email using integrations.Core.SendEmail via service role. Then create a NotifyUser component at src/components/NotifyUser.jsx that invokes this function using functions.invoke().", + "expectedSkills": [ + "base44-sdk", + "base44-cli" + ], + "checks": [ + { + "type": "file-exists", + "filePath": "base44/functions/send-notification/function.jsonc", + "description": "base44/functions/send-notification/function.jsonc exists" + }, + { + "type": "file-exists", + "filePath": "base44/functions/send-notification/index.ts", + "description": "base44/functions/send-notification/index.ts exists" + }, + { + "type": "file-exists", + "filePath": "src/components/NotifyUser.jsx", + "description": "src/components/NotifyUser.jsx exists" + }, + { + "type": "valid-json", + "filePath": "base44/functions/send-notification/function.jsonc", + "description": "base44/functions/send-notification/function.jsonc is valid JSON" + }, + { + "type": "function-def", + "filePath": "base44/functions/send-notification/function.jsonc", + "target": "file", + "description": "Function config validates (base44/functions/send-notification/function.jsonc)" + }, + { + "type": "file-content", + "filePath": "base44/functions/send-notification/index.ts", + "pattern": "Deno\\.serve", + "description": "Uses Deno.serve()" + }, + { + "type": "file-content", + "filePath": "base44/functions/send-notification/index.ts", + "pattern": "integrations\\.Core\\.SendEmail|SendEmail", + "description": "Matches expected pattern in index.ts" + }, + { + "type": "file-content", + "filePath": "base44/functions/send-notification/index.ts", + "pattern": "asServiceRole", + "description": "Uses asServiceRole for admin access" + }, + { + "type": "file-content", + "filePath": "src/components/NotifyUser.jsx", + "pattern": "functions\\.invoke\\(", + "description": "Uses functions.invoke" + } + ] + } + ] +} diff --git a/evals/fixtures/fullstack/project/.claude/settings.json b/evals/fixtures/fullstack/project/.claude/settings.json new file mode 100644 index 0000000..ab60505 --- /dev/null +++ b/evals/fixtures/fullstack/project/.claude/settings.json @@ -0,0 +1,6 @@ +{ + "permissions": { + "allow": ["Read", "Write", "Edit", "Bash", "Glob", "Grep"] + }, + "skills": ["./skills"] +} diff --git a/evals/fixtures/fullstack/project/AGENTS.md b/evals/fixtures/fullstack/project/AGENTS.md new file mode 100644 index 0000000..6c0f2be --- /dev/null +++ b/evals/fixtures/fullstack/project/AGENTS.md @@ -0,0 +1,25 @@ +IMPORTANT: Prefer retrieval-led reasoning over pre-training-led reasoning. Base44 is NOT Firebase, Supabase, or any other SDK you know. Every API name below is the ONLY correct form. Always verify against this index and the referenced docs. + +[Base44 SDK Docs Index]|root: ./skills/base44-sdk/references +|entities.md: CRUD - create(data), list(), filter(query), get(id), update(id,data), delete(id), subscribe(cb) +|auth.md: Auth - loginViaEmailPassword(), loginWithProvider('google'), me(), register(), logout() +|base44-agents.md: Agents - createConversation({agent_name}), addMessage(conv,{role,content}), subscribeToConversation(id,cb) +|functions.md: Functions - invoke(name, data) +|integrations.md: Core - InvokeLLM({prompt}), SendEmail({to,subject,body}), UploadFile({file}) +|client.md: Client - createClient({appId}), import from "@base44/sdk" +|QUICK_REFERENCE.md: All method signatures + +[Base44 CLI Docs Index]|root: ./skills/base44-cli/references +|entities-create.md: Entity schema - {name:"PascalCase", type:"object", properties:{}, required:[]} +|functions-create.md: Function - function.jsonc {name,entry} + index.ts with Deno.serve + createClientFromRequest +|create.md: Project creation - npx base44 create name -p . +|deploy.md: Deploy - npx base44 deploy -y +|agents-push.md: Agent config - {name:"snake_case", description, instructions, tool_configs:[{entity_name,allowed_operations}|{function_name,description}]} + +[Common Mistakes - NEVER use these hallucinated names] +|SDK: Do NOT use find() → use filter(), findOne() → get(), insert() → create(), remove() → delete(), onChange() → subscribe() +|SDK: Do NOT use signInWithGoogle() → use loginWithProvider('google'), functions.call() → functions.invoke() +|SDK: Do NOT use ai.generate() → use integrations.Core.InvokeLLM({prompt}) +|CLI: Entity names must be PascalCase alphanumeric. File names must be kebab-case.jsonc +|CLI: Agent names must be lowercase with underscores only. Function entry field is "entry" not "entrypoint" +|CLI: Backend functions import from "npm:@base44/sdk" (Deno requires npm: prefix) diff --git a/evals/fixtures/fullstack/project/CLAUDE.md b/evals/fixtures/fullstack/project/CLAUDE.md new file mode 100644 index 0000000..724ce4d --- /dev/null +++ b/evals/fixtures/fullstack/project/CLAUDE.md @@ -0,0 +1,7 @@ +# Base44 Development Guidelines + +When working with Base44 projects, first explore the existing project structure to understand what already exists. Look at existing code, config files, and patterns in use. + +Then consult the AGENTS.md index in the project root for correct API patterns. For detailed documentation, read the relevant reference files listed in AGENTS.md. + +IMPORTANT: Prefer retrieval-led reasoning over pre-training-led reasoning. Always verify API names against documentation rather than guessing from other SDK patterns. diff --git a/evals/fixtures/fullstack/project/base44/config.jsonc b/evals/fixtures/fullstack/project/base44/config.jsonc new file mode 100644 index 0000000..d16dded --- /dev/null +++ b/evals/fixtures/fullstack/project/base44/config.jsonc @@ -0,0 +1,11 @@ +{ + "name": "Fullstack Test App", + "description": "A test application for full-stack Base44 scenarios", + "entitiesDir": "./entities", + "functionsDir": "./functions", + "agentsDir": "./agents", + "site": { + "buildCommand": "npm run build", + "outputDirectory": "dist" + } +} diff --git a/evals/fixtures/fullstack/project/package.json b/evals/fixtures/fullstack/project/package.json new file mode 100644 index 0000000..6abb116 --- /dev/null +++ b/evals/fixtures/fullstack/project/package.json @@ -0,0 +1,20 @@ +{ + "name": "fullstack-test-app", + "version": "0.1.0", + "private": true, + "type": "module", + "scripts": { + "dev": "vite", + "build": "vite build" + }, + "dependencies": { + "@base44/sdk": "^1.0.0", + "react": "^18.2.0", + "react-dom": "^18.2.0" + }, + "devDependencies": { + "@vitejs/plugin-react": "^4.2.1", + "base44": "latest", + "vite": "^5.2.0" + } +} diff --git a/evals/fixtures/fullstack/project/src/App.jsx b/evals/fixtures/fullstack/project/src/App.jsx new file mode 100644 index 0000000..5553fc1 --- /dev/null +++ b/evals/fixtures/fullstack/project/src/App.jsx @@ -0,0 +1,11 @@ +import React from 'react'; + +function App() { + return ( +
+

Auth Test App

+
+ ); +} + +export default App; diff --git a/evals/fixtures/fullstack/project/src/api/base44Client.js b/evals/fixtures/fullstack/project/src/api/base44Client.js new file mode 100644 index 0000000..7887147 --- /dev/null +++ b/evals/fixtures/fullstack/project/src/api/base44Client.js @@ -0,0 +1,5 @@ +import { createClient } from "@base44/sdk"; + +const base44 = createClient({ appId: "test-app" }); + +export default base44; diff --git a/evals/fixtures/fullstack/skills/base44-cli/SKILL.md b/evals/fixtures/fullstack/skills/base44-cli/SKILL.md new file mode 100644 index 0000000..3c33f05 --- /dev/null +++ b/evals/fixtures/fullstack/skills/base44-cli/SKILL.md @@ -0,0 +1,384 @@ +--- +name: base44-cli +description: "The base44 CLI is used for EVERYTHING related to base44 projects: resource configuration (entities, backend functions, ai agents), initialization and actions (resource creation, deployment). This skill is the place for learning about how to configure resources. When you plan or implement a feature, you must learn this skill" +--- + +# Base44 CLI + +Create and manage Base44 apps (projects) using the Base44 CLI tool. + +## ⚡ IMMEDIATE ACTION REQUIRED - Read This First + +This skill activates on ANY mention of "base44" or when a `base44/` folder exists. **DO NOT read documentation files or search the web before acting.** + +**Your first action MUST be:** +1. Check if `base44/config.jsonc` exists in the current directory +2. If **NO** (new project scenario): + - This skill (base44-cli) handles the request + - Guide user through project initialization + - Do NOT activate base44-sdk yet +3. If **YES** (existing project scenario): + - Transfer to base44-sdk skill for implementation + - This skill only handles CLI commands (login, deploy, entities push) + +## Critical: Local Installation Only + +NEVER call `base44` directly. The CLI is installed locally as a dev dependency and must be accessed via a package manager: + +- `npx base44 ` (npm - recommended) +- `yarn base44 ` (yarn) +- `pnpm base44 ` (pnpm) + +WRONG: `base44 login` +RIGHT: `npx base44 login` + +## MANDATORY: Authentication Check at Session Start + +**CRITICAL**: At the very start of every AI session when this skill is activated, you MUST: + +1. **Check authentication status** by running: + ```bash + npx base44 whoami + ``` + +2. **If the user is logged in** (command succeeds and shows an email): + - Continue with the requested task + +3. **If the user is NOT logged in** (command fails or shows an error): + - **STOP immediately** + - **DO NOT proceed** with any CLI operations + - **Ask the user to login manually** by running: + ```bash + npx base44 login + ``` + - Wait for the user to confirm they have logged in before continuing + +**This check is mandatory and must happen before executing any other Base44 CLI commands.** + +## Overview + +The Base44 CLI provides command-line tools for authentication, creating projects, managing entities, and deploying Base44 applications. It is framework-agnostic and works with popular frontend frameworks like Vite, Next.js, and Create React App, Svelte, Vue, and more. + +## When to Use This Skill vs base44-sdk + +**Use base44-cli when:** +- Creating a **NEW** Base44 project from scratch +- Initializing a project in an empty directory +- Directory is missing `base44/config.jsonc` +- User mentions: "create a new project", "initialize project", "setup a project", "start a new Base44 app" +- Deploying, pushing entities, or authenticating via CLI +- Working with CLI commands (`npx base44 ...`) + +**Use base44-sdk when:** +- Building features in an **EXISTING** Base44 project +- `base44/config.jsonc` already exists +- Writing JavaScript/TypeScript code using Base44 SDK +- Implementing functionality, components, or features +- User mentions: "implement", "build a feature", "add functionality", "write code" + +**Skill Dependencies:** +- `base44-cli` is a **prerequisite** for `base44-sdk` in new projects +- If user wants to "create an app" and no Base44 project exists, use `base44-cli` first +- `base44-sdk` assumes a Base44 project is already initialized + +**State Check Logic:** +Before selecting a skill, check: +- IF (user mentions "create/build app" OR "make a project"): + - IF (directory is empty OR no `base44/config.jsonc` exists): + → Use **base44-cli** (project initialization needed) + - ELSE: + → Use **base44-sdk** (project exists, build features) + +## Project Structure + +A Base44 project combines a standard frontend project with a `base44/` configuration folder: + +``` +my-app/ +├── base44/ # Base44 configuration (created by CLI) +│ ├── config.jsonc # Project settings, site config +│ ├── entities/ # Entity schema definitions +│ │ ├── task.jsonc +│ │ └── board.jsonc +│ ├── functions/ # Backend functions (optional) +│ │ └── my-function/ +│ │ ├── function.jsonc +│ │ └── index.ts +│ └── agents/ # Agent configurations (optional) +│ └── support_agent.jsonc +├── src/ # Frontend source code +│ ├── api/ +│ │ └── base44Client.js # Base44 SDK client +│ ├── pages/ +│ ├── components/ +│ └── main.jsx +├── index.html # SPA entry point +├── package.json +└── vite.config.js # Or your framework's config +``` + +**Key files:** +- `base44/config.jsonc` - Project name, description, site build settings +- `base44/entities/*.jsonc` - Data model schemas (see Entity Schema section) +- `base44/agents/*.jsonc` - Agent configurations (optional) +- `src/api/base44Client.js` - Pre-configured SDK client for frontend use + +**config.jsonc example:** +```jsonc +{ + "name": "My App", // Required: project name + "description": "App description", // Optional: project description + "entitiesDir": "./entities", // Optional: default "entities" + "functionsDir": "./functions", // Optional: default "functions" + "agentsDir": "./agents", // Optional: default "agents" + "site": { // Optional: site deployment config + "installCommand": "npm install", // Optional: install dependencies + "buildCommand": "npm run build", // Optional: build command + "serveCommand": "npm run dev", // Optional: local dev server + "outputDirectory": "./dist" // Optional: build output directory + } +} +``` + +**Config properties:** + +| Property | Description | Default | +|----------|-------------|---------| +| `name` | Project name (required) | - | +| `description` | Project description | - | +| `entitiesDir` | Directory for entity schemas | `"entities"` | +| `functionsDir` | Directory for backend functions | `"functions"` | +| `agentsDir` | Directory for agent configs | `"agents"` | +| `site.installCommand` | Command to install dependencies | - | +| `site.buildCommand` | Command to build the project | - | +| `site.serveCommand` | Command to run dev server | - | +| `site.outputDirectory` | Build output directory for deployment | - | + +## Installation + +Install the Base44 CLI as a dev dependency in your project: + +```bash +npm install --save-dev base44 +``` + +**Important:** Never assume or hardcode the `base44` package version. Always install without a version specifier to get the latest version. + +Then run commands using `npx`: + +```bash +npx base44 +``` + +**Note:** All commands in this documentation use `npx base44`. You can also use `yarn base44`, or `pnpm base44` if preferred. + +## Available Commands + +### Authentication + +| Command | Description | Reference | +| --------------- | ----------------------------------------------- | ------------------------------------------- | +| `base44 login` | Authenticate with Base44 using device code flow | [auth-login.md](references/auth-login.md) | +| `base44 logout` | Logout from current device | [auth-logout.md](references/auth-logout.md) | +| `base44 whoami` | Display current authenticated user | [auth-whoami.md](references/auth-whoami.md) | + +### Project Management + +| Command | Description | Reference | +|---------|-------------|-----------| +| `base44 create` | Create a new Base44 project from a template | [create.md](references/create.md) ⚠️ **MUST READ** | +| `base44 link` | Link an existing local project to Base44 | [link.md](references/link.md) | +| `base44 dashboard` | Open the app dashboard in your browser | [dashboard.md](references/dashboard.md) | + +### Deployment + +| Command | Description | Reference | +|---------|-------------|-----------| +| `base44 deploy` | Deploy all resources (entities, functions, site) | [deploy.md](references/deploy.md) | + +### Entity Management + +| Action / Command | Description | Reference | +| ---------------------- | ------------------------------------------- | --------------------------------------------------- | +| Create Entities | Define entities in `base44/entities` folder | [entities-create.md](references/entities-create.md) | +| `base44 entities push` | Push local entities to Base44 | [entities-push.md](references/entities-push.md) | + +#### Entity Schema (Quick Reference) + +ALWAYS follow this exact structure when creating entity files: + +**File naming:** `base44/entities/{kebab-case-name}.jsonc` (e.g., `team-member.jsonc` for `TeamMember`) + +**Schema template:** +```jsonc +{ + "name": "EntityName", + "type": "object", + "properties": { + "field_name": { + "type": "string", + "description": "Field description" + } + }, + "required": ["field_name"] +} +``` + +**Field types:** `string`, `number`, `integer`, `boolean`, `array`, `object`, `binary` +**String formats:** `date`, `date-time`, `time`, `email`, `uri`, `hostname`, `ipv4`, `ipv6`, `uuid`, `file`, `regex`, `richtext` +**For enums:** Add `"enum": ["value1", "value2"]` and optionally `"default": "value1"` +**Entity names:** Must be alphanumeric only (pattern: `/^[a-zA-Z0-9]+$/`) + +For complete documentation, see [entities-create.md](references/entities-create.md). + +### Function Management + +| Action / Command | Description | Reference | +| ------------------------- | --------------------------------------------- | ------------------------------------------------------- | +| Create Functions | Define functions in `base44/functions` folder | [functions-create.md](references/functions-create.md) | +| `base44 functions deploy` | Deploy local functions to Base44 | [functions-deploy.md](references/functions-deploy.md) | + +### Agent Management + +Agents are conversational AI assistants that can interact with users, access your app's entities, and call backend functions. Use these commands to manage agent configurations. + +| Action / Command | Description | Reference | +| ----------------------- | --------------------------------------- | ----------------------------------------------- | +| Create Agents | Define agents in `base44/agents` folder | See Agent Schema below | +| `base44 agents pull` | Pull remote agents to local files | [agents-pull.md](references/agents-pull.md) | +| `base44 agents push` | Push local agents to Base44 | [agents-push.md](references/agents-push.md) | + +**Note:** Agent commands perform full synchronization - pushing replaces all remote agents with local ones, and pulling replaces all local agents with remote ones. + +#### Agent Schema (Quick Reference) + +**File naming:** `base44/agents/{agent_name}.jsonc` (e.g., `support_agent.jsonc`) + +**Schema template:** +```jsonc +{ + "name": "agent_name", + "description": "Brief description of what this agent does", + "instructions": "Detailed instructions for the agent's behavior", + "tool_configs": [ + // Entity tool - gives agent access to entity operations + { "entity_name": "tasks", "allowed_operations": ["read", "create", "update", "delete"] }, + // Backend function tool - gives agent access to a function + { "function_name": "send_email", "description": "Send an email notification" } + ], + "whatsapp_greeting": "Hello! How can I help you today?" +} +``` + +**Naming rules:** +- Agent names must match pattern: `/^[a-z0-9_]+$/` (lowercase alphanumeric with underscores, 1-100 chars) +- Valid: `support_agent`, `order_bot` +- Invalid: `Support-Agent`, `OrderBot` + +**Required fields:** `name`, `description`, `instructions` +**Optional fields:** `tool_configs` (defaults to `[]`), `whatsapp_greeting` + +**Tool config types:** +- **Entity tools**: `entity_name` + `allowed_operations` (array of: `read`, `create`, `update`, `delete`) +- **Backend function tools**: `function_name` + `description` + +### Site Deployment + +| Command | Description | Reference | +| -------------------- | ----------------------------------------- | ------------------------------------------- | +| `base44 site deploy` | Deploy built site files to Base44 hosting | [site-deploy.md](references/site-deploy.md) | + +**SPA only**: Base44 hosting supports Single Page Applications with a single `index.html` entry point. All routes are served from `index.html` (client-side routing). + +## Quick Start + +1. Install the CLI in your project: + ```bash + npm install --save-dev base44 + ``` + +2. Authenticate with Base44: + ```bash + npx base44 login + ``` + +3. Create a new project (ALWAYS provide name and `--path` flag): + ```bash + npx base44 create my-app -p . + ``` + +4. Build and deploy everything: + ```bash + npm run build + npx base44 deploy -y + ``` + +Or deploy individual resources: +- `npx base44 entities push` - Push entities only +- `npx base44 functions deploy` - Deploy functions only +- `npx base44 agents push` - Push agents only +- `npx base44 site deploy -y` - Deploy site only + +## Common Workflows + +### Creating a New Project + +**⚠️ MANDATORY: Before running `base44 create`, you MUST read [create.md](references/create.md) for:** +- **Template selection** - Choose the correct template (`backend-and-client` vs `backend-only`) +- **Correct workflow** - Different templates require different setup steps +- **Common pitfalls** - Avoid folder creation errors that cause failures + +Failure to follow the create.md instructions will result in broken project scaffolding. + +### Linking an Existing Project +```bash +# If you have base44/config.jsonc but no .app.jsonc +npx base44 link --create --name my-app +``` + +### Deploying All Changes +```bash +# Build your project first +npm run build + +# Deploy everything (entities, functions, and site) +npx base44 deploy -y +``` + +### Deploying Individual Resources +```bash +# Push only entities +npx base44 entities push + +# Deploy only functions +npx base44 functions deploy + +# Push only agents +npx base44 agents push + +# Deploy only site +npx base44 site deploy -y +``` + +### Opening the Dashboard +```bash +# Open app dashboard in browser +npx base44 dashboard +``` + +## Authentication + +Most commands require authentication. If you're not logged in, the CLI will automatically prompt you to login. Your session is stored locally and persists across CLI sessions. + +## Troubleshooting + +| Error | Solution | +| --------------------------- | ----------------------------------------------------------------------------------- | +| Not authenticated | Run `npx base44 login` first | +| No entities found | Ensure entities exist in `base44/entities/` directory | +| Entity not recognized | Ensure file uses kebab-case naming (e.g., `team-member.jsonc` not `TeamMember.jsonc`) | +| No functions found | Ensure functions exist in `base44/functions/` with valid `function.jsonc` configs | +| No agents found | Ensure agents exist in `base44/agents/` directory with valid `.jsonc` configs | +| Invalid agent name | Agent names must be lowercase alphanumeric with underscores only | +| No site configuration found | Check that `site.outputDirectory` is configured in project config | +| Site deployment fails | Ensure you ran `npm run build` first and the build succeeded | diff --git a/evals/fixtures/fullstack/skills/base44-cli/references/agents-pull.md b/evals/fixtures/fullstack/skills/base44-cli/references/agents-pull.md new file mode 100644 index 0000000..6a700f0 --- /dev/null +++ b/evals/fixtures/fullstack/skills/base44-cli/references/agents-pull.md @@ -0,0 +1,73 @@ +# base44 agents pull + +Pull AI agent configurations from Base44 to local files. Agents are conversational AI assistants that can interact with users, access your app's entities, and call backend functions. + +## Syntax + +```bash +npx base44 agents pull +``` + +## Authentication + +**Required**: Yes. If not authenticated, you'll be prompted to login first. + +## What It Does + +1. Fetches all agents from Base44 +2. Writes agent files to the `base44/agents/` directory +3. Deletes local agent files that don't exist remotely +4. Reports written and deleted agents + +## Prerequisites + +- Must be run from a Base44 project directory +- Project must be linked to a Base44 app + +## Output + +```bash +$ npx base44 agents pull + +Fetching agents from Base44... +✓ Agents fetched successfully + +Writing agent files... +✓ Agent files written successfully + +Written: support_agent, order_bot +Deleted: old_agent + +Pulled 2 agents to base44/agents +``` + +## Agent Synchronization + +The pull operation synchronizes remote agents to your local files: + +- **Written**: Agent files created or updated from remote +- **Deleted**: Local agent files removed (didn't exist remotely) + +**Warning**: This operation replaces all local agent configurations with remote versions. Any local changes not pushed to Base44 will be overwritten. + +## Error Handling + +If no agents exist on Base44: +```bash +$ npx base44 agents pull +No agents found on Base44 +``` + +## Use Cases + +- Sync agent configurations to a new development machine +- Get the latest agent configurations from your team +- Restore local agent files after accidental deletion +- Start working on an existing project with agents + +## Notes + +- This command syncs agent configurations, not conversation data +- Agent files are stored as `.jsonc` in the `base44/agents/` directory +- The directory location is configurable via `agentsDir` in `config.jsonc` +- Use `base44 agents push` to upload local changes to Base44 diff --git a/evals/fixtures/fullstack/skills/base44-cli/references/agents-push.md b/evals/fixtures/fullstack/skills/base44-cli/references/agents-push.md new file mode 100644 index 0000000..fa2795b --- /dev/null +++ b/evals/fixtures/fullstack/skills/base44-cli/references/agents-push.md @@ -0,0 +1,156 @@ +# base44 agents push + +Push local AI agent configurations to Base44. Agents are conversational AI assistants that can interact with users, access your app's entities, and call backend functions. + +## Syntax + +```bash +npx base44 agents push +``` + +## Authentication + +**Required**: Yes. If not authenticated, you'll be prompted to login first. + +## What It Does + +1. Reads all agent files from the `base44/agents/` directory +2. Validates agent configurations +3. Displays the count of agents to be pushed +4. Uploads agents to the Base44 backend +5. Reports the results: created, updated, and deleted agents + +## Prerequisites + +- Must be run from a Base44 project directory +- Project must have agent definitions in the `base44/agents/` folder + +## Output + +```bash +$ npx base44 agents push + +Found 2 agents to push +Pushing agents to Base44... + +Created: support_agent +Updated: order_bot +Deleted: old_agent + +✓ Agents pushed to Base44 +``` + +## Agent Synchronization + +The push operation synchronizes your local agents with Base44: + +- **Created**: New agents that didn't exist in Base44 +- **Updated**: Existing agents with modified configuration +- **Deleted**: Agents that were removed from your local configuration + +**Warning**: This is a full sync operation. Agents removed locally will be deleted from Base44. + +## Error Handling + +If no agents are found in your project: +```bash +$ npx base44 agents push +No local agents found - this will delete all remote agents +``` + +If an agent has an invalid name: +```bash +$ npx base44 agents push +Error: Agent name must be lowercase alphanumeric with underscores +``` + +## Agent Configuration Schema + +Each agent file should be a `.jsonc` file in `base44/agents/` with this structure: + +```jsonc +{ + "name": "agent_name", // Required: lowercase alphanumeric with underscores, 1-100 chars + "description": "Brief description of what this agent does", // Required: min 1 char + "instructions": "Detailed instructions for the agent's behavior", // Required: min 1 char + "tool_configs": [ // Optional: defaults to [] + // Entity tool - gives agent access to entity operations + { "entity_name": "Task", "allowed_operations": ["read", "create", "update", "delete"] }, + // Backend function tool - gives agent access to a function + { "function_name": "send_email", "description": "Send an email notification" } + ], + "whatsapp_greeting": "Hello! How can I help you today?" // Optional +} +``` + +**Naming rules:** +- **Agent names** must match pattern: `/^[a-z0-9_]+$/` (lowercase alphanumeric with underscores only, 1-100 characters) + - Valid: `support_agent`, `order_bot`, `task_helper` + - Invalid: `Support-Agent`, `OrderBot`, `task helper` +- **Agent file names** must use underscores (matching the agent name) + - Valid: `support_agent.jsonc`, `order_bot.jsonc` + - Invalid: `support-agent.jsonc` (hyphens not allowed) +- **Entity names in `tool_configs`** must use PascalCase (matching the entity's `name` field) + - Valid: `"entity_name": "Task"`, `"entity_name": "TeamMember"` + - Invalid: `"entity_name": "task"`, `"entity_name": "team_member"` + +**Required fields:** +- `name`: Required, must follow naming rules above +- `description`: Required, minimum 1 character +- `instructions`: Required, minimum 1 character +- `tool_configs`: Optional, defaults to empty array +- `whatsapp_greeting`: Optional + +### Common Mistake: Wrong tool_configs Format + +**WRONG** - Do NOT use `tools` with `type` and `entity`: +```jsonc +{ + "name": "my_agent", + "tools": [ // ❌ WRONG + { "type": "entity_query", "entity": "Task" } + ] +} +``` + +**CORRECT** - Use `tool_configs` with `entity_name` and `allowed_operations`: +```jsonc +{ + "name": "my_agent", + "tool_configs": [ // ✅ CORRECT + { "entity_name": "Task", "allowed_operations": ["read"] } + ] +} +``` + +### Best Practices for Agent Instructions + +When giving agents access to entities, be explicit in the instructions about using the tools: + +```jsonc +{ + "name": "support_agent", + "instructions": "You are a helpful support agent.\n\nIMPORTANT: You have access to customer data through entity tools. When users ask about their orders or account:\n1. ALWAYS use the Order entity tool to query their order history\n2. Use the Customer entity tool to look up account details\n3. Analyze the data and provide personalized responses\n\nAlways query the relevant entities first before answering questions about user data.", + "tool_configs": [ + { "entity_name": "Order", "allowed_operations": ["read"] }, + { "entity_name": "Customer", "allowed_operations": ["read"] } + ] +} +``` + +Without explicit instructions to use the entity tools, the agent may not proactively query user data when asked. + +## Use Cases + +- After defining new agents in your project +- When modifying existing agent configurations +- To sync agent changes before testing +- As part of your development workflow when agent behavior changes + +## Notes + +- This command syncs the agent configuration, not conversation data +- Changes are applied to your Base44 project immediately +- Make sure to test agent changes in a development environment first +- Agent definitions are located in the `base44/agents/` directory +- Use `base44 agents pull` to download agents from Base44 diff --git a/evals/fixtures/fullstack/skills/base44-cli/references/auth-login.md b/evals/fixtures/fullstack/skills/base44-cli/references/auth-login.md new file mode 100644 index 0000000..adebd27 --- /dev/null +++ b/evals/fixtures/fullstack/skills/base44-cli/references/auth-login.md @@ -0,0 +1,54 @@ +# base44 login + +Authenticate with Base44 using device code flow. + +## Syntax + +```bash +npx base44 login +``` + +## Authentication + +**Required**: No (this is the login command itself) + +## How It Works + +The login command uses OAuth 2.0 device code flow for authentication: + +1. Generates a device code for authentication +2. Displays a verification code and verification URI +3. Directs you to visit the URI and enter the code +4. Polls for authentication completion (up to device code expiration) +5. Retrieves access and refresh tokens upon successful authentication +6. Fetches and displays your user information +7. Saves authentication data locally with expiration timestamp + +## Interactive Flow + +```bash +$ npx base44 login + +Please visit: https://auth.base44.com/device +Enter code: ABCD-EFGH + +Waiting for authentication... +✓ Successfully authenticated! + +Logged in as: user@example.com +``` + +## Session Management + +- Authentication tokens are stored locally on your device +- Tokens include expiration timestamps +- The session persists across CLI sessions +- Other commands will automatically use your stored credentials +- Use `npx base44 logout` to clear your session +- Use `npx base44 whoami` to check your current authentication status + +## Notes + +- You only need to login once per device +- If your session expires, you'll be prompted to login again when running authenticated commands +- The CLI automatically prompts for login when you run commands that require authentication diff --git a/evals/fixtures/fullstack/skills/base44-cli/references/auth-logout.md b/evals/fixtures/fullstack/skills/base44-cli/references/auth-logout.md new file mode 100644 index 0000000..33c2ddf --- /dev/null +++ b/evals/fixtures/fullstack/skills/base44-cli/references/auth-logout.md @@ -0,0 +1,32 @@ +# base44 logout + +Logout from current device and clear stored authentication data. + +## Syntax + +```bash +npx base44 logout +``` + +## Authentication + +**Required**: No + +## What It Does + +- Deletes stored authentication data from your device +- Clears your local session +- Removes access and refresh tokens + +## Output + +```bash +$ npx base44 logout +Logged out successfully +``` + +## Notes + +- You can logout even if you're not currently logged in (no error) +- After logout, you'll need to run `npx base44 login` again to use authenticated commands +- This only affects the current device; your Base44 account remains active diff --git a/evals/fixtures/fullstack/skills/base44-cli/references/auth-whoami.md b/evals/fixtures/fullstack/skills/base44-cli/references/auth-whoami.md new file mode 100644 index 0000000..abe450c --- /dev/null +++ b/evals/fixtures/fullstack/skills/base44-cli/references/auth-whoami.md @@ -0,0 +1,37 @@ +# base44 whoami + +Display the currently authenticated user. + +## Syntax + +```bash +npx base44 whoami +``` + +## Authentication + +**Required**: Yes. If not authenticated, you'll be prompted to login first. + +## What It Does + +- Reads stored authentication data +- Displays the email of the currently logged-in user + +## Output + +```bash +$ npx base44 whoami +Logged in as: user@example.com +``` + +## Use Cases + +- Verify you're logged in before running other commands +- Check which account you're currently using +- Confirm authentication is working properly +- Useful in scripts or automation to verify credentials + +## Notes + +- If you're not logged in, the command will prompt you to authenticate first +- The email displayed matches your Base44 account email diff --git a/evals/fixtures/fullstack/skills/base44-cli/references/create.md b/evals/fixtures/fullstack/skills/base44-cli/references/create.md new file mode 100644 index 0000000..7580645 --- /dev/null +++ b/evals/fixtures/fullstack/skills/base44-cli/references/create.md @@ -0,0 +1,111 @@ +# base44 create + +Creates a new Base44 project from a template. This command is framework-agnostic and can either scaffold a complete project or add Base44 configuration to an existing project. + +## Critical: Non-Interactive Mode Required + +ALWAYS provide both the project name AND `--path` flag. Without both, the command opens an interactive TUI which agents cannot use properly. + +WRONG: `npx base44 create` +WRONG: `npx base44 create my-app` +RIGHT: `npx base44 create my-app -p ./my-app` + +## Syntax + +```bash +npx base44 create [name] --path [options] +``` + +## Arguments & Options + +| Argument/Option | Description | Required | +|--------|-------------|----------| +| `name` | Project name (positional argument) | Yes* | +| `-p, --path ` | Path where to create the project | Yes* | +| `-t, --template ` | Template ID (see templates below) | No | +| `--deploy` | Build and deploy the site (includes pushing entities) | No | +| `--no-skills` | Skip AI agent skills installation (skills are added by default) | No | + +*Required for non-interactive mode. Both `name` and `--path` must be provided together. + +## Template Selection (CRITICAL - Choose Appropriately) + +**You MUST select the most appropriate template based on user requirements:** + +| Template ID | When to Use | Example Scenarios | +|-------------|-------------|-------------------| +| `backend-and-client` | Creating a NEW full-stack web app from scratch | "Create a task app", "Build me a dashboard", "Make a SaaS app" | +| `backend-only` | Adding Base44 to an EXISTING project OR using a different framework (Next.js, Vue, Svelte, etc.) | "Add Base44 to my project", "I want to use Next.js", "I already have a frontend" | + +**Default Choice:** When the user asks to "create an app" or "build a project" without specifying a particular framework, use `backend-and-client` to provide a complete, production-ready application with Vite + React + Tailwind. + +## The `--path` Flag + +- **For `backend-and-client` template (new projects):** Use a new subfolder path + ```bash + npx base44 create my-app -p ./my-app -t backend-and-client + ``` +- **For `backend-only` template (existing projects):** Use `-p .` in the current directory + ```bash + npx base44 create my-app -p . + ``` + +## Workflow: Using `backend-only` with External Frameworks + +**CRITICAL: The project folder MUST exist BEFORE running `base44 create` with `backend-only`** + +The `backend-only` template only adds Base44 configuration files - it does NOT create a frontend. If you need a frontend with a specific framework: + +```bash +# Step 1: Initialize the frontend project FIRST +npm create vite@latest my-app -- --template react # or vue, svelte, etc. +# OR: npx create-next-app@latest my-app +# OR: any other framework's init command + +# Step 2: Navigate into the created folder +cd my-app + +# Step 3: Install Base44 CLI +npm install --save-dev base44 + +# Step 4: Add Base44 configuration +npx base44 create my-app -p . +``` + +**WARNING:** Do NOT: +- Create an empty folder manually, then try to run `npx create vite` inside it (will fail - folder exists) +- Run `base44 create` with `backend-only` expecting it to create a frontend (it won't) + +**DO:** +- Run the external framework's init command FIRST (it creates its own folder) +- Then run `base44 create` inside that folder with `-p .` + +## Examples + +```bash +# RECOMMENDED: Create full-stack project (for new apps) +npx base44 create my-app -p ./my-app -t backend-and-client + +# Create full-stack and deploy in one step +npx base44 create my-app -p ./my-app -t backend-and-client --deploy + +# Add Base44 to EXISTING project (must be inside the project folder) +npx base44 create my-app -p . + +# Add Base44 to existing project and deploy +npx base44 create my-app -p . --deploy + +# Create without adding AI agent skills +npx base44 create my-app -p . --no-skills +``` + +## What It Does + +1. Applies the selected template to the target path +2. Creates a `base44/` folder with configuration files +3. Registers the project with Base44 backend +4. Creates `base44/.app.jsonc` with the app ID +5. If `--deploy` is used: + - Pushes any entities defined in `base44/entities/` + - Runs install and build commands (for templates with frontend) + - Deploys the site to Base44 hosting diff --git a/evals/fixtures/fullstack/skills/base44-cli/references/dashboard.md b/evals/fixtures/fullstack/skills/base44-cli/references/dashboard.md new file mode 100644 index 0000000..95a534a --- /dev/null +++ b/evals/fixtures/fullstack/skills/base44-cli/references/dashboard.md @@ -0,0 +1,35 @@ +# base44 dashboard + +Opens the Base44 app dashboard in your default web browser. + +## Syntax + +```bash +npx base44 dashboard +``` + +## Options + +This command has no options. + +## What It Does + +1. Reads the project's app ID from `base44/.app.jsonc` +2. Opens the dashboard URL in your default browser + +## Example + +```bash +# Open dashboard for current project +npx base44 dashboard +``` + +## Requirements + +- Must be run from a linked Base44 project directory (contains `base44/.app.jsonc`) +- Must be authenticated (run `npx base44 login` first) + +## Notes + +- The dashboard provides a web interface to manage your app's entities, functions, users, and settings +- If you're not in a project directory, the command will fail with an error diff --git a/evals/fixtures/fullstack/skills/base44-cli/references/deploy.md b/evals/fixtures/fullstack/skills/base44-cli/references/deploy.md new file mode 100644 index 0000000..500526f --- /dev/null +++ b/evals/fixtures/fullstack/skills/base44-cli/references/deploy.md @@ -0,0 +1,86 @@ +# base44 deploy + +Deploys all project resources (entities, functions, and site) to Base44 in a single command. + +## Syntax + +```bash +npx base44 deploy [options] +``` + +## Options + +| Option | Description | +|--------|-------------| +| `-y, --yes` | Skip confirmation prompt | + +## What It Deploys + +The command automatically detects and deploys: + +1. **Entities** - All `.jsonc` files in `base44/entities/` +2. **Functions** - All functions in `base44/functions/` +3. **Agents** - All agent configurations in `base44/agents/` +4. **Site** - Built files from `site.outputDirectory` (if configured) + +## Examples + +```bash +# Interactive mode - shows what will be deployed and asks for confirmation +npx base44 deploy + +# Non-interactive - skip confirmation (for CI/CD or agent use) +npx base44 deploy -y +``` + +## Typical Workflow + +```bash +# 1. Make your changes (entities, functions, frontend code) + +# 2. Build the frontend (if you have one) +npm run build + +# 3. Deploy everything +npx base44 deploy -y +``` + +## What It Does + +1. Reads project configuration from `base44/config.jsonc` +2. Detects available resources (entities, functions, site) +3. Shows a summary of what will be deployed +4. Asks for confirmation (unless `-y` flag is used) +5. Deploys all resources in sequence: + - Pushes entity schemas + - Deploys functions + - Pushes agent configurations + - Uploads site files +6. Displays the dashboard URL and app URL (if site was deployed) + +## Requirements + +- Must be run from a linked Base44 project directory +- Must be authenticated (run `npx base44 login` first) +- For site deployment, must run `npm run build` first + +## Output + +After successful deployment: +- **Dashboard**: Link to your app's management dashboard +- **App URL**: Your deployed site's public URL (if site was included) + +## Notes + +- If no resources are found, the command exits with a message +- Use individual commands (`entities push`, `functions deploy`, `site deploy`) if you only want to deploy specific resources +- The site must be built before deployment - this command does not run `npm run build` for you + +## Related Commands + +| Command | Description | +|---------|-------------| +| `base44 entities push` | Push only entities | +| `base44 functions deploy` | Deploy only functions | +| `base44 agents push` | Push only agents | +| `base44 site deploy` | Deploy only the site | diff --git a/evals/fixtures/fullstack/skills/base44-cli/references/entities-create.md b/evals/fixtures/fullstack/skills/base44-cli/references/entities-create.md new file mode 100644 index 0000000..29b40aa --- /dev/null +++ b/evals/fixtures/fullstack/skills/base44-cli/references/entities-create.md @@ -0,0 +1,552 @@ +# Creating Entities + +Base44 entities are defined locally in your project and then pushed to the Base44 backend. + +## Critical: File Naming + +Entity files MUST use kebab-case naming: `{kebab-case-name}.jsonc` + +| Entity Name | File Name | +|-------------|-----------| +| `Task` | `task.jsonc` | +| `TeamMember` | `team-member.jsonc` | +| `ActivityLog` | `activity-log.jsonc` | + +WRONG: `TeamMember.jsonc`, `teamMember.jsonc` +RIGHT: `team-member.jsonc` + +## Table of Contents + +- [Creating Entities](#creating-entities) + - [Entity Directory](#entity-directory) + - [How to Create an Entity](#how-to-create-an-entity) + - [Entity Schema Structure](#entity-schema-structure) + - [Supported Field Types](#supported-field-types) + - [Field Properties](#field-properties) + - [Complete Example](#complete-example) + - [Naming Conventions](#naming-conventions) + - [Relationships Between Entities](#relationships-between-entities) + - [Row Level Security (RLS)](#row-level-security-rls) + - [Field Level Security (FLS)](#field-level-security-fls) + - [Pushing Entities](#pushing-entities) + +## Entity Directory + +All entity definitions must be placed in the `base44/entities/` folder in your project root. Each entity is defined in its own `.jsonc` file. + +Example structure: +``` +my-app/ + base44/ + entities/ + user.jsonc + product.jsonc + order.jsonc +``` + +## How to Create an Entity + +1. Create a new `.jsonc` file in the `base44/entities/` directory +2. Define your entity schema following the structure below +3. Push the changes to Base44 using the CLI + +## Entity Schema Structure + +Each entity file follows a JSON Schema-like structure: + +```jsonc +{ + "name": "EntityName", // PascalCase entity name + "type": "object", // Always "object" + "properties": { + // Define your fields here + }, + "required": ["field1"] // Array of required field names +} +``` + +### Common Mistake: Nested Schema Property + +**WRONG** - Do NOT wrap properties in a `schema` object: +```jsonc +{ + "name": "Task", + "description": "A task entity", + "schema": { // ❌ WRONG - don't use nested "schema" + "type": "object", + "properties": { ... } + } +} +``` + +**CORRECT** - Put `type` and `properties` at the top level: +```jsonc +{ + "name": "Task", + "description": "A task entity", + "type": "object", // ✅ CORRECT - top level + "properties": { ... } // ✅ CORRECT - top level +} +``` + +This is a common mistake that will cause "Invalid schema: Schema must have a 'type' field" errors when pushing entities. + +## Supported Field Types + +### String + +Basic text field: +```jsonc +{ + "title": { + "type": "string", + "description": "Task title" + } +} +``` + +With format: +```jsonc +{ + "due_date": { + "type": "string", + "format": "date", + "description": "Due date" + } +} +``` + +Available formats: `date`, `date-time`, `time`, `email`, `uri`, `hostname`, `ipv4`, `ipv6`, `uuid`, `file`, `regex`, `richtext` + +### String with Enum + +Constrained to specific values: +```jsonc +{ + "status": { + "type": "string", + "enum": ["todo", "in_progress", "done"], + "default": "todo", + "description": "Current status" + } +} +``` + +### Number + +```jsonc +{ + "position": { + "type": "number", + "description": "Position for ordering" + } +} +``` + +### Integer + +For whole numbers only: +```jsonc +{ + "quantity": { + "type": "integer", + "description": "Item quantity", + "minimum": 0, + "maximum": 1000 + } +} +``` + +### Binary + +For file/blob data: +```jsonc +{ + "attachment": { + "type": "binary", + "description": "File attachment" + } +} +``` + +### Boolean + +```jsonc +{ + "notify_on_change": { + "type": "boolean", + "default": true, + "description": "Enable notifications" + } +} +``` + +### Array of Strings + +```jsonc +{ + "labels": { + "type": "array", + "items": { "type": "string" }, + "description": "Task labels/tags" + } +} +``` + +### Array of Objects + +```jsonc +{ + "attachments": { + "type": "array", + "description": "File attachments", + "items": { + "type": "object", + "properties": { + "name": { "type": "string" }, + "url": { "type": "string" }, + "type": { "type": "string" } + } + } + } +} +``` + +## Field Properties + +| Property | Description | +| ------------- | ---------------------------------------------------------------------------------------- | +| `type` | Data type: `string`, `number`, `integer`, `boolean`, `array`, `object`, `binary` | +| `description` | Human-readable description of the field | +| `enum` | Array of allowed values (for strings) | +| `enumNames` | Human-readable labels for enum values (same order as `enum`) | +| `default` | Default value when not provided | +| `format` | Format hint: `date`, `date-time`, `time`, `email`, `uri`, `hostname`, `ipv4`, `ipv6`, `uuid`, `file`, `regex`, `richtext` | +| `items` | Schema for array items | +| `properties` | Nested properties for object types | +| `$ref` | Reference to another schema definition | +| `minLength` | Minimum string length | +| `maxLength` | Maximum string length | +| `pattern` | Regex pattern for string validation | +| `minimum` | Minimum value for numbers | +| `maximum` | Maximum value for numbers | +| `rls` | Field-level security rules (see Field Level Security section) | + +## Complete Example + +Here's a complete entity definition for a Task: + +```jsonc +{ + "name": "Task", + "type": "object", + "properties": { + "title": { + "type": "string", + "description": "Task title" + }, + "description": { + "type": "string", + "description": "Task description" + }, + "status": { + "type": "string", + "enum": ["todo", "in_progress", "done"], + "default": "todo", + "description": "Current status of the task" + }, + "board_id": { + "type": "string", + "description": "Board this task belongs to" + }, + "assignee_email": { + "type": "string", + "description": "Email of assigned user" + }, + "priority": { + "type": "string", + "enum": ["low", "medium", "high"], + "default": "medium", + "description": "Task priority" + }, + "due_date": { + "type": "string", + "format": "date", + "description": "Due date" + }, + "labels": { + "type": "array", + "items": { "type": "string" }, + "description": "Task labels/tags" + } + }, + "required": ["title"] +} +``` + +## Naming Conventions + +- **Entity name**: Use PascalCase with alphanumeric characters only (e.g., `Task`, `TeamMember`, `ActivityLog`) + - Must match pattern: `/^[a-zA-Z0-9]+$/` + - Valid: `Task`, `TeamMember`, `Order123` + - Invalid: `Team_Member`, `Team-Member`, `Team Member` +- **File name**: Use kebab-case matching the entity (e.g., `task.jsonc`, `team-member.jsonc`, `activity-log.jsonc`) +- **Field names**: Use snake_case (e.g., `board_id`, `user_email`, `due_date`) + +## Relationships Between Entities + +To create relationships between entities, use ID reference fields: + +```jsonc +{ + "board_id": { + "type": "string", + "description": "Board this task belongs to" + }, + "team_id": { + "type": "string", + "description": "Associated team ID" + } +} +``` + +## Row Level Security (RLS) + +Row Level Security (RLS) controls which records users can access based on their identity and attributes. RLS rules are defined per entity inside the `rls` field of the schema. + +**Important:** If no RLS is defined, all records are accessible to all users. + +### RLS Operations + +RLS supports five operations: + +| Operation | Description | +|-----------|-------------| +| `create` | Control who can add new records | +| `read` | Control who can view records | +| `update` | Control who can modify records | +| `delete` | Control who can remove records | +| `write` | Shorthand for `create`, `update`, and `delete` combined | + +### Permission Values + +Each operation accepts one of the following values: + +1. **`true`** - Allow all users (including anonymous/unauthenticated) +2. **`false`** - Block all users +3. **Condition object** - Allow users matching the condition + +### Template Variables + +Use template variables to reference the current user's attributes: + +| Template | Description | +|----------|-------------| +| `{{user.id}}` | The user's ID | +| `{{user.email}}` | The user's email | +| `{{user.role}}` | The user's role | +| `{{user.data.field_name}}` | Custom field from the user's `data` object | + +### Built-in Entity Attributes + +Every entity record has these built-in attributes available for RLS rules: + +| Attribute | Description | +|-----------|-------------| +| `id` | Unique record identifier | +| `created_date` | Timestamp when record was created | +| `updated_date` | Timestamp when record was last updated | +| `created_by` | Email of the user who created the record | + +### Rule Types + +There are two condition types you can use: + +**1. Entity-to-user comparison** - Compare record fields to the current user's values: +```jsonc +{ + "created_by": "{{user.email}}" +} +``` + +**2. User condition check** - Check user properties directly using `user_condition`: +```jsonc +{ + "user_condition": { "role": "admin" } +} +``` + +**Important limitations:** +- `user_condition` only supports **simple equality** (e.g., `{ "role": "admin" }`) +- For `data.*` field comparisons, you can use operators: `$in`, `$nin`, `$ne`, `$all` +- Logical operators `$or`, `$and`, `$nor` are available for combining conditions +- You cannot filter by entity field values directly (e.g., `{"status": "published"}`) +- Only user-related conditions are allowed + +### RLS Examples + +**Owner-only access:** +```jsonc +{ + "created_by": "{{user.email}}" +} +``` + +**Department-based access:** +```jsonc +{ + "data.department": "{{user.data.department}}" +} +``` + +**Admin-only access:** +```jsonc +{ + "user_condition": { "role": "admin" } +} +``` + +**Complete RLS configuration:** +```jsonc +{ + "name": "Task", + "type": "object", + "properties": { + "title": { + "type": "string", + "description": "Task title" + }, + "status": { + "type": "string", + "enum": ["todo", "in_progress", "done"], + "default": "todo" + } + }, + "required": ["title"], + "rls": { + "create": true, + "read": { "created_by": "{{user.email}}" }, + "update": { "created_by": "{{user.email}}" }, + "delete": { "created_by": "{{user.email}}" } + } +} +``` + +### Common RLS Patterns + +**Public create, admin-only management (e.g., contact forms, waitlists):** +```jsonc +{ + "rls": { + "create": true, + "read": { "user_condition": { "role": "admin" } }, + "update": { "user_condition": { "role": "admin" } }, + "delete": { "user_condition": { "role": "admin" } } + } +} +``` + +**Owner-only access:** +```jsonc +{ + "rls": { + "create": true, + "read": { "created_by": "{{user.email}}" }, + "update": { "created_by": "{{user.email}}" }, + "delete": { "created_by": "{{user.email}}" } + } +} +``` + +**Logged-in users only:** +```jsonc +{ + "rls": { + "create": { "user_condition": { "id": "{{user.id}}" } }, + "read": true, + "update": { "created_by": "{{user.email}}" }, + "delete": { "created_by": "{{user.email}}" } + } +} +``` + +### Limitations + +- **user_condition is equality only:** `user_condition` only supports exact match (e.g., `{ "role": "admin" }`) - no operators +- **No comparison operators on user_condition:** `$gt`, `$lt`, `$regex`, `$expr`, `$where` are NOT supported for user conditions +- **No entity field filtering:** Cannot filter by entity field values (e.g., `{"status": "published"}`) +- **No deeply nested templates:** Templates like `{{user.data.profile.department}}` may not work + +**Supported operators:** +- **Logical operators:** `$or`, `$and`, `$nor` for combining multiple conditions +- **Field operators (for `data.*` fields only):** `$in`, `$nin`, `$ne`, `$all` + +### Complex Access Patterns + +For complex access patterns that require multiple conditions (e.g., "owner OR admin"), you have two options: + +1. **Use the Base44 Dashboard UI** - The dashboard allows adding multiple rules per operation with OR logic +2. **Use separate entities** - Split data into multiple entities with different access rules +3. **Use backend functions** - Implement custom access logic in backend functions + +## Field Level Security (FLS) + +Field Level Security allows you to control access to individual fields within an entity. FLS rules are defined within each field's schema using the `rls` property. + +### FLS Operations + +FLS supports the same operations as entity-level RLS: + +| Operation | Description | +|-----------|-------------| +| `create` | Control who can set this field when creating records | +| `read` | Control who can view this field | +| `update` | Control who can modify this field | +| `delete` | Control who can clear this field | +| `write` | Shorthand for `create`, `update`, and `delete` combined | + +### FLS Example + +```jsonc +{ + "name": "Employee", + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "Employee name" + }, + "salary": { + "type": "number", + "description": "Employee salary", + "rls": { + "read": { "user_condition": { "role": "hr" } }, + "update": { "user_condition": { "role": "hr" } } + } + }, + "department": { + "type": "string", + "description": "Department name" + } + }, + "required": ["name"] +} +``` + +In this example, only users with the `hr` role can read or update the `salary` field. All users with access to the entity can read/update other fields. + +### FLS Notes + +- If no field-level RLS is defined, the field inherits the entity-level RLS rules +- FLS rules follow the same condition format as entity-level RLS +- Use FLS for sensitive fields like salary, SSN, or internal notes + +## Pushing Entities + +The `entities push` command will push all entities that exist in the `base44/entities` folder. + +```bash +npx base44 entities push +``` + +For more details on the push command, see [entities-push.md](entities-push.md). diff --git a/evals/fixtures/fullstack/skills/base44-cli/references/entities-push.md b/evals/fixtures/fullstack/skills/base44-cli/references/entities-push.md new file mode 100644 index 0000000..63b92d7 --- /dev/null +++ b/evals/fixtures/fullstack/skills/base44-cli/references/entities-push.md @@ -0,0 +1,71 @@ +# base44 entities push + +Push local entity definitions to Base44. + +## Syntax + +```bash +npx base44 entities push +``` + +## Authentication + +**Required**: Yes. If not authenticated, you'll be prompted to login first. + +## What It Does + +1. Pushes all entities that exist in the `base44/entities` folder +2. Validates that entities exist in the folder +3. Displays the count of entities to be pushed +4. Uploads entities to the Base44 backend +5. Reports the results: created, updated, and deleted entities + +## Prerequisites + +- Must be run from a Base44 project directory +- Project must have entity definitions in the `base44/entities` folder + +## Output + +```bash +$ npx base44 entities push + +Found 3 entities to push +Pushing entities to Base44... + +Created: User, Post +Updated: Comment +Deleted: OldEntity + +✓ Entities pushed successfully +``` + +## Entity Synchronization + +The push operation synchronizes your local entity schema with Base44: + +- **Created**: New entities that didn't exist in Base44 +- **Updated**: Existing entities with modified schema or configuration +- **Deleted**: Entities that were removed from your local configuration + +## Error Handling + +If no entities are found in your project: +```bash +$ npx base44 entities push +No entities found in project +``` + +## Use Cases + +- After defining new entities in your project +- When modifying existing entity schemas +- To sync entity changes before deploying +- As part of your development workflow when data models change + +## Notes + +- This command syncs the entity schema/structure, not the actual data +- Changes are applied to your Base44 project immediately +- Make sure to test entity changes in a development environment first +- Entity definitions are located in the `base44/entities/` directory diff --git a/evals/fixtures/fullstack/skills/base44-cli/references/functions-create.md b/evals/fixtures/fullstack/skills/base44-cli/references/functions-create.md new file mode 100644 index 0000000..c523cdc --- /dev/null +++ b/evals/fixtures/fullstack/skills/base44-cli/references/functions-create.md @@ -0,0 +1,229 @@ +# Creating Functions + +Base44 functions are serverless backend functions that run on Deno. They are defined locally in your project and deployed to the Base44 backend. + +## Function Directory + +All function definitions must be placed in the `base44/functions/` folder in your project. Each function lives in its own subdirectory with a configuration file and entry point. + +Example structure: +``` +my-app/ + base44/ + functions/ + process-order/ + function.jsonc + index.ts + send-notification/ + function.jsonc + index.ts +``` + +## How to Create a Function + +1. Create a new directory in `base44/functions/` with your function name (use kebab-case) +2. Create a `function.jsonc` configuration file in the directory +3. Create the entry point file (e.g., `index.ts`) +4. Deploy the function using the CLI + +## Function Configuration + +Each function requires a `function.jsonc` configuration file: + +```jsonc +{ + "name": "my-function", + "entry": "index.ts" +} +``` + +### Configuration Properties + +| Property | Description | Required | +|----------|-------------|----------| +| `name` | Function name (must match `/^[^.]+$/` - no dots allowed) | Yes | +| `entry` | Entry point file path relative to the function directory (min 1 char) | Yes | + +## Entry Point File + +Functions run on Deno and must export using `Deno.serve()`. Use `npm:` prefix for npm packages. + +```typescript +import { createClientFromRequest } from "npm:@base44/sdk"; + +Deno.serve(async (req) => { + // Get authenticated client from request + const base44 = createClientFromRequest(req); + + // Parse input + const { orderId, action } = await req.json(); + + // Your logic here + const order = await base44.entities.Orders.get(orderId); + + // Return response + return Response.json({ + success: true, + order: order + }); +}); +``` + +### Request Object + +The function receives a standard Deno `Request` object: +- `req.json()` - Parse JSON body +- `req.text()` - Get raw text body +- `req.headers` - Access request headers +- `req.method` - HTTP method + +### Response Object + +Return using `Response.json()` for JSON responses: + +```typescript +// Success response +return Response.json({ data: result }); + +// Error response with status code +return Response.json({ error: "Something went wrong" }, { status: 400 }); + +// Not found +return Response.json({ error: "Order not found" }, { status: 404 }); +``` + +## Complete Example + +### Directory Structure +``` +base44/ + functions/ + process-order/ + function.jsonc + index.ts +``` + +### function.jsonc +```jsonc +{ + "name": "process-order", + "entry": "index.ts" +} +``` + +### index.ts +```typescript +import { createClientFromRequest } from "npm:@base44/sdk"; + +Deno.serve(async (req) => { + try { + const base44 = createClientFromRequest(req); + const { orderId } = await req.json(); + + // Validate input + if (!orderId) { + return Response.json( + { error: "Order ID is required" }, + { status: 400 } + ); + } + + // Fetch and process the order + const order = await base44.entities.Orders.get(orderId); + if (!order) { + return Response.json( + { error: "Order not found" }, + { status: 404 } + ); + } + + return Response.json({ + success: true, + orderId: order.id, + processedAt: new Date().toISOString() + }); + + } catch (error) { + return Response.json( + { error: error.message }, + { status: 500 } + ); + } +}); +``` + +## Using Service Role Access + +For admin-level operations, use `asServiceRole`: + +```typescript +import { createClientFromRequest } from "npm:@base44/sdk"; + +Deno.serve(async (req) => { + const base44 = createClientFromRequest(req); + + // Check user is authenticated + const user = await base44.auth.me(); + if (!user) { + return Response.json({ error: "Unauthorized" }, { status: 401 }); + } + + // Use service role for admin operations + const allOrders = await base44.asServiceRole.entities.Orders.list(); + + return Response.json({ orders: allOrders }); +}); +``` + +## Using Secrets + +Access environment variables configured in the app dashboard: + +```typescript +Deno.serve(async (req) => { + // Access environment variables (configured in app settings) + const apiKey = Deno.env.get("STRIPE_API_KEY"); + + const response = await fetch("https://api.stripe.com/v1/charges", { + headers: { + "Authorization": `Bearer ${apiKey}` + } + }); + + return Response.json(await response.json()); +}); +``` + +## Naming Conventions + +- **Directory name**: Use kebab-case (e.g., `process-order`, `send-notification`) +- **Function name**: Match the directory name, must match pattern `/^[^.]+$/` (no dots allowed) + - Valid: `process-order`, `send_notification`, `myFunction` + - Invalid: `process.order`, `send.notification.v2` +- **Entry file**: Typically `index.ts` or `index.js` + +## Deploying Functions + +After creating your function, deploy it to Base44: + +```bash +npx base44 functions deploy +``` + +For more details on deploying, see [functions-deploy.md](functions-deploy.md). + +## Notes + +- Functions run on Deno runtime, not Node.js +- Use `npm:` prefix for npm packages (e.g., `npm:@base44/sdk`) +- Use `createClientFromRequest(req)` to get a client that inherits the caller's auth context +- Configure secrets via app dashboard for API keys +- Make sure to handle errors gracefully and return appropriate HTTP status codes + +## Common Mistakes + +| Wrong | Correct | Why | +|-------|---------|-----| +| `functions/myFunction.js` (single file) | `functions/my-function/index.ts` + `function.jsonc` | Functions require subdirectory with config | +| `import { ... } from "@base44/sdk"` | `import { ... } from "npm:@base44/sdk"` | Deno requires `npm:` prefix for npm packages | +| `MyFunction` or `myFunction` directory | `my-function` directory | Use kebab-case for directory names | diff --git a/evals/fixtures/fullstack/skills/base44-cli/references/functions-deploy.md b/evals/fixtures/fullstack/skills/base44-cli/references/functions-deploy.md new file mode 100644 index 0000000..f89d796 --- /dev/null +++ b/evals/fixtures/fullstack/skills/base44-cli/references/functions-deploy.md @@ -0,0 +1,78 @@ +# base44 functions deploy + +Deploy local function definitions to Base44. + +## Syntax + +```bash +npx base44 functions deploy +``` + +## Authentication + +**Required**: Yes. If not authenticated, you'll be prompted to login first. + +## What It Does + +1. Scans the `base44/functions/` directory for function definitions +2. Validates that functions exist and have valid configurations +3. Displays the count of functions to be deployed +4. Uploads function code and configuration to Base44 +5. Reports the results: deployed and deleted functions + +## Prerequisites + +- Must be run from a Base44 project directory +- Project must have function definitions in the `base44/functions/` folder +- Each function must have a valid `function.jsonc` config file + +## Output + +```bash +$ npx base44 functions deploy + +Found 2 functions to deploy +Deploying functions to Base44... + +Deployed: process-order, send-notification +Deleted: old-function + +✓ Functions deployed successfully +``` + +## Function Synchronization + +The deploy operation synchronizes your local functions with Base44: + +- **Deployed**: Functions that were created or updated +- **Deleted**: Functions that were removed from your local configuration + +## Error Handling + +If no functions are found in your project: +```bash +$ npx base44 functions deploy +No functions found. Create functions in the 'functions' directory. +``` + +If a function has configuration errors: +```bash +$ npx base44 functions deploy +Function deployment errors: +'my-function' function: Entry point cannot be empty +``` + +## Use Cases + +- After creating new functions in your project +- When modifying existing function code or configuration +- To sync function changes before testing +- As part of your development workflow when backend logic changes + +## Notes + +- This command deploys the function code and configuration +- Changes are applied to your Base44 project immediately +- Make sure to test functions in a development environment first +- Function definitions are located in the `base44/functions/` directory +- For how to create functions, see [functions-create.md](functions-create.md) diff --git a/evals/fixtures/fullstack/skills/base44-cli/references/link.md b/evals/fixtures/fullstack/skills/base44-cli/references/link.md new file mode 100644 index 0000000..0b009ce --- /dev/null +++ b/evals/fixtures/fullstack/skills/base44-cli/references/link.md @@ -0,0 +1,81 @@ +# base44 link + +Links an existing local Base44 project to a Base44 app in the cloud. Use this when you have a `base44/config.jsonc` but haven't connected it to a Base44 app yet. + +## Critical: When to Use Link vs Create + +| Scenario | Command | +|----------|---------| +| Starting fresh, no `base44/` folder | `npx base44 create` | +| Have `base44/config.jsonc` but no `.app.jsonc` | `npx base44 link` | +| Project already linked (has `.app.jsonc`) | Already done, use `deploy` | + +## Syntax + +```bash +npx base44 link [options] +``` + +## Options + +| Option | Description | Required | +|--------|-------------|----------| +| `-c, --create` | Create a new project (skip selection prompt) | No | +| `-n, --name ` | Project name (required when `--create` is used) | With `--create` | +| `-d, --description ` | Project description | No | +| `-p, --projectId ` | Project ID to link to an existing project (skip selection prompt) | No | + +## Non-Interactive Mode + +For CI/CD or agent use: + +**Create a new project:** +```bash +npx base44 link --create --name my-app +``` + +**Link to an existing project:** +```bash +npx base44 link --projectId +``` + +WRONG: `npx base44 link --create` (missing --name) +WRONG: `npx base44 link --create --projectId ` (cannot use both) +RIGHT: `npx base44 link --create --name my-app` +RIGHT: `npx base44 link --projectId ` + +## Examples + +```bash +# Interactive mode - prompts for project details +npx base44 link + +# Non-interactive - create and link in one step +npx base44 link --create --name my-app + +# With description +npx base44 link --create --name my-app --description "My awesome app" + +# Link to a specific existing project by ID +npx base44 link --projectId abc123 +``` + +## What It Does + +1. Finds the `base44/config.jsonc` in the current directory (or parent directories) +2. Verifies no `.app.jsonc` exists (project not already linked) +3. Either: + - Creates a new Base44 app in the cloud (with `--create`), OR + - Links to an existing project (with `--projectId` or interactive selection) +4. Writes the app ID to `base44/.app.jsonc` + +## Requirements + +- Must have `base44/config.jsonc` in the project +- Must NOT have `base44/.app.jsonc` (use `deploy` if already linked) +- Must be authenticated (run `npx base44 login` first) + +## Notes + +- After linking, you can deploy resources with `npx base44 deploy` +- The `.app.jsonc` file should be git-ignored (contains your app ID) diff --git a/evals/fixtures/fullstack/skills/base44-cli/references/rls-examples.md b/evals/fixtures/fullstack/skills/base44-cli/references/rls-examples.md new file mode 100644 index 0000000..7d3ef42 --- /dev/null +++ b/evals/fixtures/fullstack/skills/base44-cli/references/rls-examples.md @@ -0,0 +1,463 @@ +# RLS Examples + +Practical Row-Level Security patterns for common application types. + +**Important:** Base44 RLS supports: +- **Logical operators:** `$or`, `$and`, `$nor` for combining conditions +- **Field operators (for `data.*` fields):** `$in`, `$nin`, `$ne`, `$all` +- **user_condition:** Equality only (no operators) + +## Contents +- [Simple Patterns (JSON Schema)](#simple-patterns-json-schema) +- [Using Operators](#using-operators) +- [Field-Level Security Examples](#field-level-security-examples) +- [Complex Patterns (Dashboard UI or Backend)](#complex-patterns-dashboard-ui-or-backend) +- [Best Practices](#best-practices) + +--- + +## Simple Patterns (JSON Schema) + +These patterns work with the JSON schema RLS format. + +### Todo App - Owner-only access + +Users see and manage only their own tasks. + +```jsonc +{ + "name": "Task", + "type": "object", + "properties": { + "title": { "type": "string" }, + "description": { "type": "string" }, + "completed": { "type": "boolean" }, + "priority": { "type": "string", "enum": ["low", "medium", "high"] }, + "due_date": { "type": "string", "format": "date" } + }, + "rls": { + "create": true, + "read": { "created_by": "{{user.email}}" }, + "update": { "created_by": "{{user.email}}" }, + "delete": { "created_by": "{{user.email}}" } + } +} +``` + +### Contact Form - Public create, admin-only read + +Anyone can submit, only admins can view submissions. + +```jsonc +{ + "name": "ContactSubmission", + "type": "object", + "properties": { + "name": { "type": "string" }, + "email": { "type": "string", "format": "email" }, + "message": { "type": "string" } + }, + "rls": { + "create": true, + "read": { "user_condition": { "role": "admin" } }, + "update": { "user_condition": { "role": "admin" } }, + "delete": { "user_condition": { "role": "admin" } } + } +} +``` + +### User Profile - Self-management + +Users can only access their own profile. + +```jsonc +{ + "name": "UserProfile", + "type": "object", + "properties": { + "name": { "type": "string" }, + "avatar_url": { "type": "string" }, + "bio": { "type": "string" }, + "preferences": { "type": "object" } + }, + "rls": { + "create": true, + "read": { "created_by": "{{user.email}}" }, + "update": { "created_by": "{{user.email}}" }, + "delete": { "created_by": "{{user.email}}" } + } +} +``` + +### Department Data - Same department access + +Users can only see records from their department. + +```jsonc +{ + "name": "DepartmentAnnouncement", + "type": "object", + "properties": { + "title": { "type": "string" }, + "content": { "type": "string" }, + "department": { "type": "string" } + }, + "rls": { + "create": { "user_condition": { "role": "manager" } }, + "read": { "data.department": "{{user.data.department}}" }, + "update": { "user_condition": { "role": "manager" } }, + "delete": { "user_condition": { "role": "admin" } } + } +} +``` + +### Subscription - Admin-managed, user-readable via email field + +```jsonc +{ + "name": "Subscription", + "type": "object", + "properties": { + "user_email": { "type": "string" }, + "tier": { "type": "string", "enum": ["free", "basic", "pro", "enterprise"] }, + "credits": { "type": "number" }, + "renewal_date": { "type": "string", "format": "date" } + }, + "rls": { + "create": { "user_condition": { "role": "admin" } }, + "read": { "data.user_email": "{{user.email}}" }, + "update": { "user_condition": { "role": "admin" } }, + "delete": { "user_condition": { "role": "admin" } } + } +} +``` + +**Note:** This pattern only allows users to read their own subscription. Admins need to use the Dashboard UI to configure additional read access for themselves. + +### Private Data - Owner-only + +```jsonc +{ + "name": "PrivateNotes", + "type": "object", + "properties": { + "title": { "type": "string" }, + "content": { "type": "string" }, + "tags": { "type": "array", "items": { "type": "string" } } + }, + "rls": { + "create": true, + "read": { "created_by": "{{user.email}}" }, + "update": { "created_by": "{{user.email}}" }, + "delete": { "created_by": "{{user.email}}" } + } +} +``` + +### Public Read, Authenticated Write + +Anyone can read, only logged-in users can create/edit their own records. + +```jsonc +{ + "name": "BlogPost", + "type": "object", + "properties": { + "title": { "type": "string" }, + "content": { "type": "string" }, + "author_email": { "type": "string" } + }, + "rls": { + "create": true, + "read": true, + "update": { "created_by": "{{user.email}}" }, + "delete": { "created_by": "{{user.email}}" } + } +} +``` + +--- + +## Using Operators + +### Logical Operators + +Combine multiple conditions using `$or`, `$and`, or `$nor`: + +**Owner OR Admin access:** +```jsonc +{ + "name": "Document", + "type": "object", + "properties": { + "title": { "type": "string" }, + "content": { "type": "string" } + }, + "rls": { + "create": true, + "read": { + "$or": [ + { "created_by": "{{user.email}}" }, + { "user_condition": { "role": "admin" } } + ] + }, + "update": { + "$or": [ + { "created_by": "{{user.email}}" }, + { "user_condition": { "role": "admin" } } + ] + }, + "delete": { "user_condition": { "role": "admin" } } + } +} +``` + +**Multiple roles with $or:** +```jsonc +{ + "rls": { + "read": { + "$or": [ + { "user_condition": { "role": "admin" } }, + { "user_condition": { "role": "manager" } }, + { "user_condition": { "role": "hr" } } + ] + } + } +} +``` + +### Field Operators for data.* Fields + +Use `$in`, `$nin`, `$ne`, `$all` for comparing entity data fields: + +**Access based on tags ($in):** +```jsonc +{ + "rls": { + "read": { + "data.category": { "$in": ["public", "shared"] } + } + } +} +``` + +**Exclude specific statuses ($nin):** +```jsonc +{ + "rls": { + "read": { + "data.status": { "$nin": ["archived", "deleted"] } + } + } +} +``` + +**Not equal ($ne):** +```jsonc +{ + "rls": { + "read": { + "data.visibility": { "$ne": "private" } + } + } +} +``` + +**All tags must match ($all):** +```jsonc +{ + "rls": { + "read": { + "data.required_tags": { "$all": ["approved", "reviewed"] } + } + } +} +``` + +### Combining Logical and Field Operators + +```jsonc +{ + "rls": { + "read": { + "$and": [ + { "data.status": { "$ne": "draft" } }, + { + "$or": [ + { "created_by": "{{user.email}}" }, + { "data.visibility": "public" } + ] + } + ] + } + } +} +``` + +--- + +## Field-Level Security Examples + +Control access to specific fields within an entity. + +### Sensitive Salary Field + +```jsonc +{ + "name": "Employee", + "type": "object", + "properties": { + "name": { "type": "string" }, + "email": { "type": "string", "format": "email" }, + "salary": { + "type": "number", + "description": "Annual salary", + "rls": { + "read": { "user_condition": { "role": "hr" } }, + "write": { "user_condition": { "role": "hr" } } + } + }, + "performance_notes": { + "type": "string", + "description": "Manager notes", + "rls": { + "read": { + "$or": [ + { "user_condition": { "role": "manager" } }, + { "user_condition": { "role": "hr" } } + ] + }, + "write": { "user_condition": { "role": "manager" } } + } + } + } +} +``` + +### Admin-Only Internal Fields + +```jsonc +{ + "name": "Order", + "type": "object", + "properties": { + "order_number": { "type": "string" }, + "total": { "type": "number" }, + "internal_notes": { + "type": "string", + "description": "Internal processing notes", + "rls": { + "read": { "user_condition": { "role": "admin" } }, + "write": { "user_condition": { "role": "admin" } } + } + }, + "profit_margin": { + "type": "number", + "description": "Profit margin percentage", + "rls": { + "read": { "user_condition": { "role": "admin" } }, + "write": false + } + } + } +} +``` + +--- + +## Complex Patterns (Dashboard UI or Backend) + +Some patterns may still require the Dashboard UI or backend functions. + +### Bidirectional Relationships (e.g., Friendships, Matches) + +**Requirement:** Either party in a relationship should have access. + +**Now possible with $or:** +```jsonc +{ + "rls": { + "read": { + "$or": [ + { "data.user_a_email": "{{user.email}}" }, + { "data.user_b_email": "{{user.email}}" } + ] + } + } +} +``` + +**Alternative solutions:** +1. **Entity redesign:** Store two records per relationship (one for each party) +2. **Backend function:** Query with custom logic + +### Complex Business Logic + +**Requirement:** Access depends on multiple entity fields with complex conditions. + +**JSON Schema limitation:** While operators help, very complex business logic may still be hard to express. + +**Solution options:** +1. **Backend function:** Implement custom access logic +2. **Combine simpler rules:** Break complex rules into simpler entity-level and field-level rules + +--- + +## Best Practices + +### Security Strategy + +Use a combination of entity-level RLS and field-level security: + +| Data Type | Approach | Example | +|-----------|----------|---------| +| User-editable | Entity RLS: Owner-only | UserProfile with `created_by` check | +| Sensitive fields | Field-level RLS | Salary field with HR role check | +| Multi-role access | `$or` with user_condition | Admin OR Manager access | +| Conditional access | Field operators | `$in`, `$ne` on data fields | +| Public content | Entity RLS: `read: true` | PublicPost | +| Private content | Entity RLS: Owner-only | PrivateNote | + +### When to Use Each Approach + +| Requirement | Approach | +|-------------|----------| +| Single condition (owner, admin, department) | JSON Schema RLS | +| Multiple OR/AND conditions | JSON Schema RLS with `$or`/`$and` | +| Field value checks with `$in`/`$ne`/etc. | JSON Schema RLS for `data.*` fields | +| Field-level access control | JSON Schema FLS (field-level `rls`) | +| Complex comparison operators (`$gt`, `$lt`) | Backend functions | +| Very complex business logic | Backend functions | + +### Common Role Patterns + +| Role | Typical Access | +|------|----------------| +| `admin` | Full access to all records | +| `moderator` | Read/update access, limited delete | +| `manager` | Department-scoped access | +| `user` | Own records only | + +### Supported Operators Summary + +| Operator | Supported | Notes | +|----------|-----------|-------| +| `$or` | Yes | Combine multiple conditions | +| `$and` | Yes | All conditions must match | +| `$nor` | Yes | None of the conditions match | +| `$in` | Yes | For `data.*` fields only | +| `$nin` | Yes | For `data.*` fields only | +| `$ne` | Yes | For `data.*` fields only | +| `$all` | Yes | For `data.*` fields only | +| `$gt`, `$lt`, `$gte`, `$lte` | No | Use backend functions | +| `$regex` | No | Use backend functions | + +### Limitations Summary + +| Not Supported | Alternative | +|---------------|-------------| +| Operators on `user_condition` | Use equality only for user checks | +| Comparison operators (`$gt`, `$lt`) | Backend functions | +| Regex matching (`$regex`) | Backend functions | +| Cross-entity relationships | Backend functions | diff --git a/evals/fixtures/fullstack/skills/base44-cli/references/site-deploy.md b/evals/fixtures/fullstack/skills/base44-cli/references/site-deploy.md new file mode 100644 index 0000000..929d302 --- /dev/null +++ b/evals/fixtures/fullstack/skills/base44-cli/references/site-deploy.md @@ -0,0 +1,118 @@ +# base44 site deploy + +Deploy built site files to Base44 hosting. + +## Table of Contents + +- [Syntax](#syntax) +- [Authentication](#authentication) +- [Prerequisites](#prerequisites) +- [How It Works](#how-it-works) +- [Interactive Flow](#interactive-flow) +- [Typical Workflow](#typical-workflow) +- [Configuration](#configuration) +- [Error Handling](#error-handling) +- [Use Cases](#use-cases) +- [Notes](#notes) + +## Syntax + +```bash +npx base44 site deploy [options] +``` + +## Options + +| Option | Description | +| ------------ | ------------------------- | +| `-y, --yes` | Skip confirmation prompt | + +Use `-y` flag for non-interactive/automated deployments: + +```bash +npx base44 site deploy -y +``` + +## Authentication + +**Required**: Yes. If not authenticated, you'll be prompted to login first. + +## Prerequisites + +- Must be run from a Base44 project directory +- Project must have `site.outputDirectory` configured in project config +- Site must be built before deploying (run your build command first) +- **SPA only**: Base44 hosting supports Single Page Applications with a single `index.html` entry point. All routes are served from `index.html` (client-side routing). + +## How It Works + +1. Reads project configuration +2. Validates that site configuration exists +3. Prompts for deployment confirmation showing the output directory +4. Creates an archive of site files from the output directory +5. Deploys to Base44 hosting +6. Returns the app URL + +## Interactive Flow + +```bash +$ npx base44 site deploy + +Deploy site from ./dist? (yes/no) yes + +Creating archive... +Uploading to Base44... +Deploying... + +✓ Deployment successful! + +Visit your site at: https://my-app.base44.app +``` + +## Typical Workflow + +```bash +# 1. Build your site using your framework's build command +npm run build + +# 2. Deploy to Base44 +npx base44 site deploy +``` + +## Configuration + +The `site.outputDirectory` in your project configuration should point to where your framework outputs built files: + +- Vite: typically `./dist` +- Next.js: typically `./.next` or `./out` +- Create React App: typically `./build` +- Custom: whatever your build tool outputs to + +## Error Handling + +If site configuration is missing: +```bash +$ npx base44 site deploy +Error: No site configuration found in project +``` + +If you cancel the deployment: +```bash +Deploy site from ./dist? (yes/no) no +Deployment cancelled +``` + +## Use Cases + +- Deploy your site after making changes +- Push new versions of your application +- Deploy after updating content or functionality +- Part of your CI/CD pipeline + +## Notes + +- Always build your site before deploying +- The command deploys whatever is in your output directory +- Make sure your build completed successfully before deploying +- Previous deployments are preserved (versioned) in Base44 +- Deployment is immediate and updates your live site diff --git a/evals/fixtures/fullstack/skills/base44-sdk/SKILL.md b/evals/fixtures/fullstack/skills/base44-sdk/SKILL.md new file mode 100644 index 0000000..01ddd7f --- /dev/null +++ b/evals/fixtures/fullstack/skills/base44-sdk/SKILL.md @@ -0,0 +1,296 @@ +--- +name: base44-sdk +description: "The base44 SDK is the library to communicate with base44 services. In projects, you use it to communicate with remote resources (entities, backend functions, ai agents) and to write backend functions. This skill is the place for learning about available modules and types. When you plan or implement a feature, you must learn this skill" +--- + +# Base44 Coder + +Build apps on the Base44 platform using the Base44 JavaScript SDK. + +## ⚡ IMMEDIATE ACTION REQUIRED - Read This First + +This skill activates on ANY mention of "base44" or when a `base44/` folder exists. **DO NOT read documentation files or search the web before acting.** + +**Your first action MUST be:** +1. Check if `base44/config.jsonc` exists in the current directory +2. If **YES** (existing project scenario): + - This skill (base44-sdk) handles the request + - Implement features using Base44 SDK + - Do NOT use base44-cli unless user explicitly requests CLI commands +3. If **NO** (new project scenario): + - Transfer to base44-cli skill for project initialization + - This skill cannot help until project is initialized + +## When to Use This Skill vs base44-cli + +**Use base44-sdk when:** +- Building features in an **EXISTING** Base44 project +- `base44/config.jsonc` already exists in the project +- Base44 SDK imports are present (`@base44/sdk`) +- Writing JavaScript/TypeScript code using Base44 SDK modules +- Implementing functionality, components, or features +- User mentions: "implement", "build a feature", "add functionality", "write code for" +- User says "create a [type] app" **and** a Base44 project already exists + +**DO NOT USE base44-sdk for:** +- ❌ Initializing new Base44 projects (use `base44-cli` instead) +- ❌ Empty directories without Base44 configuration +- ❌ When user says "create a new Base44 project/app/site" and no project exists +- ❌ CLI commands like `npx base44 create`, `npx base44 deploy`, `npx base44 login` (use `base44-cli`) + +**Skill Dependencies:** +- `base44-sdk` assumes a Base44 project is **already initialized** +- `base44-cli` is a **prerequisite** for `base44-sdk` in new projects +- If user wants to "create an app" and no Base44 project exists, use `base44-cli` first + +**State Check Logic:** +Before selecting this skill, verify: +- IF (user mentions "create/build app" OR "make a project"): + - IF (directory is empty OR no `base44/config.jsonc` exists): + → Use **base44-cli** (project initialization needed) + - ELSE: + → Use **base44-sdk** (project exists, build features) + +## Quick Start + +```javascript +// In Base44-generated apps, base44 client is pre-configured and available + +// CRUD operations +const task = await base44.entities.Task.create({ title: "New task", status: "pending" }); +const tasks = await base44.entities.Task.list(); +await base44.entities.Task.update(task.id, { status: "done" }); + +// Get current user +const user = await base44.auth.me(); +``` + +```javascript +// External apps +import { createClient } from "@base44/sdk"; + +// IMPORTANT: Use 'appId' (NOT 'clientId' or 'id') +const base44 = createClient({ appId: "your-app-id" }); +await base44.auth.loginViaEmailPassword("user@example.com", "password"); +``` + +## ⚠️ CRITICAL: Do Not Hallucinate APIs + +**Before writing ANY Base44 code, verify method names against this table or [QUICK_REFERENCE.md](references/QUICK_REFERENCE.md).** + +Base44 SDK has unique method names. Do NOT assume patterns from Firebase, Supabase, or other SDKs. + +### Authentication - WRONG vs CORRECT + +| ❌ WRONG (hallucinated) | ✅ CORRECT | +|------------------------|-----------| +| `signInWithGoogle()` | `loginWithProvider('google')` | +| `signInWithProvider('google')` | `loginWithProvider('google')` | +| `auth.google()` | `loginWithProvider('google')` | +| `signInWithEmailAndPassword(email, pw)` | `loginViaEmailPassword(email, pw)` | +| `signIn(email, pw)` | `loginViaEmailPassword(email, pw)` | +| `createUser()` / `signUp()` | `register({email, password})` | +| `onAuthStateChanged()` | `me()` (no listener, call when needed) | +| `currentUser` | `await auth.me()` | + +### Functions - WRONG vs CORRECT + +| ❌ WRONG (hallucinated) | ✅ CORRECT | +|------------------------|-----------| +| `functions.call('name', data)` | `functions.invoke('name', data)` | +| `functions.run('name', data)` | `functions.invoke('name', data)` | +| `callFunction('name', data)` | `functions.invoke('name', data)` | +| `httpsCallable('name')(data)` | `functions.invoke('name', data)` | + +### Integrations - WRONG vs CORRECT + +| ❌ WRONG (hallucinated) | ✅ CORRECT | +|------------------------|-----------| +| `ai.generate(prompt)` | `integrations.Core.InvokeLLM({prompt})` | +| `openai.chat(prompt)` | `integrations.Core.InvokeLLM({prompt})` | +| `llm(prompt)` | `integrations.Core.InvokeLLM({prompt})` | +| `sendEmail(to, subject, body)` | `integrations.Core.SendEmail({to, subject, body})` | +| `email.send()` | `integrations.Core.SendEmail({to, subject, body})` | +| `uploadFile(file)` | `integrations.Core.UploadFile({file})` | +| `storage.upload(file)` | `integrations.Core.UploadFile({file})` | + +### Entities - WRONG vs CORRECT + +| ❌ WRONG (hallucinated) | ✅ CORRECT | +|------------------------|-----------| +| `entities.Task.find({...})` | `entities.Task.filter({...})` | +| `entities.Task.findOne(id)` | `entities.Task.get(id)` | +| `entities.Task.insert(data)` | `entities.Task.create(data)` | +| `entities.Task.remove(id)` | `entities.Task.delete(id)` | +| `entities.Task.onChange(cb)` | `entities.Task.subscribe(cb)` | + +## SDK Modules + +| Module | Purpose | Reference | +|--------|---------|-----------| +| `entities` | CRUD operations on data models | [entities.md](references/entities.md) | +| `auth` | Login, register, user management | [auth.md](references/auth.md) | +| `agents` | AI conversations and messages | [base44-agents.md](references/base44-agents.md) | +| `functions` | Backend function invocation | [functions.md](references/functions.md) | +| `integrations` | AI, email, file uploads, custom APIs | [integrations.md](references/integrations.md) | +| `connectors` | OAuth tokens (service role only) | [connectors.md](references/connectors.md) | +| `analytics` | Track custom events and user activity | [analytics.md](references/analytics.md) | +| `appLogs` | Log user activity in app | [app-logs.md](references/app-logs.md) | +| `users` | Invite users to the app | [users.md](references/users.md) | + +For client setup and authentication modes, see [client.md](references/client.md). + +**TypeScript Support:** Each reference file includes a "Type Definitions" section with TypeScript interfaces and types for the module's methods, parameters, and return values. + +## Installation + +Install the Base44 SDK: + +```bash +npm install @base44/sdk +``` + +**Important:** Never assume or hardcode the `@base44/sdk` package version. Always install without a version specifier to get the latest version. + +## Creating a Client (External Apps) + +When creating a client in external apps, **ALWAYS use `appId` as the parameter name**: + +```javascript +import { createClient } from "@base44/sdk"; + +// ✅ CORRECT +const base44 = createClient({ appId: "your-app-id" }); + +// ❌ WRONG - Do NOT use these: +// const base44 = createClient({ clientId: "your-app-id" }); // WRONG +// const base44 = createClient({ id: "your-app-id" }); // WRONG +``` + +**Required parameter:** `appId` (string) - Your Base44 application ID + +**Optional parameters:** +- `token` (string) - Pre-authenticated user token +- `options` (object) - Configuration options + - `options.onError` (function) - Global error handler + +**Example with error handler:** +```javascript +const base44 = createClient({ + appId: "your-app-id", + options: { + onError: (error) => { + console.error("Base44 error:", error); + } + } +}); +``` + +## Module Selection + +**Working with app data?** +- Create/read/update/delete records → `entities` +- Import data from file → `entities.importEntities()` +- Realtime updates → `entities.EntityName.subscribe()` + +**User management?** +- Login/register/logout → `auth` +- Get current user → `auth.me()` +- Update user profile → `auth.updateMe()` +- Invite users → `users.inviteUser()` + +**AI features?** +- Chat with AI agents → `agents` (requires logged-in user) +- Create new conversation → `agents.createConversation()` +- Manage conversations → `agents.getConversations()` +- Generate text/JSON with AI → `integrations.Core.InvokeLLM()` +- Generate images → `integrations.Core.GenerateImage()` + +**Custom backend logic?** +- Run server-side code → `functions.invoke()` +- Need admin access → `base44.asServiceRole.functions.invoke()` + +**External services?** +- Send emails → `integrations.Core.SendEmail()` +- Upload files → `integrations.Core.UploadFile()` +- Custom APIs → `integrations.custom.call()` +- OAuth tokens (Google, Slack) → `connectors` (backend only) + +**Tracking and analytics?** +- Track custom events → `analytics.track()` +- Log page views/activity → `appLogs.logUserInApp()` + +## Common Patterns + +### Filter and Sort Data + +```javascript +const pendingTasks = await base44.entities.Task.filter( + { status: "pending", assignedTo: userId }, // query + "-created_date", // sort (descending) + 10, // limit + 0 // skip +); +``` + +### Protected Routes (check auth) + +```javascript +const user = await base44.auth.me(); +if (!user) { + // Navigate to your custom login page + navigate('/login', { state: { returnTo: window.location.pathname } }); + return; +} +``` + +### Backend Function Call + +```javascript +// Frontend +const result = await base44.functions.invoke("processOrder", { + orderId: "123", + action: "ship" +}); + +// Backend function (Deno) +import { createClientFromRequest } from "npm:@base44/sdk"; + +Deno.serve(async (req) => { + const base44 = createClientFromRequest(req); + const { orderId, action } = await req.json(); + // Process with service role for admin access + const order = await base44.asServiceRole.entities.Orders.get(orderId); + return Response.json({ success: true }); +}); +``` + +### Service Role Access + +Use `asServiceRole` in backend functions for admin-level operations: + +```javascript +// User mode - respects permissions +const myTasks = await base44.entities.Task.list(); + +// Service role - full access (backend only) +const allTasks = await base44.asServiceRole.entities.Task.list(); +const token = await base44.asServiceRole.connectors.getAccessToken("slack"); +``` + +## Frontend vs Backend + +| Capability | Frontend | Backend | +|------------|----------|---------| +| `entities` (user's data) | Yes | Yes | +| `auth` | Yes | Yes | +| `agents` | Yes | Yes | +| `functions.invoke()` | Yes | Yes | +| `integrations` | Yes | Yes | +| `analytics` | Yes | Yes | +| `appLogs` | Yes | Yes | +| `users` | Yes | Yes | +| `asServiceRole.*` | No | Yes | +| `connectors` | No | Yes | + +Backend functions use `Deno.serve()` and `createClientFromRequest(req)` to get a properly authenticated client. diff --git a/evals/fixtures/fullstack/skills/base44-sdk/references/QUICK_REFERENCE.md b/evals/fixtures/fullstack/skills/base44-sdk/references/QUICK_REFERENCE.md new file mode 100644 index 0000000..d357384 --- /dev/null +++ b/evals/fixtures/fullstack/skills/base44-sdk/references/QUICK_REFERENCE.md @@ -0,0 +1,155 @@ +# Base44 SDK Quick Reference + +Compact method signatures for all SDK modules. **Verify against this before writing code.** + +--- + +## Auth (`base44.auth.*`) + +``` +loginViaEmailPassword(email, password, turnstileToken?) → Promise<{access_token, user}> +loginWithProvider('google' | 'microsoft' | 'facebook', fromUrl?) → void +me() → Promise +updateMe(data) → Promise +isAuthenticated() → Promise +logout(redirectUrl?) → void +redirectToLogin(nextUrl) → void # ⚠️ Avoid - prefer custom login UI +register({email, password, turnstile_token?, referral_code?}) → Promise +verifyOtp({email, otpCode}) → Promise +resendOtp(email) → Promise +inviteUser(userEmail, role) → Promise +resetPasswordRequest(email) → Promise +resetPassword({resetToken, newPassword}) → Promise +changePassword({userId, currentPassword, newPassword}) → Promise +setToken(token, saveToStorage?) → void +``` + +--- + +## Entities (`base44.entities.EntityName.*`) + +``` +create(data) → Promise +bulkCreate(dataArray) → Promise +list(sort?, limit?, skip?, fields?) → Promise +filter(query, sort?, limit?, skip?, fields?) → Promise +get(id) → Promise +update(id, data) → Promise +delete(id) → Promise +deleteMany(query) → Promise +importEntities(file) → Promise // frontend only +subscribe(callback) → () => void // returns unsubscribe fn +``` + +**Sort:** Use `-fieldName` for descending (e.g., `-created_date`) + +--- + +## Functions (`base44.functions.*`) + +``` +invoke(functionName, data) → Promise +``` + +**Backend:** Use `base44.asServiceRole.functions.invoke()` for admin access. + +--- + +## Integrations (`base44.integrations.Core.*`) + +``` +InvokeLLM({prompt, add_context_from_internet?, response_json_schema?, file_urls?}) → Promise +GenerateImage({prompt}) → Promise<{url}> +SendEmail({to, subject, body, from_name?}) → Promise +UploadFile({file}) → Promise<{file_url}> +UploadPrivateFile({file}) → Promise<{file_uri}> +CreateFileSignedUrl({file_uri, expires_in?}) → Promise<{signed_url}> +ExtractDataFromUploadedFile({file_url, json_schema}) → Promise +``` + +### Custom Integrations (`base44.integrations.custom.*`) + +``` +call(slug, operationId, {payload?, pathParams?, queryParams?, headers?}?) → Promise<{success, status_code, data}> +``` + +**operationId format:** `"method:/path"` (e.g., `"get:/contacts"`, `"post:/users/{id}"`) + +--- + +## Analytics (`base44.analytics.*`) + +``` +track({eventName, properties?}) → void +``` + +--- + +## App Logs (`base44.appLogs.*`) + +``` +logUserInApp(pageName) → Promise +``` + +--- + +## Users (`base44.users.*`) + +``` +inviteUser(userEmail, role) → Promise // role: 'user' | 'admin' +``` + +--- + +## Connectors (`base44.asServiceRole.connectors.*`) + +**Backend only, service role required.** + +``` +getAccessToken(integrationType) → Promise +``` + +**Types:** `'googlecalendar'`, `'googledrive'`, `'slack'`, `'notion'`, `'salesforce'`, `'hubspot'`, `'linkedin'`, `'tiktok'`, `'github'` + +--- + +## Service Role Access + +**Backend functions only.** Prefix any module with `asServiceRole` for admin access: + +```javascript +base44.asServiceRole.entities.Task.list() +base44.asServiceRole.functions.invoke('name', data) +base44.asServiceRole.connectors.getAccessToken('slack') +``` + +--- + +## Backend Function Template + +```javascript +import { createClientFromRequest } from "@base44/sdk"; + +Deno.serve(async (req) => { + const base44 = createClientFromRequest(req); + const data = await req.json(); + + // User context + const user = await base44.auth.me(); + + // Service role for admin operations + const allRecords = await base44.asServiceRole.entities.Task.list(); + + return Response.json({ success: true }); +}); +``` + +--- + +## Client Initialization (External Apps) + +```javascript +import { createClient } from "@base44/sdk"; + +const base44 = createClient({ appId: "your-app-id" }); // MUST use 'appId' +``` diff --git a/evals/fixtures/fullstack/skills/base44-sdk/references/analytics.md b/evals/fixtures/fullstack/skills/base44-sdk/references/analytics.md new file mode 100644 index 0000000..d1b2c69 --- /dev/null +++ b/evals/fixtures/fullstack/skills/base44-sdk/references/analytics.md @@ -0,0 +1,113 @@ +# Analytics Module + +Track custom events and user activity via `base44.analytics`. + +## Contents +- [Methods](#methods) +- [Examples](#examples) +- [Automatic Tracking](#automatic-tracking) +- [Best Practices](#best-practices) + +## Methods + +| Method | Signature | Description | +|--------|-----------|-------------| +| `track(params)` | `void` | Track a custom event | + +## Examples + +### Track Custom Event + +```javascript +// Track a simple event +base44.analytics.track({ + eventName: "button_clicked" +}); + +// Track event with properties +base44.analytics.track({ + eventName: "purchase_completed", + properties: { + product_id: "prod-123", + amount: 99.99, + currency: "USD" + } +}); +``` + +### Track User Actions + +```javascript +// Track page view +base44.analytics.track({ + eventName: "page_view", + properties: { + page: "/dashboard", + referrer: document.referrer + } +}); + +// Track feature usage +base44.analytics.track({ + eventName: "feature_used", + properties: { + feature: "export_data", + format: "csv" + } +}); +``` + +## Automatic Tracking + +The analytics module automatically tracks: +- **Initialization events**: When the app loads +- **Heartbeat events**: Periodic activity signals +- **Session duration**: Time spent in the app + +These internal events help measure user engagement without manual instrumentation. + +## Best Practices + +1. **Use descriptive event names**: `order_completed` instead of `click` +2. **Include relevant properties**: Add context that helps analyze the event +3. **Be consistent**: Use the same event names and property keys across your app +4. **Don't track sensitive data**: Avoid PII in event properties + +```javascript +// Good: Descriptive with relevant properties +base44.analytics.track({ + eventName: "subscription_started", + properties: { + plan: "pro", + billing_cycle: "annual" + } +}); + +// Avoid: Vague event name, no context +base44.analytics.track({ + eventName: "click" +}); +``` + +## Type Definitions + +```typescript +/** Properties that can be attached to a tracked event. */ +type TrackEventProperties = { + [key: string]: string | number | boolean | null | undefined; +}; + +/** Parameters for the track() method. */ +interface TrackEventParams { + /** The name of the event to track. */ + eventName: string; + /** Optional properties to attach to the event. */ + properties?: TrackEventProperties; +} + +/** The analytics module interface. */ +interface AnalyticsModule { + /** Track a custom event with optional properties. */ + track(params: TrackEventParams): void; +} +``` diff --git a/evals/fixtures/fullstack/skills/base44-sdk/references/app-logs.md b/evals/fixtures/fullstack/skills/base44-sdk/references/app-logs.md new file mode 100644 index 0000000..0439805 --- /dev/null +++ b/evals/fixtures/fullstack/skills/base44-sdk/references/app-logs.md @@ -0,0 +1,78 @@ +# App Logs Module + +Log user activity in your app via `base44.appLogs`. + +## Contents +- [Methods](#methods) +- [Examples](#examples) +- [Use Cases](#use-cases) + +## Methods + +| Method | Signature | Description | +|--------|-----------|-------------| +| `logUserInApp(pageName)` | `Promise` | Log user activity on a page | + +## Examples + +### Log User Activity + +```javascript +// Log when user visits a page +await base44.appLogs.logUserInApp("dashboard"); + +// Log specific page visits +await base44.appLogs.logUserInApp("settings"); +await base44.appLogs.logUserInApp("profile"); + +// Log feature usage +await base44.appLogs.logUserInApp("export-button-click"); +``` + +The page name doesn't have to be an actual page - it can be any string you want to track. + +## Use Cases + +### Track Page Views in React + +```javascript +// Log page views on route change +useEffect(() => { + base44.appLogs.logUserInApp(window.location.pathname); +}, [location.pathname]); +``` + +### Track Feature Usage + +```javascript +// Log when user uses specific features +function handleExport() { + base44.appLogs.logUserInApp("export-data"); + // ... export logic +} + +function handleSettingsChange() { + base44.appLogs.logUserInApp("settings-updated"); + // ... save settings +} +``` + +## Notes + +- Logs appear in the Analytics page of your app dashboard +- App logs track page-level and feature-level activity +- Use `analytics.track()` for custom events with properties, `appLogs.logUserInApp()` for simple page/feature tracking + +## Type Definitions + +```typescript +/** App Logs module for tracking and analyzing app usage. */ +interface AppLogsModule { + /** + * Log user activity in the app. + * @param pageName - Name of the page or section being visited. + * @returns Promise that resolves when the log is recorded. + */ + logUserInApp(pageName: string): Promise; +} +``` diff --git a/evals/fixtures/fullstack/skills/base44-sdk/references/auth.md b/evals/fixtures/fullstack/skills/base44-sdk/references/auth.md new file mode 100644 index 0000000..5195b3a --- /dev/null +++ b/evals/fixtures/fullstack/skills/base44-sdk/references/auth.md @@ -0,0 +1,761 @@ +# Auth Module + +User authentication, registration, and session management via `base44.auth`. + +## Contents +- [TypeScript Types](#typescript-types) +- [Methods](#methods) +- [Examples](#examples) +- [Error Handling](#error-handling) +- [Auth Providers](#auth-providers) +- [Environment Availability](#environment-availability) +- [App Visibility](#app-visibility) +- [Limitations](#limitations) + +--- + +## TypeScript Types + +### User Interface +```typescript +interface User { + id: string; + created_date: string; + updated_date: string; + email: string; + full_name: string | null; + disabled: boolean | null; + is_verified: boolean; + app_id: string; + is_service: boolean; + role: string; + [key: string]: any; // Custom schema fields +} +``` + +### LoginResponse Interface +```typescript +interface LoginResponse { + access_token: string; // JWT token + user: User; // Complete user object +} +``` + +### Parameter Interfaces + +#### RegisterParams +```typescript +interface RegisterParams { + email: string; // Required + password: string; // Required + turnstile_token?: string | null; // Optional: Cloudflare Turnstile for bot protection + referral_code?: string | null; // Optional: Referral code +} +``` + +#### VerifyOtpParams +```typescript +interface VerifyOtpParams { + email: string; // User's email + otpCode: string; // OTP code from email +} +``` + +#### ResetPasswordParams +```typescript +interface ResetPasswordParams { + resetToken: string; // Token from password reset email + newPassword: string; // New password to set +} +``` + +#### ChangePasswordParams +```typescript +interface ChangePasswordParams { + userId: string; // User ID + currentPassword: string; // Current password for verification + newPassword: string; // New password to set +} +``` + +### Provider Type +```typescript +type Provider = 'google' | 'microsoft' | 'facebook'; +``` + +--- + +## Methods + +### Module Interface +```typescript +interface AuthModule { + // User Info + me(): Promise; + updateMe(data: Partial>): Promise; + isAuthenticated(): Promise; + + // Login/Logout + loginViaEmailPassword(email: string, password: string, turnstileToken?: string): Promise; + loginWithProvider(provider: Provider, fromUrl?: string): void; + logout(redirectUrl?: string): void; + redirectToLogin(nextUrl: string): void; + + // Token Management + setToken(token: string, saveToStorage?: boolean): void; + + // Registration + register(params: RegisterParams): Promise; + verifyOtp(params: VerifyOtpParams): Promise; + resendOtp(email: string): Promise; + + // User Management + inviteUser(userEmail: string, role: string): Promise; + + // Password Management + resetPasswordRequest(email: string): Promise; + resetPassword(params: ResetPasswordParams): Promise; + changePassword(params: ChangePasswordParams): Promise; +} +``` + +### Method Reference Table + +| Method | Parameters | Return Type | Description | +|--------|-----------|-------------|-------------| +| `register()` | `params: RegisterParams` | `Promise` | Create new user account | +| `loginViaEmailPassword()` | `email: string, password: string, turnstileToken?: string` | `Promise` | Authenticate with email/password | +| `loginWithProvider()` | `provider: Provider, fromUrl?: string` | `void` | Initiate OAuth login flow | +| `me()` | None | `Promise` | Get current authenticated user | +| `updateMe()` | `data: Partial` | `Promise` | Update current user's profile | +| `logout()` | `redirectUrl?: string` | `void` | Clear session, optionally redirect | +| `redirectToLogin()` | `nextUrl: string` | `void` | ⚠️ **Avoid** - Prefer custom login UI with `loginViaEmailPassword()` or `loginWithProvider()` | +| `isAuthenticated()` | None | `Promise` | Check if user is logged in | +| `setToken()` | `token: string, saveToStorage?: boolean` | `void` | Manually set auth token | +| `inviteUser()` | `userEmail: string, role: string` | `Promise` | Send invitation email | +| `verifyOtp()` | `params: VerifyOtpParams` | `Promise` | Verify OTP code | +| `resendOtp()` | `email: string` | `Promise` | Resend OTP code | +| `resetPasswordRequest()` | `email: string` | `Promise` | Request password reset | +| `resetPassword()` | `params: ResetPasswordParams` | `Promise` | Reset password with token | +| `changePassword()` | `params: ChangePasswordParams` | `Promise` | Change user password | + +--- + +## Examples + +### Register New User (Complete Flow) + +Registration requires email verification before login. Complete flow: + +1. **Register** - Create the user account +2. **Verification email sent** - User receives an OTP code +3. **Verify OTP** - User enters code to verify email +4. **Login** - User can now log in + +```javascript +try { + // Step 1: Register the user + await base44.auth.register({ + email: "user@example.com", + password: "securePassword123", + referral_code: "OPTIONAL_CODE", // optional + turnstile_token: "CAPTCHA_TOKEN" // optional, for bot protection + }); + console.log('Registration successful. Check email for OTP code.'); + + // Step 2: User receives email with OTP code (e.g., "123456") + + // Step 3: Verify the OTP code + await base44.auth.verifyOtp({ + email: "user@example.com", + otpCode: "123456" // code from verification email + }); + console.log('Email verified successfully.'); + + // Step 4: Now the user can log in + const loginResponse = await base44.auth.loginViaEmailPassword( + "user@example.com", + "securePassword123" + ); + console.log('Login successful:', loginResponse.user); + +} catch (error) { + console.error('Registration flow failed:', error.message); + // Handle specific errors (see Error Handling section) +} +``` + +> **Important**: Users cannot log in until they complete OTP verification. Attempting to call `loginViaEmailPassword` before verification will fail. + +### Login with Email/Password + +```javascript +try { + const response = await base44.auth.loginViaEmailPassword( + "user@example.com", + "password123", + turnstileToken // optional: for bot protection + ); + + console.log('Login successful'); + console.log('User:', response.user); + console.log('Token:', response.access_token); + + // JWT is automatically stored for subsequent requests + +} catch (error) { + console.error('Login failed:', error.message); + if (error.status === 401) { + console.error('Invalid credentials'); + } else if (error.status === 403) { + console.error('Email not verified. Please check your email for OTP.'); + } +} +``` + +### Login with OAuth Provider + +```javascript +// Redirect to Google OAuth +base44.auth.loginWithProvider('google'); + +// Redirect to Google OAuth and return to current page after +base44.auth.loginWithProvider('google', window.location.href); + +// Supported providers: 'google', 'microsoft', 'facebook' +base44.auth.loginWithProvider('microsoft'); +``` + +### Get Current User + +```javascript +try { + const user = await base44.auth.me(); + + if (user) { + console.log('User ID:', user.id); + console.log('Email:', user.email); + console.log('Name:', user.full_name); + console.log('Role:', user.role); + console.log('Verified:', user.is_verified); + } else { + console.log('User not authenticated'); + } + +} catch (error) { + console.error('Failed to fetch user:', error.message); + if (error.status === 401) { + // Token expired or invalid - navigate to your custom login page + navigate('/login'); + } +} +``` + +### Update User Profile + +```javascript +try { + const updatedUser = await base44.auth.updateMe({ + full_name: "John Doe", + // Custom schema fields can be updated here + phone: "+1234567890", + preferences: { theme: "dark" } + }); + + console.log('Profile updated:', updatedUser); + +} catch (error) { + console.error('Profile update failed:', error.message); + if (error.status === 400) { + console.error('Invalid data provided'); + } else if (error.status === 401) { + console.error('Authentication required'); + navigate('/login'); + } +} +``` + +### Check Authentication Status + +```javascript +try { + const isLoggedIn = await base44.auth.isAuthenticated(); + + if (isLoggedIn) { + // Show authenticated UI + console.log('User is authenticated'); + } else { + // Show login button + console.log('User is not authenticated'); + } + +} catch (error) { + console.error('Failed to check authentication:', error.message); + // On error, treat as not authenticated +} +``` + +### Logout + +```javascript +// Simple logout +base44.auth.logout(); + +// Logout and redirect to goodbye page +base44.auth.logout("/goodbye"); + +// Logout and redirect to homepage +base44.auth.logout("/"); +``` + +### Protected Route Pattern + +```javascript +// Using a navigation function (e.g., React Router's useNavigate, Next.js router) +async function requireAuth(navigate) { + try { + const user = await base44.auth.me(); + + if (!user) { + // Navigate to your custom login page + navigate('/login', { state: { returnTo: window.location.pathname } }); + return null; + } + + return user; + + } catch (error) { + console.error('Authentication check failed:', error.message); + navigate('/login', { state: { returnTo: window.location.pathname } }); + return null; + } +} + +// Usage in your app +async function loadProtectedPage(navigate) { + const user = await requireAuth(navigate); + if (!user) { + // Will navigate to login + return; + } + + // Continue with authenticated logic + console.log('Welcome,', user.full_name); +} +``` + +### Set Authentication Token + +```javascript +// SECURITY WARNING: Never hardcode tokens or expose them in client code +// Tokens should only be received from secure authentication flows + +// Set token and save to localStorage (default) +base44.auth.setToken(receivedToken); + +// Set token without saving to localStorage (temporary session) +base44.auth.setToken(receivedToken, false); + +// Verify token was set +try { + const isAuthenticated = await base44.auth.isAuthenticated(); + if (!isAuthenticated) { + console.error('Token validation failed'); + } +} catch (error) { + console.error('Failed to set token:', error.message); +} +``` + +### Invite User to Application + +```javascript +try { + // Note: Typically requires admin privileges + const response = await base44.auth.inviteUser( + "newuser@example.com", + "user" // or "admin" + ); + + console.log('Invitation sent successfully'); + +} catch (error) { + console.error('Failed to invite user:', error.message); + if (error.status === 403) { + console.error('Insufficient permissions to invite users'); + } else if (error.status === 400) { + console.error('Invalid email or role'); + } +} +``` + +### OTP Verification + +```javascript +try { + // Verify OTP code sent to user's email + await base44.auth.verifyOtp({ + email: "user@example.com", + otpCode: "123456" + }); + + console.log('OTP verified successfully'); + +} catch (error) { + console.error('OTP verification failed:', error.message); + if (error.status === 400) { + console.error('Invalid or expired OTP code'); + } else if (error.status === 429) { + console.error('Too many attempts. Please try again later.'); + } +} + +// Resend OTP if needed +try { + await base44.auth.resendOtp("user@example.com"); + console.log('OTP resent successfully'); + +} catch (error) { + console.error('Failed to resend OTP:', error.message); + if (error.status === 429) { + console.error('Too many requests. Please wait before trying again.'); + } +} +``` + +### Password Reset Flow + +```javascript +// Step 1: Request password reset +try { + await base44.auth.resetPasswordRequest("user@example.com"); + console.log('Password reset email sent. Check your inbox.'); + +} catch (error) { + console.error('Password reset request failed:', error.message); + if (error.status === 429) { + console.error('Too many requests. Please try again later.'); + } + // Note: For security, don't reveal if email exists +} + +// Step 2: Reset password with token from email +try { + await base44.auth.resetPassword({ + resetToken: "token-from-email", + newPassword: "newSecurePassword123" + }); + + console.log('Password reset successfully. You can now log in.'); + +} catch (error) { + console.error('Password reset failed:', error.message); + if (error.status === 400) { + console.error('Invalid or expired reset token'); + } else if (error.status === 422) { + console.error('Password does not meet requirements'); + } +} +``` + +### Change Password + +```javascript +try { + const currentUser = await base44.auth.me(); + + if (!currentUser) { + throw new Error('User must be authenticated to change password'); + } + + await base44.auth.changePassword({ + userId: currentUser.id, + currentPassword: "oldPassword123", + newPassword: "newSecurePassword456" + }); + + console.log('Password changed successfully'); + +} catch (error) { + console.error('Password change failed:', error.message); + if (error.status === 401) { + console.error('Current password is incorrect'); + } else if (error.status === 422) { + console.error('New password does not meet requirements'); + } else if (error.status === 403) { + console.error('Not authorized to change this password'); + } +} +``` + +--- + +## Error Handling + +### Common Error Scenarios + +The auth module can throw various errors. Here are common scenarios and how to handle them: + +#### Authentication Errors (401/403) +```javascript +try { + const user = await base44.auth.me(); +} catch (error) { + if (error.status === 401) { + // Token expired or invalid - navigate to your custom login page + navigate('/login'); + } else if (error.status === 403) { + // Email not verified or insufficient permissions + console.error('Access denied:', error.message); + } +} +``` + +#### Validation Errors (400/422) +```javascript +try { + await base44.auth.register({ + email: "invalid-email", + password: "weak" + }); +} catch (error) { + if (error.status === 400) { + console.error('Invalid input:', error.message); + // Handle validation errors + } else if (error.status === 422) { + console.error('Data validation failed:', error.details); + } +} +``` + +#### Rate Limiting (429) +```javascript +try { + await base44.auth.resendOtp("user@example.com"); +} catch (error) { + if (error.status === 429) { + console.error('Too many requests. Please wait before trying again.'); + // Show countdown or disable button temporarily + } +} +``` + +#### Generic Error Handler +```javascript +function handleAuthError(error) { + switch (error.status) { + case 400: + return 'Invalid input. Please check your data.'; + case 401: + return 'Authentication required. Redirecting to login...'; + case 403: + return 'Access denied. You may need to verify your email.'; + case 404: + return 'Resource not found.'; + case 422: + return 'Validation failed. Please check your input.'; + case 429: + return 'Too many requests. Please try again later.'; + case 500: + return 'Server error. Please try again.'; + default: + return 'An unexpected error occurred.'; + } +} + +// Usage +try { + await base44.auth.loginViaEmailPassword(email, password); +} catch (error) { + console.error(handleAuthError(error)); +} +``` + +--- + +## Auth Providers + +Configure authentication providers in your app dashboard: + +### Available Providers + +**Built-in (All Plans):** +- **Email/Password** - Default, always enabled +- **Google** - OAuth authentication +- **Microsoft** - OAuth authentication +- **Facebook** - OAuth authentication + +**SSO Providers (Elite Plan):** +- **Okta** +- **Azure AD** +- **GitHub** + +### Using OAuth Providers + +```javascript +// Initiate OAuth login flow +base44.auth.loginWithProvider('google'); + +// Return to specific page after authentication +base44.auth.loginWithProvider('microsoft', '/dashboard'); + +// Supported values: 'google', 'microsoft', 'facebook' +``` + +--- + +## Environment Availability + +| Environment | Availability | Notes | +|------------|--------------|-------| +| **Frontend** | ✅ Yes | All methods available | +| **Backend Functions** | ✅ Yes | Use `createClientFromRequest(req)` for authenticated client | +| **Service Role** | ❌ No | Auth methods not available in service role context | + +### Frontend Usage +```javascript +// Standard browser usage +const user = await base44.auth.me(); +``` + +### Backend Functions Usage +```javascript +import { createClientFromRequest } from "@base44/sdk"; + +Deno.serve(async (req) => { + const base44 = createClientFromRequest(req); + + // Get user from request context + const user = await base44.auth.me(); + + // User operations here... + return Response.json({ user }); +}); +``` + +--- + +## App Visibility + +Control who can access your app in the app settings: + +### Public Apps +- No login required for basic access +- Users can view public content without authentication +- Authenticated users get additional features/data + +### Private Apps +- Login required to access any content +- Unauthenticated users are automatically redirected to login +- All content is protected by default + +--- + +## Limitations + +### Authentication UI Options +- **Recommended:** Build custom login/signup UI using `loginViaEmailPassword()` and `loginWithProvider()` for full control over user experience and branding +- **Alternative:** `redirectToLogin()` uses Base44's hosted authentication pages with limited customization + +### Hosted Login (via redirectToLogin) +- `redirectToLogin()` shows both login and signup options on the same page +- No separate `redirectToSignup()` method +- Users can switch between login/signup on the hosted page +- ⚠️ **Note:** Prefer building custom login UI for better user experience + +### Password Requirements +- Minimum length and complexity requirements enforced +- Requirements not exposed via API +- Validation errors returned when requirements not met + +### Rate Limiting +- OTP requests are rate-limited to prevent abuse +- Password reset requests are rate-limited +- Login attempts may be rate-limited with Turnstile protection + +### Token Management +- JWTs are automatically stored in localStorage by default +- Token expiration and refresh not exposed in API +- Call `me()` or `isAuthenticated()` to verify token validity + +--- + +## Best Practices + +### 1. Always Handle Errors +```javascript +try { + await base44.auth.loginViaEmailPassword(email, password); +} catch (error) { + // Always handle authentication errors + console.error('Login failed:', error.message); +} +``` + +### 2. Verify Authentication Before Protected Actions +```javascript +const user = await requireAuth(); +if (!user) return; // Will redirect to login +// Proceed with authenticated action +``` + +### 3. Use Type Safety with TypeScript +```typescript +import type { User, LoginResponse } from '@base44/sdk'; + +const user: User = await base44.auth.me(); +const response: LoginResponse = await base44.auth.loginViaEmailPassword(email, password); +``` + +### 4. Don't Hardcode Credentials +```javascript +// ❌ BAD +base44.auth.setToken("hardcoded-token-here"); + +// ✅ GOOD +const token = await secureAuthFlow(); +base44.auth.setToken(token); +``` + +### 5. Provide User Feedback +```javascript +try { + await base44.auth.register(params); + showSuccessMessage('Registration successful! Check your email for verification code.'); +} catch (error) { + showErrorMessage('Registration failed: ' + error.message); +} +``` + +### 6. Handle Token Expiration Gracefully +```javascript +try { + const user = await base44.auth.me(); +} catch (error) { + if (error.status === 401) { + // Clear local state and navigate to login + localStorage.clear(); + navigate('/login'); + } +} +``` + +### 7. Build Custom Login UI (Recommended) +```javascript +// ✅ RECOMMENDED - Custom login form with direct methods +const handleLogin = async (email, password) => { + try { + const { user } = await base44.auth.loginViaEmailPassword(email, password); + navigate('/dashboard'); + } catch (error) { + setError(error.message); + } +}; + +const handleGoogleLogin = () => { + base44.auth.loginWithProvider('google', '/dashboard'); +}; + +// ❌ AVOID - Redirecting to hosted login page +// base44.auth.redirectToLogin(window.location.href); +``` diff --git a/evals/fixtures/fullstack/skills/base44-sdk/references/base44-agents.md b/evals/fixtures/fullstack/skills/base44-sdk/references/base44-agents.md new file mode 100644 index 0000000..ee55dee --- /dev/null +++ b/evals/fixtures/fullstack/skills/base44-sdk/references/base44-agents.md @@ -0,0 +1,364 @@ +# Agents Module + +AI agent conversations and messages via `base44.agents`. + +> **Note:** This module requires a logged-in user. All agent methods work in the context of the authenticated user. + +## Contents +- [Concepts](#concepts) +- [Methods](#methods) +- [Examples](#examples) (Create, Get Conversations, List, Subscribe, Send Message, WhatsApp) +- [Message Structure](#message-structure) +- [Conversation Structure](#conversation-structure) +- [Common Patterns](#common-patterns) + +## Concepts + +- **Conversation**: A dialogue between user and an AI agent. Has unique ID, agent name, user reference, and metadata. +- **Message**: Single message in a conversation. Has role (`user`, `assistant`, `system`), content, timestamps, and optional metadata. + +## Methods + +| Method | Signature | Description | +|--------|-----------|-------------| +| `createConversation(params)` | `Promise` | Create a new conversation with an agent | +| `getConversations()` | `Promise` | Get all user's conversations | +| `getConversation(id)` | `Promise` | Get conversation with messages | +| `listConversations(filterParams)` | `Promise` | Filter/sort/paginate conversations | +| `subscribeToConversation(id, onUpdate?)` | `() => void` | Real-time updates via WebSocket (returns unsubscribe function) | +| `addMessage(conversation, message)` | `Promise` | Send a message | +| `getWhatsAppConnectURL(agentName)` | `string` | Get WhatsApp connection URL for agent | + +## Examples + +### Create Conversation + +```javascript +const conversation = await base44.agents.createConversation({ + agent_name: "support-agent", + metadata: { + order_id: "ORD-123", + category: "billing" + } +}); + +console.log(conversation.id); +``` + +### Get All Conversations + +```javascript +const conversations = await base44.agents.getConversations(); + +conversations.forEach(conv => { + console.log(conv.id, conv.agent_name, conv.created_date); +}); +``` + +### Get Single Conversation (with messages) + +```javascript +const conversation = await base44.agents.getConversation("conv-id-123"); + +console.log(conversation.messages); +``` + +### List with Filters + +```javascript +// Using filterParams object with q, sort, limit, skip, fields +const recent = await base44.agents.listConversations({ + q: { agent_name: "support-agent" }, + sort: "-created_date", + limit: 10, + skip: 0 +}); + +// Filter by metadata +const highPriority = await base44.agents.listConversations({ + q: { + agent_name: "support-agent", + "metadata.priority": "high" + }, + sort: "-updated_date", + limit: 20 +}); +``` + +### Subscribe to Updates (Real-time) + +```javascript +const unsubscribe = base44.agents.subscribeToConversation( + "conv-id-123", + (updatedConversation) => { + // Called when new messages arrive + console.log("New messages:", updatedConversation.messages); + } +); + +// Later: unsubscribe +unsubscribe(); +``` + +### Send a Message + +```javascript +const conversation = await base44.agents.getConversation("conv-id-123"); + +await base44.agents.addMessage(conversation, { + role: "user", + content: "What's the weather like today?" +}); +``` + +### Get WhatsApp Connection URL + +```javascript +const whatsappUrl = base44.agents.getWhatsAppConnectURL("support-agent"); +// Returns URL for users to connect with agent via WhatsApp +console.log(whatsappUrl); +``` + +## Message Structure + +```javascript +{ + role: "user" | "assistant" | "system", + content: "Message text or structured object", + created_date: "2024-01-15T10:30:00Z", + updated_date: "2024-01-15T10:30:00Z", + + // Optional fields + reasoning: { + content: "Agent's reasoning process", + timing: 1500 + }, + tool_calls: [{ + name: "search", + arguments: { query: "weather" }, + result: { ... }, + status: "success" + }], + file_urls: ["https://..."], + usage: { + prompt_tokens: 150, + completion_tokens: 50 + }, + metadata: { ... }, + custom_context: { ... } +} +``` + +## Conversation Structure + +```javascript +{ + id: "conv-id-123", + app_id: "app-id", + agent_name: "support-agent", + created_by_id: "user-id", + created_date: "2024-01-15T10:00:00Z", + updated_date: "2024-01-15T10:30:00Z", + messages: [ ... ], + metadata: { ... } +} +``` + +## Common Patterns + +### Chat Interface + +```javascript +// Load conversation +const conv = await base44.agents.getConversation(conversationId); +setMessages(conv.messages); + +// Subscribe to updates +const unsubscribe = base44.agents.subscribeToConversation(conversationId, (updated) => { + setMessages(updated.messages); +}); + +// Send message +async function sendMessage(text) { + await base44.agents.addMessage(conv, { role: "user", content: text }); +} + +// Cleanup on unmount +return () => unsubscribe(); +``` + +## Type Definitions + +### AgentConversation + +```typescript +/** An agent conversation containing messages exchanged with an AI agent. */ +interface AgentConversation { + /** Unique identifier for the conversation. */ + id: string; + /** Application ID. */ + app_id: string; + /** Name of the agent in this conversation. */ + agent_name: string; + /** ID of the user who created the conversation. */ + created_by_id: string; + /** When the conversation was created. */ + created_date: string; + /** When the conversation was last updated. */ + updated_date: string; + /** Array of messages in the conversation. */ + messages: AgentMessage[]; + /** Optional metadata associated with the conversation. */ + metadata?: Record; +} +``` + +### AgentMessage + +```typescript +/** A message in an agent conversation. */ +interface AgentMessage { + /** Unique identifier for the message. */ + id: string; + /** Role of the message sender. */ + role: "user" | "assistant" | "system"; + /** When the message was created. */ + created_date: string; + /** When the message was last updated. */ + updated_date: string; + /** Message content. */ + content?: string | Record; + /** Optional reasoning information for the message. */ + reasoning?: AgentMessageReasoning | null; + /** URLs to files attached to the message. */ + file_urls?: string[]; + /** Tool calls made by the agent. */ + tool_calls?: AgentMessageToolCall[]; + /** Token usage statistics. */ + usage?: AgentMessageUsage; + /** Whether the message is hidden from the user. */ + hidden?: boolean; + /** Custom context provided with the message. */ + custom_context?: AgentMessageCustomContext[]; + /** Model used to generate the message. */ + model?: string; + /** Checkpoint ID for the message. */ + checkpoint_id?: string; + /** Metadata about when and by whom the message was created. */ + metadata?: AgentMessageMetadata; +} +``` + +### Supporting Types + +```typescript +/** Reasoning information for an agent message. */ +interface AgentMessageReasoning { + /** When reasoning started. */ + start_date: string; + /** When reasoning ended. */ + end_date?: string; + /** Reasoning content. */ + content: string; +} + +/** A tool call made by the agent. */ +interface AgentMessageToolCall { + /** Tool call ID. */ + id: string; + /** Name of the tool called. */ + name: string; + /** Arguments passed to the tool as JSON string. */ + arguments_string: string; + /** Status of the tool call. */ + status: "running" | "success" | "error" | "stopped"; + /** Results from the tool call. */ + results?: string; +} + +/** Token usage statistics for an agent message. */ +interface AgentMessageUsage { + /** Number of tokens in the prompt. */ + prompt_tokens?: number; + /** Number of tokens in the completion. */ + completion_tokens?: number; +} + +/** Custom context provided with an agent message. */ +interface AgentMessageCustomContext { + /** Context message. */ + message: string; + /** Associated data for the context. */ + data: Record; + /** Type of context. */ + type: string; +} + +/** Metadata about when and by whom a message was created. */ +interface AgentMessageMetadata { + /** When the message was created. */ + created_date: string; + /** Email of the user who created the message. */ + created_by_email: string; + /** Full name of the user who created the message. */ + created_by_full_name: string; +} +``` + +### CreateConversationParams + +```typescript +/** Parameters for creating a new conversation. */ +interface CreateConversationParams { + /** The name of the agent to create a conversation with. */ + agent_name: string; + /** Optional metadata to attach to the conversation. */ + metadata?: Record; +} +``` + +### ModelFilterParams + +```typescript +/** Parameters for filtering, sorting, and paginating conversations. */ +interface ModelFilterParams { + /** Query object with field-value pairs for filtering. */ + q?: Record; + /** Sort parameter (e.g., "-created_date" for descending). */ + sort?: string | null; + /** Maximum number of results to return. */ + limit?: number | null; + /** Number of results to skip for pagination. */ + skip?: number | null; + /** Array of field names to include in the response. */ + fields?: string[] | null; +} +``` + +### AgentsModule + +```typescript +/** Agents module for managing AI agent conversations. */ +interface AgentsModule { + /** Gets all conversations from all agents in the app. */ + getConversations(): Promise; + + /** Gets a specific conversation by ID. */ + getConversation(conversationId: string): Promise; + + /** Lists conversations with filtering, sorting, and pagination. */ + listConversations(filterParams: ModelFilterParams): Promise; + + /** Creates a new conversation with an agent. */ + createConversation(conversation: CreateConversationParams): Promise; + + /** Adds a message to a conversation. */ + addMessage(conversation: AgentConversation, message: Partial): Promise; + + /** Subscribes to real-time updates for a conversation. Returns unsubscribe function. */ + subscribeToConversation(conversationId: string, onUpdate?: (conversation: AgentConversation) => void): () => void; + + /** Gets WhatsApp connection URL for an agent. */ + getWhatsAppConnectURL(agentName: string): string; +} +``` diff --git a/evals/fixtures/fullstack/skills/base44-sdk/references/client.md b/evals/fixtures/fullstack/skills/base44-sdk/references/client.md new file mode 100644 index 0000000..554bf2b --- /dev/null +++ b/evals/fixtures/fullstack/skills/base44-sdk/references/client.md @@ -0,0 +1,257 @@ +# Client Setup + +How to create and configure the Base44 client. + +## Contents +- [In Base44-Generated Apps](#in-base44-generated-apps) +- [In External Apps](#in-external-apps) +- [In Backend Functions](#in-backend-functions) +- [Authentication Modes](#authentication-modes) (Anonymous, User, Service Role) +- [Available Modules](#available-modules) +- [Client Methods](#client-methods) +- [Client Configuration Options](#client-configuration-options) + +## In Base44-Generated Apps + +The client is pre-configured and available as `base44`. Just use it: + +```javascript +const tasks = await base44.entities.Task.list(); +``` + +## In External Apps + +Install the SDK and create a client: + +```bash +npm install @base44/sdk +``` + +```javascript +import { createClient } from "@base44/sdk"; + +// IMPORTANT: The parameter name is 'appId' (NOT 'clientId', NOT 'id') +// IMPORTANT: onError must be nested inside 'options' object +const base44 = createClient({ + appId: "your-app-id", // Required: Use 'appId' parameter + token: "optional-user-token", // Optional: for pre-authenticated requests + options: { // Optional: configuration options + onError: (error) => { // Optional: error handler (must be in options) + console.error("Base44 error:", error); + } + } +}); +``` + +**Common Mistakes:** +- ❌ `createClient({ clientId: "..." })` - WRONG parameter name +- ❌ `createClient({ id: "..." })` - WRONG parameter name +- ❌ `createClient({ appId: "...", onError: ... })` - WRONG: onError must be in options +- ✅ `createClient({ appId: "..." })` - CORRECT parameter name +- ✅ `createClient({ appId: "...", options: { onError: ... } })` - CORRECT: onError in options + +## In Backend Functions + +Use `createClientFromRequest` to get a client with the caller's auth context: + +```javascript +import { createClientFromRequest } from "@base44/sdk"; + +Deno.serve(async (req) => { + const base44 = createClientFromRequest(req); + + // Client inherits authentication from the request + const user = await base44.auth.me(); + + return Response.json({ user }); +}); +``` + +## Authentication Modes + +| Mode | How to Get | Permissions | +|------|-----------|-------------| +| **Anonymous** | `createClient({ appId })` without token | Public data only | +| **User** | After `loginViaEmailPassword()` or via `createClientFromRequest` | User's own data | +| **Service Role** | `base44.asServiceRole.*` in backend | Full admin access | + +## Anonymous Mode + +No authentication. Can only access public resources. + +```javascript +const base44 = createClient({ appId: "your-app-id" }); + +// Only works if Task entity allows anonymous read +const publicTasks = await base44.entities.Task.list(); +``` + +## User Mode + +After user logs in, the client automatically includes their token. + +```javascript +const base44 = createClient({ appId: "your-app-id" }); + +// Login sets the token +await base44.auth.loginViaEmailPassword("user@example.com", "password"); + +// Subsequent requests are authenticated +const user = await base44.auth.me(); +const myTasks = await base44.entities.Task.list(); // filtered by permissions +``` + +## Service Role Mode + +Admin-level access. **Backend only.** + +```javascript +// Inside a backend function +Deno.serve(async (req) => { + const base44 = createClientFromRequest(req); + + // User mode - respects permissions + const myTasks = await base44.entities.Task.list(); + + // Service role - bypasses permissions + const allTasks = await base44.asServiceRole.entities.Task.list(); + const allUsers = await base44.asServiceRole.entities.User.list(); + const oauthToken = await base44.asServiceRole.connectors.getAccessToken("slack"); + + return Response.json({ myTasks, allTasks }); +}); +``` + +## Available Modules + +The client exposes these modules: + +```javascript +base44.entities // CRUD operations +base44.auth // Authentication +base44.agents // AI conversations +base44.functions // Backend function invocation +base44.integrations // Third-party services +base44.analytics // Event tracking +base44.appLogs // App usage logging +base44.users // User invitations + +// Service role only (backend) +base44.asServiceRole.entities +base44.asServiceRole.agents +base44.asServiceRole.functions +base44.asServiceRole.integrations +base44.asServiceRole.appLogs +base44.asServiceRole.connectors +``` + +## Client Methods + +The client provides these methods: + +```javascript +// Set authentication token for all subsequent requests +base44.setToken(newToken); + +// Cleanup WebSocket connections (call when done with client) +base44.cleanup(); +``` + +### setToken + +Updates the authentication token for all subsequent API requests and WebSocket connections. + +```javascript +// After receiving a token (e.g., from external auth) +base44.setToken("new-jwt-token"); +``` + +### cleanup + +Disconnects WebSocket connections. Call when you're done with the client or when the component unmounts. + +```javascript +// Cleanup on component unmount (React example) +useEffect(() => { + return () => base44.cleanup(); +}, []); +``` + +## Client Configuration Options + +```javascript +createClient({ + appId: "your-app-id", // Required: MUST use 'appId' (not 'clientId' or 'id') + token: "jwt-token", // Optional: pre-set auth token + options: { // Optional: configuration options + onError: (error) => {} // Optional: global error handler (must be in options) + } +}); +``` + +**⚠️ Critical:** +- The parameter name is `appId`, not `clientId` or `id`. Using the wrong parameter name will cause errors. +- The `onError` handler must be nested inside the `options` object, not at the top level. + +## Type Definitions + +### CreateClientConfig + +```typescript +/** Configuration for creating a Base44 client. */ +interface CreateClientConfig { + /** The Base44 app ID (required). */ + appId: string; + /** User authentication token. Used to authenticate as a specific user. */ + token?: string; + /** Service role authentication token (backend only). */ + serviceToken?: string; + /** Additional client options. */ + options?: CreateClientOptions; +} + +/** Options for creating a Base44 client. */ +interface CreateClientOptions { + /** Optional error handler called whenever an API error occurs. */ + onError?: (error: Error) => void; +} +``` + +### Base44Client + +```typescript +/** The Base44 client instance. */ +interface Base44Client { + /** Entities module for CRUD operations on your data models. */ + entities: EntitiesModule; + /** Integrations module for calling pre-built integration endpoints. */ + integrations: IntegrationsModule; + /** Auth module for user authentication and management. */ + auth: AuthModule; + /** Functions module for invoking custom backend functions. */ + functions: FunctionsModule; + /** Agents module for managing AI agent conversations. */ + agents: AgentsModule; + /** App logs module for tracking app usage. */ + appLogs: AppLogsModule; + /** Analytics module for tracking custom events. */ + analytics: AnalyticsModule; + + /** Cleanup function to disconnect WebSocket connections. */ + cleanup(): void; + + /** Sets a new authentication token for all subsequent requests. */ + setToken(newToken: string): void; + + /** Provides access to modules with elevated service role permissions (backend only). */ + readonly asServiceRole: { + entities: EntitiesModule; + integrations: IntegrationsModule; + connectors: ConnectorsModule; + functions: FunctionsModule; + agents: AgentsModule; + appLogs: AppLogsModule; + cleanup(): void; + }; +} +``` diff --git a/evals/fixtures/fullstack/skills/base44-sdk/references/connectors.md b/evals/fixtures/fullstack/skills/base44-sdk/references/connectors.md new file mode 100644 index 0000000..f3fbcb4 --- /dev/null +++ b/evals/fixtures/fullstack/skills/base44-sdk/references/connectors.md @@ -0,0 +1,113 @@ +# Connectors Module + +OAuth token management for external services. **Service role only** (backend functions). + +## Method + +```javascript +base44.asServiceRole.connectors.getAccessToken(integrationType): Promise +``` + +Returns a raw OAuth access token for the specified service. + +## Supported Services + +| Integration Type | Service | +|-----------------|---------| +| `"googlecalendar"` | Google Calendar | +| `"googledrive"` | Google Drive | +| `"slack"` | Slack | +| `"notion"` | Notion | +| `"salesforce"` | Salesforce | +| `"hubspot"` | HubSpot | +| `"linkedin"` | LinkedIn | +| `"tiktok"` | TikTok | +| `"github"` | GitHub | + +## Example Usage + +```javascript +// Backend function only +Deno.serve(async (req) => { + const base44 = createClientFromRequest(req); + + // Get OAuth token for Slack + const slackToken = await base44.asServiceRole.connectors.getAccessToken("slack"); + + // Use token directly with Slack API + const response = await fetch("https://slack.com/api/chat.postMessage", { + method: "POST", + headers: { + "Authorization": `Bearer ${slackToken}`, + "Content-Type": "application/json" + }, + body: JSON.stringify({ + channel: "#general", + text: "Hello from Base44!" + }) + }); + + return Response.json(await response.json()); +}); +``` + +## Google Calendar Example + +```javascript +Deno.serve(async (req) => { + const base44 = createClientFromRequest(req); + + const token = await base44.asServiceRole.connectors.getAccessToken("googlecalendar"); + + // List upcoming events + const response = await fetch( + "https://www.googleapis.com/calendar/v3/calendars/primary/events?" + + new URLSearchParams({ + maxResults: "10", + orderBy: "startTime", + singleEvents: "true", + timeMin: new Date().toISOString() + }), + { + headers: { "Authorization": `Bearer ${token}` } + } + ); + + const events = await response.json(); + return Response.json(events); +}); +``` + +## Setup Requirements + +1. **Builder plan** or higher +2. **Backend functions** enabled +3. **Connector configured** in Base44 dashboard (OAuth flow completed) + +## Important Notes + +- **One account per connector per app**: All users share the same connected account +- **Backend only**: `connectors` module not available in frontend code +- **Service role required**: Must use `base44.asServiceRole.connectors` +- **You handle the API calls**: Base44 only provides the token; you make the actual API requests +- **Token refresh**: Base44 handles token refresh automatically + +## Type Definitions + +```typescript +/** + * The type of external integration/connector. + * Examples: 'googlecalendar', 'slack', 'github', 'notion', etc. + */ +type ConnectorIntegrationType = string; + +/** Connectors module for managing OAuth tokens for external services. */ +interface ConnectorsModule { + /** + * Retrieves an OAuth access token for a specific external integration type. + * @param integrationType - The type of integration (e.g., 'googlecalendar', 'slack'). + * @returns Promise resolving to the access token string. + */ + getAccessToken(integrationType: ConnectorIntegrationType): Promise; +} +``` diff --git a/evals/fixtures/fullstack/skills/base44-sdk/references/entities.md b/evals/fixtures/fullstack/skills/base44-sdk/references/entities.md new file mode 100644 index 0000000..a7bdf35 --- /dev/null +++ b/evals/fixtures/fullstack/skills/base44-sdk/references/entities.md @@ -0,0 +1,253 @@ +# Entities Module + +CRUD operations on data models. Access via `base44.entities.EntityName.method()`. + +## Contents +- [Methods](#methods) +- [Examples](#examples) (Create, Bulk Create, List, Filter, Get, Update, Delete, Subscribe) +- [User Entity](#user-entity) +- [Service Role Access](#service-role-access) +- [Permissions](#permissions) + +## Methods + +| Method | Signature | Description | +|--------|-----------|-------------| +| `create(data)` | `Promise` | Create one record | +| `bulkCreate(dataArray)` | `Promise` | Create multiple records | +| `list(sort?, limit?, skip?, fields?)` | `Promise` | Get all records (paginated) | +| `filter(query, sort?, limit?, skip?, fields?)` | `Promise` | Get records matching conditions | +| `get(id)` | `Promise` | Get single record by ID | +| `update(id, data)` | `Promise` | Update record (partial update) | +| `delete(id)` | `Promise` | Delete record by ID | +| `deleteMany(query)` | `Promise` | Delete all matching records | +| `importEntities(file)` | `Promise` | Import from CSV (frontend only) | +| `subscribe(callback)` | `() => void` | Subscribe to realtime updates (returns unsubscribe function) | + +## Examples + +### Create + +```javascript +const task = await base44.entities.Task.create({ + title: "Complete documentation", + status: "pending", + dueDate: "2024-12-31" +}); +``` + +### Bulk Create + +```javascript +const tasks = await base44.entities.Task.bulkCreate([ + { title: "Task 1", status: "pending" }, + { title: "Task 2", status: "pending" } +]); +``` + +### List with Pagination + +```javascript +// Get first 10 records, sorted by created_date descending +const tasks = await base44.entities.Task.list( + "-created_date", // sort (string: prefix with - for descending) + 10, // limit + 0 // skip +); + +// Get next page +const page2 = await base44.entities.Task.list("-created_date", 10, 10); +``` + +### Filter + +```javascript +// Simple filter +const pending = await base44.entities.Task.filter({ status: "pending" }); + +// Multiple conditions +const myPending = await base44.entities.Task.filter({ + status: "pending", + assignedTo: userId +}); + +// With sort, limit, skip +const recent = await base44.entities.Task.filter( + { status: "pending" }, + "-created_date", // sort (string: prefix with - for descending) + 5, + 0 +); + +// Select specific fields +const titles = await base44.entities.Task.filter( + { status: "pending" }, + null, + null, + null, + ["id", "title"] +); +``` + +### Get by ID + +```javascript +const task = await base44.entities.Task.get("task-id-123"); +``` + +### Update + +```javascript +// Partial update - only specified fields change +await base44.entities.Task.update("task-id-123", { + status: "completed", + completedAt: new Date().toISOString() +}); +``` + +### Delete + +```javascript +// Single record +await base44.entities.Task.delete("task-id-123"); + +// Multiple records matching query +await base44.entities.Task.deleteMany({ status: "archived" }); +``` + +### Subscribe to Realtime Updates + +```javascript +// Subscribe to all changes on Task entity +const unsubscribe = base44.entities.Task.subscribe((event) => { + console.log(`Task ${event.id} was ${event.type}:`, event.data); + // event.type is "create", "update", or "delete" +}); + +// Later: unsubscribe to stop receiving updates +unsubscribe(); +``` + +**Event structure:** +```javascript +{ + type: "create" | "update" | "delete", + data: { ... }, // the entity data + id: "entity-id", // the affected entity's ID + timestamp: "2024-01-15T10:30:00Z" +} +``` + +## User Entity + +Every app has a built-in `User` entity with special rules: + +- Regular users can only read/update **their own** record +- Cannot create users via `entities.create()` - use `auth.register()` instead +- Service role has full access to all user records + +```javascript +// Get current user's record +const me = await base44.entities.User.get(currentUserId); + +// Service role: get any user +const anyUser = await base44.asServiceRole.entities.User.get(userId); +``` + +## Service Role Access + +For admin-level operations (bypass user permissions): + +```javascript +// Backend only +const allTasks = await base44.asServiceRole.entities.Task.list(); +const allUsers = await base44.asServiceRole.entities.User.list(); +``` + +## Permissions (RLS & FLS) + +Data access is controlled by **Row Level Security (RLS)** and **Field Level Security (FLS)** rules defined in entity schemas. + +1. **Authentication level**: anonymous, authenticated, or service role +2. **RLS rules**: Control which records (rows) users can create/read/update/delete +3. **FLS rules**: Control which fields users can read/write within accessible records + +Operations succeed or fail based on these rules - no partial results. + +RLS and FLS are configured in entity schema files (`base44/entities/*.jsonc`). See [entities-create.md](../../base44-cli/references/entities-create.md#row-level-security-rls) for configuration details. + +**Note:** `asServiceRole` sets the user's role to `"admin"` but does NOT bypass RLS. Your RLS rules must include admin access (e.g., `{ "user_condition": { "role": "admin" } }`) for service role operations to succeed. + +## Type Definitions + +### RealtimeEvent + +```typescript +/** Event types for realtime entity updates. */ +type RealtimeEventType = "create" | "update" | "delete"; + +/** Payload received when a realtime event occurs. */ +interface RealtimeEvent { + /** The type of change that occurred. */ + type: RealtimeEventType; + /** The entity data. */ + data: any; + /** The unique identifier of the affected entity. */ + id: string; + /** ISO 8601 timestamp of when the event occurred. */ + timestamp: string; +} + +/** Callback function invoked when a realtime event occurs. */ +type RealtimeCallback = (event: RealtimeEvent) => void; + +/** Function returned from subscribe, call it to unsubscribe. */ +type Subscription = () => void; +``` + +### EntityHandler + +```typescript +/** Entity handler providing CRUD operations for a specific entity type. */ +interface EntityHandler { + /** Lists records with optional pagination and sorting. */ + list(sort?: string, limit?: number, skip?: number, fields?: string[]): Promise; + + /** Filters records based on a query. */ + filter(query: Record, sort?: string, limit?: number, skip?: number, fields?: string[]): Promise; + + /** Gets a single record by ID. */ + get(id: string): Promise; + + /** Creates a new record. */ + create(data: Record): Promise; + + /** Updates an existing record. */ + update(id: string, data: Record): Promise; + + /** Deletes a single record by ID. */ + delete(id: string): Promise; + + /** Deletes multiple records matching a query. */ + deleteMany(query: Record): Promise; + + /** Creates multiple records in a single request. */ + bulkCreate(data: Record[]): Promise; + + /** Imports records from a file (frontend only). */ + importEntities(file: File): Promise; + + /** Subscribes to realtime updates. Returns unsubscribe function. */ + subscribe(callback: RealtimeCallback): Subscription; +} +``` + +### EntitiesModule + +```typescript +/** Entities module for managing app data. */ +interface EntitiesModule { + /** Access any entity by name dynamically. */ + [entityName: string]: EntityHandler; +} +``` diff --git a/evals/fixtures/fullstack/skills/base44-sdk/references/functions.md b/evals/fixtures/fullstack/skills/base44-sdk/references/functions.md new file mode 100644 index 0000000..8072465 --- /dev/null +++ b/evals/fixtures/fullstack/skills/base44-sdk/references/functions.md @@ -0,0 +1,216 @@ +# Functions Module + +Invoke custom backend functions via `base44.functions`. + +## Contents +- [Method](#method) +- [Invoking Functions](#invoking-functions) (Frontend, File Upload, Service Role, REST API) +- [Writing Backend Functions](#writing-backend-functions) (Basic, Service Role, Secrets, Errors) +- [Setup Requirements](#setup-requirements) +- [Authentication Modes](#authentication-modes) + +## Method + +```javascript +base44.functions.invoke(functionName, data): Promise +``` + +- `functionName`: Name of the backend function +- `data`: Object of parameters (sent as JSON, or multipart if contains File objects) +- Returns: Whatever the function returns + +## Invoking Functions + +### From Frontend + +```javascript +const result = await base44.functions.invoke("processOrder", { + orderId: "order-123", + action: "ship" +}); + +console.log(result); +``` + +### With File Upload + +```javascript +const fileInput = document.querySelector('input[type="file"]'); +const file = fileInput.files[0]; + +// Automatically uses multipart/form-data when File objects present +const result = await base44.functions.invoke("uploadDocument", { + file: file, + category: "invoices" +}); +``` + +### With Service Role (Backend) + +```javascript +// Inside another backend function +const result = await base44.asServiceRole.functions.invoke("adminTask", { + userId: "user-123" +}); +``` + +### Via REST API (curl) + +Functions can be called via HTTP POST to your app domain: + +```bash +curl -X POST "https:///functions/" \ + -H "Content-Type: application/json" \ + -d '{"key": "value"}' +``` + +## Writing Backend Functions + +Backend functions run on Deno. Must export using `Deno.serve()`. + +### Required Directory Structure + +Each function must be in its own subdirectory under `base44/functions/` with a configuration file: + +``` +base44/ + functions/ + process-order/ # kebab-case directory name + function.jsonc # required configuration + index.ts # entry point +``` + +**function.jsonc:** +```jsonc +{ + "name": "process-order", + "entry": "index.ts" +} +``` + +For complete setup and deployment instructions, see [functions-create.md](../../base44-cli/references/functions-create.md) in base44-cli. + +### Basic Structure + +```javascript +// base44/functions/process-order/index.ts +import { createClientFromRequest } from "npm:@base44/sdk"; + +Deno.serve(async (req) => { + // Get authenticated client from request + const base44 = createClientFromRequest(req); + + // Parse input + const { orderId, action } = await req.json(); + + // Your logic here + const order = await base44.entities.Orders.get(orderId); + + // Return response + return Response.json({ + success: true, + order: order + }); +}); +``` + +### With Service Role Access + +```javascript +import { createClientFromRequest } from "npm:@base44/sdk"; + +Deno.serve(async (req) => { + const base44 = createClientFromRequest(req); + + // Check user is authenticated + const user = await base44.auth.me(); + if (!user) { + return Response.json({ error: "Unauthorized" }, { status: 401 }); + } + + // Use service role for admin operations + const allOrders = await base44.asServiceRole.entities.Orders.list(); + + return Response.json({ orders: allOrders }); +}); +``` + +### Using Secrets + +```javascript +Deno.serve(async (req) => { + // Access environment variables (configured in app settings) + const apiKey = Deno.env.get("STRIPE_API_KEY"); + + const response = await fetch("https://api.stripe.com/v1/charges", { + headers: { + "Authorization": `Bearer ${apiKey}` + } + }); + + return Response.json(await response.json()); +}); +``` + +### Error Handling + +```javascript +import { createClientFromRequest } from "npm:@base44/sdk"; + +Deno.serve(async (req) => { + try { + const base44 = createClientFromRequest(req); + const { orderId } = await req.json(); + + const order = await base44.entities.Orders.get(orderId); + if (!order) { + return Response.json( + { error: "Order not found" }, + { status: 404 } + ); + } + + return Response.json({ order }); + + } catch (error) { + return Response.json( + { error: error.message }, + { status: 500 } + ); + } +}); +``` + +## Setup Requirements + +1. Enable Backend Functions in app settings (requires appropriate plan) +2. Create function files in `/functions` folder +3. Configure secrets via app dashboard for API keys + +## Authentication Modes + +| Mode | Context | Permissions | +|------|---------|-------------| +| User | `base44.functions.invoke()` | Runs under calling user's permissions | +| Service Role | `base44.asServiceRole.functions.invoke()` | Admin-level access | + +Inside the function, use `createClientFromRequest(req)` to get a client that inherits the caller's auth context. + +## Type Definitions + +```typescript +/** Functions module for invoking custom backend functions. */ +interface FunctionsModule { + /** + * Invokes a custom backend function by name. + * + * If any parameter is a File object, the request will automatically be + * sent as multipart/form-data. Otherwise, it will be sent as JSON. + * + * @param functionName - The name of the function to invoke. + * @param data - An object containing named parameters for the function. + * @returns Promise resolving to the function's response. + */ + invoke(functionName: string, data: Record): Promise; +} +``` diff --git a/evals/fixtures/fullstack/skills/base44-sdk/references/integrations.md b/evals/fixtures/fullstack/skills/base44-sdk/references/integrations.md new file mode 100644 index 0000000..f356a18 --- /dev/null +++ b/evals/fixtures/fullstack/skills/base44-sdk/references/integrations.md @@ -0,0 +1,377 @@ +# Integrations Module + +Access third-party services via `base44.integrations`. + +## Types of Integrations + +1. **Core/Built-in**: AI, email, file uploads (available by default) +2. **Catalog integrations**: Pre-built connectors from Base44 catalog +3. **Custom integrations**: Your own OpenAPI-based integrations + +## Accessing Integrations + +```javascript +// Core integrations +base44.integrations.Core.FunctionName(params) + +// Custom integrations +base44.integrations.custom.call(slug, operationId, params) +``` + +## Core Integrations + +### InvokeLLM (AI Text Generation) + +Generate text or structured JSON data using AI models. + +```javascript +// Basic prompt - returns string +const response = await base44.integrations.Core.InvokeLLM({ + prompt: "Summarize this text: ..." +}); + +// With internet context (uses Google Search, Maps, News) +const response = await base44.integrations.Core.InvokeLLM({ + prompt: "What's the current weather in NYC?", + add_context_from_internet: true +}); + +// Structured JSON response - returns object +const response = await base44.integrations.Core.InvokeLLM({ + prompt: "Analyze the sentiment of: 'Great product but slow shipping'", + response_json_schema: { + type: "object", + properties: { + sentiment: { type: "string", enum: ["positive", "negative", "mixed"] }, + score: { type: "number", description: "Score from 1-10" }, + key_points: { type: "array", items: { type: "string" } } + } + } +}); +// Returns: { sentiment: "mixed", score: 7, key_points: ["great product", "slow shipping"] } + +// With file attachments (uploaded via UploadFile) +const response = await base44.integrations.Core.InvokeLLM({ + prompt: "Describe what's in this image", + file_urls: ["https://...uploaded_image.png"] +}); +``` + +**Parameters:** +- `prompt` (string, required): The prompt text to send to the model +- `add_context_from_internet` (boolean, optional): If true, uses Google Search/Maps/News for real-time context +- `response_json_schema` (object, optional): JSON schema for structured output +- `file_urls` (string[], optional): URLs of uploaded files for context + +### GenerateImage + +Create AI-generated images from text prompts. + +```javascript +const { url } = await base44.integrations.Core.GenerateImage({ + prompt: "A serene mountain landscape with a lake" +}); +console.log(url); // https://...generated_image.png +``` + +### SendEmail + +Send emails to registered users. Every app gets this integration (no plan upgrade required). + +```javascript +await base44.integrations.Core.SendEmail({ + to: "user@example.com", + subject: "Welcome!", + body: "

Hello

Welcome to our app.

", + from_name: "My App" // optional, defaults to app name +}); +``` + +**Parameters:** +- `to` (string, required): Recipient email address +- `subject` (string, required): Email subject line +- `body` (string, required): Plain text or HTML email body +- `from_name` (string, optional): Sender name displayed to recipient + +**Limitations:** +- 1 credit per email (2 credits with custom domain) + +### UploadFile (Public) + +Upload files to public storage. + +```javascript +const fileInput = document.querySelector('input[type="file"]'); +const { file_url } = await base44.integrations.Core.UploadFile({ + file: fileInput.files[0] +}); +console.log(file_url); // https://...uploaded_file.pdf +``` + +### UploadPrivateFile + +Upload files to private storage that requires a signed URL to access. + +```javascript +const { file_uri } = await base44.integrations.Core.UploadPrivateFile({ + file: fileInput.files[0] +}); +console.log(file_uri); // "private/user123/document.pdf" + +// Create a signed URL to access the file +const { signed_url } = await base44.integrations.Core.CreateFileSignedUrl({ + file_uri, + expires_in: 3600 // URL expires in 1 hour (default: 300 seconds) +}); +console.log(signed_url); // Temporary URL to access the private file +``` + +### CreateFileSignedUrl + +Generate temporary access links for private files. + +```javascript +const { signed_url } = await base44.integrations.Core.CreateFileSignedUrl({ + file_uri: "private/user123/document.pdf", + expires_in: 7200 // 2 hours +}); +``` + +**Parameters:** +- `file_uri` (string, required): URI from UploadPrivateFile +- `expires_in` (number, optional): Expiration time in seconds (default: 300) + +### ExtractDataFromUploadedFile + +Extract structured data from uploaded files using AI. + +```javascript +// First upload the file +const { file_url } = await base44.integrations.Core.UploadFile({ file }); + +// Then extract structured data +const result = await base44.integrations.Core.ExtractDataFromUploadedFile({ + file_url, + json_schema: { + type: "object", + properties: { + invoice_number: { type: "string" }, + total_amount: { type: "number" }, + date: { type: "string" }, + vendor_name: { type: "string" } + } + } +}); +console.log(result); // { invoice_number: "INV-12345", total_amount: 1250.00, ... } +``` + +## Custom Integrations + +Custom integrations allow workspace administrators to connect any external API by importing an OpenAPI specification. Use `base44.integrations.custom.call()` to invoke them. + +### Syntax + +```javascript +const response = await base44.integrations.custom.call( + slug, // Integration identifier (set by admin) + operationId, // Endpoint in "method:path" format + params // Optional: payload, pathParams, queryParams +); +``` + +### Examples + +```javascript +// GET request with query params +const response = await base44.integrations.custom.call( + "my-crm", + "get:/contacts", + { queryParams: { limit: 10, status: "active" } } +); + +// POST request with body +const response = await base44.integrations.custom.call( + "my-crm", + "post:/contacts", + { payload: { name: "John Doe", email: "john@example.com" } } +); + +// Request with path parameters +const response = await base44.integrations.custom.call( + "github", + "post:/repos/{owner}/{repo}/issues", + { + pathParams: { owner: "myorg", repo: "myrepo" }, + payload: { title: "Bug report", body: "Something is broken" } + } +); +``` + +### Response Structure + +```javascript +{ + success: true, // Whether external API returned 2xx + status_code: 200, // HTTP status code + data: { ... } // Response from external API +} +``` + +## Requirements + +- **Core integrations**: Available on all plans +- **Catalog/Custom integrations**: Require Builder plan or higher + +## Type Definitions + +### Core Integration Parameters + +```typescript +/** Parameters for the InvokeLLM function. */ +interface InvokeLLMParams { + /** The prompt text to send to the model. */ + prompt: string; + /** If true, uses Google Search/Maps/News for real-time context. */ + add_context_from_internet?: boolean; + /** JSON schema for structured output. If provided, returns object instead of string. */ + response_json_schema?: object; + /** File URLs (from UploadFile) to provide as context. */ + file_urls?: string[]; +} + +/** Parameters for the GenerateImage function. */ +interface GenerateImageParams { + /** Description of the image to generate. */ + prompt: string; +} + +/** Result from GenerateImage. */ +interface GenerateImageResult { + /** URL of the generated image. */ + url: string; +} + +/** Parameters for the UploadFile function. */ +interface UploadFileParams { + /** The file object to upload. */ + file: File; +} + +/** Result from UploadFile. */ +interface UploadFileResult { + /** URL of the uploaded file. */ + file_url: string; +} + +/** Parameters for the SendEmail function. */ +interface SendEmailParams { + /** Recipient email address. */ + to: string; + /** Email subject line. */ + subject: string; + /** Plain text or HTML email body. */ + body: string; + /** Sender name (defaults to app name). */ + from_name?: string; +} + +/** Parameters for ExtractDataFromUploadedFile. */ +interface ExtractDataFromUploadedFileParams { + /** URL of the uploaded file. */ + file_url: string; + /** JSON schema defining fields to extract. */ + json_schema: object; +} + +/** Parameters for UploadPrivateFile. */ +interface UploadPrivateFileParams { + /** The file object to upload. */ + file: File; +} + +/** Result from UploadPrivateFile. */ +interface UploadPrivateFileResult { + /** URI of the private file (used for signed URLs). */ + file_uri: string; +} + +/** Parameters for CreateFileSignedUrl. */ +interface CreateFileSignedUrlParams { + /** URI from UploadPrivateFile. */ + file_uri: string; + /** Expiration time in seconds (default: 300). */ + expires_in?: number; +} + +/** Result from CreateFileSignedUrl. */ +interface CreateFileSignedUrlResult { + /** Temporary signed URL to access the file. */ + signed_url: string; +} +``` + +### CoreIntegrations + +```typescript +/** Core package containing built-in Base44 integration functions. */ +interface CoreIntegrations { + InvokeLLM(params: InvokeLLMParams): Promise; + GenerateImage(params: GenerateImageParams): Promise; + UploadFile(params: UploadFileParams): Promise; + SendEmail(params: SendEmailParams): Promise; + ExtractDataFromUploadedFile(params: ExtractDataFromUploadedFileParams): Promise; + UploadPrivateFile(params: UploadPrivateFileParams): Promise; + CreateFileSignedUrl(params: CreateFileSignedUrlParams): Promise; +} +``` + +### Custom Integrations + +```typescript +/** Parameters for calling a custom integration endpoint. */ +interface CustomIntegrationCallParams { + /** Request body payload. */ + payload?: Record; + /** Path parameters to substitute in the URL. */ + pathParams?: Record; + /** Query string parameters. */ + queryParams?: Record; + /** Additional headers for this request. */ + headers?: Record; +} + +/** Response from a custom integration call. */ +interface CustomIntegrationCallResponse { + /** Whether the external API returned a 2xx status code. */ + success: boolean; + /** The HTTP status code from the external API. */ + status_code: number; + /** The response data from the external API. */ + data: any; +} + +/** Module for calling custom workspace-level API integrations. */ +interface CustomIntegrationsModule { + /** + * Call a custom integration endpoint. + * @param slug - The integration's unique identifier. + * @param operationId - The operation ID from the OpenAPI spec. + * @param params - Optional payload, pathParams, queryParams, headers. + */ + call(slug: string, operationId: string, params?: CustomIntegrationCallParams): Promise; +} +``` + +### IntegrationsModule + +```typescript +/** Integrations module for calling integration endpoints. */ +type IntegrationsModule = { + /** Core package with built-in integrations. */ + Core: CoreIntegrations; + /** Custom integrations module. */ + custom: CustomIntegrationsModule; + /** Additional integration packages (dynamic). */ + [packageName: string]: any; +}; +``` diff --git a/evals/fixtures/fullstack/skills/base44-sdk/references/users.md b/evals/fixtures/fullstack/skills/base44-sdk/references/users.md new file mode 100644 index 0000000..4205b06 --- /dev/null +++ b/evals/fixtures/fullstack/skills/base44-sdk/references/users.md @@ -0,0 +1,82 @@ +# Users Module + +Invite users to the app via `base44.users`. + +## Contents +- [Methods](#methods) +- [Examples](#examples) (Invite User) +- [Roles](#roles) +- [Notes](#notes) + +## Methods + +| Method | Signature | Description | +|--------|-----------|-------------| +| `inviteUser(user_email, role)` | `Promise` | Invite a user to the app | + +## Examples + +### Invite User + +```javascript +// Invite a user with "user" role +await base44.users.inviteUser("newuser@example.com", "user"); + +// Invite an admin +await base44.users.inviteUser("admin@example.com", "admin"); +``` + +### Invite Multiple Users + +```javascript +const usersToInvite = [ + { email: "user1@example.com", role: "user" }, + { email: "user2@example.com", role: "user" }, + { email: "manager@example.com", role: "admin" } +]; + +for (const user of usersToInvite) { + await base44.users.inviteUser(user.email, user.role); + console.log(`Invited ${user.email} as ${user.role}`); +} +``` + +## Roles + +The `role` parameter must be one of: + +| Role | Description | +|------|-------------| +| `"user"` | Standard user with default permissions | +| `"admin"` | Administrator with elevated permissions | + +**Note:** Only `"user"` and `"admin"` are valid role values. An error will be thrown if you pass any other value. + +## Notes + +- **Email invitation**: The invited user receives an email with a link to join the app +- **Duplicate handling**: Inviting an existing user will re-send the invitation +- **Also available in auth**: `base44.auth.inviteUser()` provides the same functionality +- **Role validation**: Only `"user"` or `"admin"` are accepted + +```javascript +// These are equivalent: +await base44.users.inviteUser("newuser@example.com", "user"); +await base44.auth.inviteUser("newuser@example.com", "user"); +``` + +## Type Definitions + +```typescript +/** Users module for inviting users to the app. */ +interface UsersModule { + /** + * Invite a user to the application. + * @param user_email - User's email address. + * @param role - User's role ('user' or 'admin'). + * @returns Promise resolving when the invitation is sent. + * @throws Error if role is not 'user' or 'admin'. + */ + inviteUser(user_email: string, role: "user" | "admin"): Promise; +} +``` diff --git a/evals/fixtures/nextjs-todo/AGENTS.md b/evals/fixtures/nextjs-todo/AGENTS.md new file mode 100644 index 0000000..37e2902 --- /dev/null +++ b/evals/fixtures/nextjs-todo/AGENTS.md @@ -0,0 +1,14 @@ +# Todo App + +A Next.js application using Base44 for backend services. + +## Project Structure + +- `src/app/` - Next.js App Router pages +- `src/lib/base44.ts` - Base44 SDK client +- `base44/` - Base44 configuration and entities + +## Available Skills + +- **base44-cli**: CLI for managing Base44 entities, functions, and deployment +- **base44-sdk**: SDK for building features with Base44 APIs (entities, auth, etc.) diff --git a/evals/fixtures/nextjs-todo/eval.json b/evals/fixtures/nextjs-todo/eval.json new file mode 100644 index 0000000..e642a08 --- /dev/null +++ b/evals/fixtures/nextjs-todo/eval.json @@ -0,0 +1,94 @@ +{ + "$schema": "../../eval.schema.json", + "name": "nextjs-todo-entities", + "description": "Test creating Todo and Category entities in an existing Next.js Base44 project", + "prompt": "Create Base44 entity schema files in base44/entities/ for a todo app. Create a Todo entity (todo.jsonc) with title (string, required), description (string), completed (boolean, default false), and due_date (string with date format). Also create a Category entity (category.jsonc) with name (string, required) and color (string). The Todo should have a category_id field to reference the Category. Use the standard Base44 entity schema format with name, type, properties, and required fields.", + "expectedSkills": ["base44-cli"], + "checks": [ + { + "type": "file-exists", + "description": "Todo entity file created", + "filePath": "base44/entities/todo.jsonc" + }, + { + "type": "file-exists", + "description": "Category entity file created", + "filePath": "base44/entities/category.jsonc" + }, + { + "type": "valid-json", + "description": "Todo entity is valid JSON", + "filePath": "base44/entities/todo.jsonc" + }, + { + "type": "valid-json", + "description": "Category entity is valid JSON", + "filePath": "base44/entities/category.jsonc" + }, + { + "type": "file-content", + "description": "Todo has title property", + "filePath": "base44/entities/todo.jsonc", + "pattern": "\"title\"\\s*:\\s*\\{" + }, + { + "type": "file-content", + "description": "Todo has completed property", + "filePath": "base44/entities/todo.jsonc", + "pattern": "\"completed\"\\s*:\\s*\\{" + }, + { + "type": "file-content", + "description": "Category has name property", + "filePath": "base44/entities/category.jsonc", + "pattern": "\"name\"\\s*:\\s*\\{" + }, + { + "type": "contains", + "description": "mentions base44 or entities", + "value": "base44" + }, + { + "type": "entity-config", + "description": "Todo entity validates against Base44 entity schema", + "filePath": "base44/entities/todo.jsonc", + "target": "file" + }, + { + "type": "entity-config", + "description": "Category entity validates against Base44 entity schema", + "filePath": "base44/entities/category.jsonc", + "target": "file" + }, + { + "type": "file-content", + "description": "Todo entity name is PascalCase 'Todo'", + "filePath": "base44/entities/todo.jsonc", + "pattern": "\"name\"\\s*:\\s*\"Todo\"" + }, + { + "type": "file-content", + "description": "Todo has type 'object' at root", + "filePath": "base44/entities/todo.jsonc", + "pattern": "\"type\"\\s*:\\s*\"object\"" + }, + { + "type": "file-content", + "description": "Todo has required array", + "filePath": "base44/entities/todo.jsonc", + "pattern": "\"required\"\\s*:\\s*\\[" + }, + { + "type": "file-content", + "description": "Todo completed field has type boolean", + "filePath": "base44/entities/todo.jsonc", + "pattern": "\"completed\"[\\s\\S]*?\"type\"\\s*:\\s*\"boolean\"" + }, + { + "type": "file-content", + "description": "Category entity name is PascalCase 'Category'", + "filePath": "base44/entities/category.jsonc", + "pattern": "\"name\"\\s*:\\s*\"Category\"" + } + ] +} diff --git a/evals/fixtures/nextjs-todo/project/.claude/settings.json b/evals/fixtures/nextjs-todo/project/.claude/settings.json new file mode 100644 index 0000000..ab60505 --- /dev/null +++ b/evals/fixtures/nextjs-todo/project/.claude/settings.json @@ -0,0 +1,6 @@ +{ + "permissions": { + "allow": ["Read", "Write", "Edit", "Bash", "Glob", "Grep"] + }, + "skills": ["./skills"] +} diff --git a/evals/fixtures/nextjs-todo/project/AGENTS.md b/evals/fixtures/nextjs-todo/project/AGENTS.md new file mode 100644 index 0000000..6c0f2be --- /dev/null +++ b/evals/fixtures/nextjs-todo/project/AGENTS.md @@ -0,0 +1,25 @@ +IMPORTANT: Prefer retrieval-led reasoning over pre-training-led reasoning. Base44 is NOT Firebase, Supabase, or any other SDK you know. Every API name below is the ONLY correct form. Always verify against this index and the referenced docs. + +[Base44 SDK Docs Index]|root: ./skills/base44-sdk/references +|entities.md: CRUD - create(data), list(), filter(query), get(id), update(id,data), delete(id), subscribe(cb) +|auth.md: Auth - loginViaEmailPassword(), loginWithProvider('google'), me(), register(), logout() +|base44-agents.md: Agents - createConversation({agent_name}), addMessage(conv,{role,content}), subscribeToConversation(id,cb) +|functions.md: Functions - invoke(name, data) +|integrations.md: Core - InvokeLLM({prompt}), SendEmail({to,subject,body}), UploadFile({file}) +|client.md: Client - createClient({appId}), import from "@base44/sdk" +|QUICK_REFERENCE.md: All method signatures + +[Base44 CLI Docs Index]|root: ./skills/base44-cli/references +|entities-create.md: Entity schema - {name:"PascalCase", type:"object", properties:{}, required:[]} +|functions-create.md: Function - function.jsonc {name,entry} + index.ts with Deno.serve + createClientFromRequest +|create.md: Project creation - npx base44 create name -p . +|deploy.md: Deploy - npx base44 deploy -y +|agents-push.md: Agent config - {name:"snake_case", description, instructions, tool_configs:[{entity_name,allowed_operations}|{function_name,description}]} + +[Common Mistakes - NEVER use these hallucinated names] +|SDK: Do NOT use find() → use filter(), findOne() → get(), insert() → create(), remove() → delete(), onChange() → subscribe() +|SDK: Do NOT use signInWithGoogle() → use loginWithProvider('google'), functions.call() → functions.invoke() +|SDK: Do NOT use ai.generate() → use integrations.Core.InvokeLLM({prompt}) +|CLI: Entity names must be PascalCase alphanumeric. File names must be kebab-case.jsonc +|CLI: Agent names must be lowercase with underscores only. Function entry field is "entry" not "entrypoint" +|CLI: Backend functions import from "npm:@base44/sdk" (Deno requires npm: prefix) diff --git a/evals/fixtures/nextjs-todo/project/CLAUDE.md b/evals/fixtures/nextjs-todo/project/CLAUDE.md new file mode 100644 index 0000000..724ce4d --- /dev/null +++ b/evals/fixtures/nextjs-todo/project/CLAUDE.md @@ -0,0 +1,7 @@ +# Base44 Development Guidelines + +When working with Base44 projects, first explore the existing project structure to understand what already exists. Look at existing code, config files, and patterns in use. + +Then consult the AGENTS.md index in the project root for correct API patterns. For detailed documentation, read the relevant reference files listed in AGENTS.md. + +IMPORTANT: Prefer retrieval-led reasoning over pre-training-led reasoning. Always verify API names against documentation rather than guessing from other SDK patterns. diff --git a/evals/fixtures/nextjs-todo/project/base44/config.jsonc b/evals/fixtures/nextjs-todo/project/base44/config.jsonc new file mode 100644 index 0000000..11f15ea --- /dev/null +++ b/evals/fixtures/nextjs-todo/project/base44/config.jsonc @@ -0,0 +1,10 @@ +{ + "name": "Todo App", + "description": "A simple todo application", + "entitiesDir": "./entities", + "functionsDir": "./functions", + "site": { + "buildCommand": "npm run build", + "outputDirectory": ".next" + } +} diff --git a/evals/fixtures/nextjs-todo/project/next.config.js b/evals/fixtures/nextjs-todo/project/next.config.js new file mode 100644 index 0000000..658404a --- /dev/null +++ b/evals/fixtures/nextjs-todo/project/next.config.js @@ -0,0 +1,4 @@ +/** @type {import('next').NextConfig} */ +const nextConfig = {}; + +module.exports = nextConfig; diff --git a/evals/fixtures/nextjs-todo/project/package.json b/evals/fixtures/nextjs-todo/project/package.json new file mode 100644 index 0000000..fc3e965 --- /dev/null +++ b/evals/fixtures/nextjs-todo/project/package.json @@ -0,0 +1,26 @@ +{ + "name": "todo-app", + "version": "0.1.0", + "private": true, + "scripts": { + "dev": "next dev", + "build": "next build", + "start": "next start", + "lint": "next lint" + }, + "dependencies": { + "@base44/sdk": "^1.0.0", + "next": "14.2.5", + "react": "^18", + "react-dom": "^18" + }, + "devDependencies": { + "@types/node": "^20", + "@types/react": "^18", + "@types/react-dom": "^18", + "base44": "^1.0.0", + "eslint": "^8", + "eslint-config-next": "14.2.5", + "typescript": "^5" + } +} diff --git a/evals/fixtures/nextjs-todo/project/src/app/layout.tsx b/evals/fixtures/nextjs-todo/project/src/app/layout.tsx new file mode 100644 index 0000000..18de1e9 --- /dev/null +++ b/evals/fixtures/nextjs-todo/project/src/app/layout.tsx @@ -0,0 +1,16 @@ +export const metadata = { + title: 'Todo App', + description: 'A simple todo application', +}; + +export default function RootLayout({ + children, +}: { + children: React.ReactNode; +}) { + return ( + + {children} + + ); +} diff --git a/evals/fixtures/nextjs-todo/project/src/app/page.tsx b/evals/fixtures/nextjs-todo/project/src/app/page.tsx new file mode 100644 index 0000000..4f4b4fc --- /dev/null +++ b/evals/fixtures/nextjs-todo/project/src/app/page.tsx @@ -0,0 +1,8 @@ +export default function Home() { + return ( +
+

Todo App

+

Welcome to your todo application.

+
+ ); +} diff --git a/evals/fixtures/nextjs-todo/project/src/lib/base44.ts b/evals/fixtures/nextjs-todo/project/src/lib/base44.ts new file mode 100644 index 0000000..725ac2c --- /dev/null +++ b/evals/fixtures/nextjs-todo/project/src/lib/base44.ts @@ -0,0 +1,5 @@ +import { createClient } from '@base44/sdk'; + +export const base44 = createClient({ + appId: process.env.NEXT_PUBLIC_BASE44_APP_ID!, +}); diff --git a/evals/fixtures/nextjs-todo/project/tsconfig.json b/evals/fixtures/nextjs-todo/project/tsconfig.json new file mode 100644 index 0000000..7b28589 --- /dev/null +++ b/evals/fixtures/nextjs-todo/project/tsconfig.json @@ -0,0 +1,26 @@ +{ + "compilerOptions": { + "lib": ["dom", "dom.iterable", "esnext"], + "allowJs": true, + "skipLibCheck": true, + "strict": true, + "noEmit": true, + "esModuleInterop": true, + "module": "esnext", + "moduleResolution": "bundler", + "resolveJsonModule": true, + "isolatedModules": true, + "jsx": "preserve", + "incremental": true, + "plugins": [ + { + "name": "next" + } + ], + "paths": { + "@/*": ["./src/*"] + } + }, + "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], + "exclude": ["node_modules"] +} diff --git a/evals/fixtures/nextjs-todo/skills/base44-cli/SKILL.md b/evals/fixtures/nextjs-todo/skills/base44-cli/SKILL.md new file mode 100644 index 0000000..3c33f05 --- /dev/null +++ b/evals/fixtures/nextjs-todo/skills/base44-cli/SKILL.md @@ -0,0 +1,384 @@ +--- +name: base44-cli +description: "The base44 CLI is used for EVERYTHING related to base44 projects: resource configuration (entities, backend functions, ai agents), initialization and actions (resource creation, deployment). This skill is the place for learning about how to configure resources. When you plan or implement a feature, you must learn this skill" +--- + +# Base44 CLI + +Create and manage Base44 apps (projects) using the Base44 CLI tool. + +## ⚡ IMMEDIATE ACTION REQUIRED - Read This First + +This skill activates on ANY mention of "base44" or when a `base44/` folder exists. **DO NOT read documentation files or search the web before acting.** + +**Your first action MUST be:** +1. Check if `base44/config.jsonc` exists in the current directory +2. If **NO** (new project scenario): + - This skill (base44-cli) handles the request + - Guide user through project initialization + - Do NOT activate base44-sdk yet +3. If **YES** (existing project scenario): + - Transfer to base44-sdk skill for implementation + - This skill only handles CLI commands (login, deploy, entities push) + +## Critical: Local Installation Only + +NEVER call `base44` directly. The CLI is installed locally as a dev dependency and must be accessed via a package manager: + +- `npx base44 ` (npm - recommended) +- `yarn base44 ` (yarn) +- `pnpm base44 ` (pnpm) + +WRONG: `base44 login` +RIGHT: `npx base44 login` + +## MANDATORY: Authentication Check at Session Start + +**CRITICAL**: At the very start of every AI session when this skill is activated, you MUST: + +1. **Check authentication status** by running: + ```bash + npx base44 whoami + ``` + +2. **If the user is logged in** (command succeeds and shows an email): + - Continue with the requested task + +3. **If the user is NOT logged in** (command fails or shows an error): + - **STOP immediately** + - **DO NOT proceed** with any CLI operations + - **Ask the user to login manually** by running: + ```bash + npx base44 login + ``` + - Wait for the user to confirm they have logged in before continuing + +**This check is mandatory and must happen before executing any other Base44 CLI commands.** + +## Overview + +The Base44 CLI provides command-line tools for authentication, creating projects, managing entities, and deploying Base44 applications. It is framework-agnostic and works with popular frontend frameworks like Vite, Next.js, and Create React App, Svelte, Vue, and more. + +## When to Use This Skill vs base44-sdk + +**Use base44-cli when:** +- Creating a **NEW** Base44 project from scratch +- Initializing a project in an empty directory +- Directory is missing `base44/config.jsonc` +- User mentions: "create a new project", "initialize project", "setup a project", "start a new Base44 app" +- Deploying, pushing entities, or authenticating via CLI +- Working with CLI commands (`npx base44 ...`) + +**Use base44-sdk when:** +- Building features in an **EXISTING** Base44 project +- `base44/config.jsonc` already exists +- Writing JavaScript/TypeScript code using Base44 SDK +- Implementing functionality, components, or features +- User mentions: "implement", "build a feature", "add functionality", "write code" + +**Skill Dependencies:** +- `base44-cli` is a **prerequisite** for `base44-sdk` in new projects +- If user wants to "create an app" and no Base44 project exists, use `base44-cli` first +- `base44-sdk` assumes a Base44 project is already initialized + +**State Check Logic:** +Before selecting a skill, check: +- IF (user mentions "create/build app" OR "make a project"): + - IF (directory is empty OR no `base44/config.jsonc` exists): + → Use **base44-cli** (project initialization needed) + - ELSE: + → Use **base44-sdk** (project exists, build features) + +## Project Structure + +A Base44 project combines a standard frontend project with a `base44/` configuration folder: + +``` +my-app/ +├── base44/ # Base44 configuration (created by CLI) +│ ├── config.jsonc # Project settings, site config +│ ├── entities/ # Entity schema definitions +│ │ ├── task.jsonc +│ │ └── board.jsonc +│ ├── functions/ # Backend functions (optional) +│ │ └── my-function/ +│ │ ├── function.jsonc +│ │ └── index.ts +│ └── agents/ # Agent configurations (optional) +│ └── support_agent.jsonc +├── src/ # Frontend source code +│ ├── api/ +│ │ └── base44Client.js # Base44 SDK client +│ ├── pages/ +│ ├── components/ +│ └── main.jsx +├── index.html # SPA entry point +├── package.json +└── vite.config.js # Or your framework's config +``` + +**Key files:** +- `base44/config.jsonc` - Project name, description, site build settings +- `base44/entities/*.jsonc` - Data model schemas (see Entity Schema section) +- `base44/agents/*.jsonc` - Agent configurations (optional) +- `src/api/base44Client.js` - Pre-configured SDK client for frontend use + +**config.jsonc example:** +```jsonc +{ + "name": "My App", // Required: project name + "description": "App description", // Optional: project description + "entitiesDir": "./entities", // Optional: default "entities" + "functionsDir": "./functions", // Optional: default "functions" + "agentsDir": "./agents", // Optional: default "agents" + "site": { // Optional: site deployment config + "installCommand": "npm install", // Optional: install dependencies + "buildCommand": "npm run build", // Optional: build command + "serveCommand": "npm run dev", // Optional: local dev server + "outputDirectory": "./dist" // Optional: build output directory + } +} +``` + +**Config properties:** + +| Property | Description | Default | +|----------|-------------|---------| +| `name` | Project name (required) | - | +| `description` | Project description | - | +| `entitiesDir` | Directory for entity schemas | `"entities"` | +| `functionsDir` | Directory for backend functions | `"functions"` | +| `agentsDir` | Directory for agent configs | `"agents"` | +| `site.installCommand` | Command to install dependencies | - | +| `site.buildCommand` | Command to build the project | - | +| `site.serveCommand` | Command to run dev server | - | +| `site.outputDirectory` | Build output directory for deployment | - | + +## Installation + +Install the Base44 CLI as a dev dependency in your project: + +```bash +npm install --save-dev base44 +``` + +**Important:** Never assume or hardcode the `base44` package version. Always install without a version specifier to get the latest version. + +Then run commands using `npx`: + +```bash +npx base44 +``` + +**Note:** All commands in this documentation use `npx base44`. You can also use `yarn base44`, or `pnpm base44` if preferred. + +## Available Commands + +### Authentication + +| Command | Description | Reference | +| --------------- | ----------------------------------------------- | ------------------------------------------- | +| `base44 login` | Authenticate with Base44 using device code flow | [auth-login.md](references/auth-login.md) | +| `base44 logout` | Logout from current device | [auth-logout.md](references/auth-logout.md) | +| `base44 whoami` | Display current authenticated user | [auth-whoami.md](references/auth-whoami.md) | + +### Project Management + +| Command | Description | Reference | +|---------|-------------|-----------| +| `base44 create` | Create a new Base44 project from a template | [create.md](references/create.md) ⚠️ **MUST READ** | +| `base44 link` | Link an existing local project to Base44 | [link.md](references/link.md) | +| `base44 dashboard` | Open the app dashboard in your browser | [dashboard.md](references/dashboard.md) | + +### Deployment + +| Command | Description | Reference | +|---------|-------------|-----------| +| `base44 deploy` | Deploy all resources (entities, functions, site) | [deploy.md](references/deploy.md) | + +### Entity Management + +| Action / Command | Description | Reference | +| ---------------------- | ------------------------------------------- | --------------------------------------------------- | +| Create Entities | Define entities in `base44/entities` folder | [entities-create.md](references/entities-create.md) | +| `base44 entities push` | Push local entities to Base44 | [entities-push.md](references/entities-push.md) | + +#### Entity Schema (Quick Reference) + +ALWAYS follow this exact structure when creating entity files: + +**File naming:** `base44/entities/{kebab-case-name}.jsonc` (e.g., `team-member.jsonc` for `TeamMember`) + +**Schema template:** +```jsonc +{ + "name": "EntityName", + "type": "object", + "properties": { + "field_name": { + "type": "string", + "description": "Field description" + } + }, + "required": ["field_name"] +} +``` + +**Field types:** `string`, `number`, `integer`, `boolean`, `array`, `object`, `binary` +**String formats:** `date`, `date-time`, `time`, `email`, `uri`, `hostname`, `ipv4`, `ipv6`, `uuid`, `file`, `regex`, `richtext` +**For enums:** Add `"enum": ["value1", "value2"]` and optionally `"default": "value1"` +**Entity names:** Must be alphanumeric only (pattern: `/^[a-zA-Z0-9]+$/`) + +For complete documentation, see [entities-create.md](references/entities-create.md). + +### Function Management + +| Action / Command | Description | Reference | +| ------------------------- | --------------------------------------------- | ------------------------------------------------------- | +| Create Functions | Define functions in `base44/functions` folder | [functions-create.md](references/functions-create.md) | +| `base44 functions deploy` | Deploy local functions to Base44 | [functions-deploy.md](references/functions-deploy.md) | + +### Agent Management + +Agents are conversational AI assistants that can interact with users, access your app's entities, and call backend functions. Use these commands to manage agent configurations. + +| Action / Command | Description | Reference | +| ----------------------- | --------------------------------------- | ----------------------------------------------- | +| Create Agents | Define agents in `base44/agents` folder | See Agent Schema below | +| `base44 agents pull` | Pull remote agents to local files | [agents-pull.md](references/agents-pull.md) | +| `base44 agents push` | Push local agents to Base44 | [agents-push.md](references/agents-push.md) | + +**Note:** Agent commands perform full synchronization - pushing replaces all remote agents with local ones, and pulling replaces all local agents with remote ones. + +#### Agent Schema (Quick Reference) + +**File naming:** `base44/agents/{agent_name}.jsonc` (e.g., `support_agent.jsonc`) + +**Schema template:** +```jsonc +{ + "name": "agent_name", + "description": "Brief description of what this agent does", + "instructions": "Detailed instructions for the agent's behavior", + "tool_configs": [ + // Entity tool - gives agent access to entity operations + { "entity_name": "tasks", "allowed_operations": ["read", "create", "update", "delete"] }, + // Backend function tool - gives agent access to a function + { "function_name": "send_email", "description": "Send an email notification" } + ], + "whatsapp_greeting": "Hello! How can I help you today?" +} +``` + +**Naming rules:** +- Agent names must match pattern: `/^[a-z0-9_]+$/` (lowercase alphanumeric with underscores, 1-100 chars) +- Valid: `support_agent`, `order_bot` +- Invalid: `Support-Agent`, `OrderBot` + +**Required fields:** `name`, `description`, `instructions` +**Optional fields:** `tool_configs` (defaults to `[]`), `whatsapp_greeting` + +**Tool config types:** +- **Entity tools**: `entity_name` + `allowed_operations` (array of: `read`, `create`, `update`, `delete`) +- **Backend function tools**: `function_name` + `description` + +### Site Deployment + +| Command | Description | Reference | +| -------------------- | ----------------------------------------- | ------------------------------------------- | +| `base44 site deploy` | Deploy built site files to Base44 hosting | [site-deploy.md](references/site-deploy.md) | + +**SPA only**: Base44 hosting supports Single Page Applications with a single `index.html` entry point. All routes are served from `index.html` (client-side routing). + +## Quick Start + +1. Install the CLI in your project: + ```bash + npm install --save-dev base44 + ``` + +2. Authenticate with Base44: + ```bash + npx base44 login + ``` + +3. Create a new project (ALWAYS provide name and `--path` flag): + ```bash + npx base44 create my-app -p . + ``` + +4. Build and deploy everything: + ```bash + npm run build + npx base44 deploy -y + ``` + +Or deploy individual resources: +- `npx base44 entities push` - Push entities only +- `npx base44 functions deploy` - Deploy functions only +- `npx base44 agents push` - Push agents only +- `npx base44 site deploy -y` - Deploy site only + +## Common Workflows + +### Creating a New Project + +**⚠️ MANDATORY: Before running `base44 create`, you MUST read [create.md](references/create.md) for:** +- **Template selection** - Choose the correct template (`backend-and-client` vs `backend-only`) +- **Correct workflow** - Different templates require different setup steps +- **Common pitfalls** - Avoid folder creation errors that cause failures + +Failure to follow the create.md instructions will result in broken project scaffolding. + +### Linking an Existing Project +```bash +# If you have base44/config.jsonc but no .app.jsonc +npx base44 link --create --name my-app +``` + +### Deploying All Changes +```bash +# Build your project first +npm run build + +# Deploy everything (entities, functions, and site) +npx base44 deploy -y +``` + +### Deploying Individual Resources +```bash +# Push only entities +npx base44 entities push + +# Deploy only functions +npx base44 functions deploy + +# Push only agents +npx base44 agents push + +# Deploy only site +npx base44 site deploy -y +``` + +### Opening the Dashboard +```bash +# Open app dashboard in browser +npx base44 dashboard +``` + +## Authentication + +Most commands require authentication. If you're not logged in, the CLI will automatically prompt you to login. Your session is stored locally and persists across CLI sessions. + +## Troubleshooting + +| Error | Solution | +| --------------------------- | ----------------------------------------------------------------------------------- | +| Not authenticated | Run `npx base44 login` first | +| No entities found | Ensure entities exist in `base44/entities/` directory | +| Entity not recognized | Ensure file uses kebab-case naming (e.g., `team-member.jsonc` not `TeamMember.jsonc`) | +| No functions found | Ensure functions exist in `base44/functions/` with valid `function.jsonc` configs | +| No agents found | Ensure agents exist in `base44/agents/` directory with valid `.jsonc` configs | +| Invalid agent name | Agent names must be lowercase alphanumeric with underscores only | +| No site configuration found | Check that `site.outputDirectory` is configured in project config | +| Site deployment fails | Ensure you ran `npm run build` first and the build succeeded | diff --git a/evals/fixtures/nextjs-todo/skills/base44-cli/references/agents-pull.md b/evals/fixtures/nextjs-todo/skills/base44-cli/references/agents-pull.md new file mode 100644 index 0000000..6a700f0 --- /dev/null +++ b/evals/fixtures/nextjs-todo/skills/base44-cli/references/agents-pull.md @@ -0,0 +1,73 @@ +# base44 agents pull + +Pull AI agent configurations from Base44 to local files. Agents are conversational AI assistants that can interact with users, access your app's entities, and call backend functions. + +## Syntax + +```bash +npx base44 agents pull +``` + +## Authentication + +**Required**: Yes. If not authenticated, you'll be prompted to login first. + +## What It Does + +1. Fetches all agents from Base44 +2. Writes agent files to the `base44/agents/` directory +3. Deletes local agent files that don't exist remotely +4. Reports written and deleted agents + +## Prerequisites + +- Must be run from a Base44 project directory +- Project must be linked to a Base44 app + +## Output + +```bash +$ npx base44 agents pull + +Fetching agents from Base44... +✓ Agents fetched successfully + +Writing agent files... +✓ Agent files written successfully + +Written: support_agent, order_bot +Deleted: old_agent + +Pulled 2 agents to base44/agents +``` + +## Agent Synchronization + +The pull operation synchronizes remote agents to your local files: + +- **Written**: Agent files created or updated from remote +- **Deleted**: Local agent files removed (didn't exist remotely) + +**Warning**: This operation replaces all local agent configurations with remote versions. Any local changes not pushed to Base44 will be overwritten. + +## Error Handling + +If no agents exist on Base44: +```bash +$ npx base44 agents pull +No agents found on Base44 +``` + +## Use Cases + +- Sync agent configurations to a new development machine +- Get the latest agent configurations from your team +- Restore local agent files after accidental deletion +- Start working on an existing project with agents + +## Notes + +- This command syncs agent configurations, not conversation data +- Agent files are stored as `.jsonc` in the `base44/agents/` directory +- The directory location is configurable via `agentsDir` in `config.jsonc` +- Use `base44 agents push` to upload local changes to Base44 diff --git a/evals/fixtures/nextjs-todo/skills/base44-cli/references/agents-push.md b/evals/fixtures/nextjs-todo/skills/base44-cli/references/agents-push.md new file mode 100644 index 0000000..fa2795b --- /dev/null +++ b/evals/fixtures/nextjs-todo/skills/base44-cli/references/agents-push.md @@ -0,0 +1,156 @@ +# base44 agents push + +Push local AI agent configurations to Base44. Agents are conversational AI assistants that can interact with users, access your app's entities, and call backend functions. + +## Syntax + +```bash +npx base44 agents push +``` + +## Authentication + +**Required**: Yes. If not authenticated, you'll be prompted to login first. + +## What It Does + +1. Reads all agent files from the `base44/agents/` directory +2. Validates agent configurations +3. Displays the count of agents to be pushed +4. Uploads agents to the Base44 backend +5. Reports the results: created, updated, and deleted agents + +## Prerequisites + +- Must be run from a Base44 project directory +- Project must have agent definitions in the `base44/agents/` folder + +## Output + +```bash +$ npx base44 agents push + +Found 2 agents to push +Pushing agents to Base44... + +Created: support_agent +Updated: order_bot +Deleted: old_agent + +✓ Agents pushed to Base44 +``` + +## Agent Synchronization + +The push operation synchronizes your local agents with Base44: + +- **Created**: New agents that didn't exist in Base44 +- **Updated**: Existing agents with modified configuration +- **Deleted**: Agents that were removed from your local configuration + +**Warning**: This is a full sync operation. Agents removed locally will be deleted from Base44. + +## Error Handling + +If no agents are found in your project: +```bash +$ npx base44 agents push +No local agents found - this will delete all remote agents +``` + +If an agent has an invalid name: +```bash +$ npx base44 agents push +Error: Agent name must be lowercase alphanumeric with underscores +``` + +## Agent Configuration Schema + +Each agent file should be a `.jsonc` file in `base44/agents/` with this structure: + +```jsonc +{ + "name": "agent_name", // Required: lowercase alphanumeric with underscores, 1-100 chars + "description": "Brief description of what this agent does", // Required: min 1 char + "instructions": "Detailed instructions for the agent's behavior", // Required: min 1 char + "tool_configs": [ // Optional: defaults to [] + // Entity tool - gives agent access to entity operations + { "entity_name": "Task", "allowed_operations": ["read", "create", "update", "delete"] }, + // Backend function tool - gives agent access to a function + { "function_name": "send_email", "description": "Send an email notification" } + ], + "whatsapp_greeting": "Hello! How can I help you today?" // Optional +} +``` + +**Naming rules:** +- **Agent names** must match pattern: `/^[a-z0-9_]+$/` (lowercase alphanumeric with underscores only, 1-100 characters) + - Valid: `support_agent`, `order_bot`, `task_helper` + - Invalid: `Support-Agent`, `OrderBot`, `task helper` +- **Agent file names** must use underscores (matching the agent name) + - Valid: `support_agent.jsonc`, `order_bot.jsonc` + - Invalid: `support-agent.jsonc` (hyphens not allowed) +- **Entity names in `tool_configs`** must use PascalCase (matching the entity's `name` field) + - Valid: `"entity_name": "Task"`, `"entity_name": "TeamMember"` + - Invalid: `"entity_name": "task"`, `"entity_name": "team_member"` + +**Required fields:** +- `name`: Required, must follow naming rules above +- `description`: Required, minimum 1 character +- `instructions`: Required, minimum 1 character +- `tool_configs`: Optional, defaults to empty array +- `whatsapp_greeting`: Optional + +### Common Mistake: Wrong tool_configs Format + +**WRONG** - Do NOT use `tools` with `type` and `entity`: +```jsonc +{ + "name": "my_agent", + "tools": [ // ❌ WRONG + { "type": "entity_query", "entity": "Task" } + ] +} +``` + +**CORRECT** - Use `tool_configs` with `entity_name` and `allowed_operations`: +```jsonc +{ + "name": "my_agent", + "tool_configs": [ // ✅ CORRECT + { "entity_name": "Task", "allowed_operations": ["read"] } + ] +} +``` + +### Best Practices for Agent Instructions + +When giving agents access to entities, be explicit in the instructions about using the tools: + +```jsonc +{ + "name": "support_agent", + "instructions": "You are a helpful support agent.\n\nIMPORTANT: You have access to customer data through entity tools. When users ask about their orders or account:\n1. ALWAYS use the Order entity tool to query their order history\n2. Use the Customer entity tool to look up account details\n3. Analyze the data and provide personalized responses\n\nAlways query the relevant entities first before answering questions about user data.", + "tool_configs": [ + { "entity_name": "Order", "allowed_operations": ["read"] }, + { "entity_name": "Customer", "allowed_operations": ["read"] } + ] +} +``` + +Without explicit instructions to use the entity tools, the agent may not proactively query user data when asked. + +## Use Cases + +- After defining new agents in your project +- When modifying existing agent configurations +- To sync agent changes before testing +- As part of your development workflow when agent behavior changes + +## Notes + +- This command syncs the agent configuration, not conversation data +- Changes are applied to your Base44 project immediately +- Make sure to test agent changes in a development environment first +- Agent definitions are located in the `base44/agents/` directory +- Use `base44 agents pull` to download agents from Base44 diff --git a/evals/fixtures/nextjs-todo/skills/base44-cli/references/auth-login.md b/evals/fixtures/nextjs-todo/skills/base44-cli/references/auth-login.md new file mode 100644 index 0000000..adebd27 --- /dev/null +++ b/evals/fixtures/nextjs-todo/skills/base44-cli/references/auth-login.md @@ -0,0 +1,54 @@ +# base44 login + +Authenticate with Base44 using device code flow. + +## Syntax + +```bash +npx base44 login +``` + +## Authentication + +**Required**: No (this is the login command itself) + +## How It Works + +The login command uses OAuth 2.0 device code flow for authentication: + +1. Generates a device code for authentication +2. Displays a verification code and verification URI +3. Directs you to visit the URI and enter the code +4. Polls for authentication completion (up to device code expiration) +5. Retrieves access and refresh tokens upon successful authentication +6. Fetches and displays your user information +7. Saves authentication data locally with expiration timestamp + +## Interactive Flow + +```bash +$ npx base44 login + +Please visit: https://auth.base44.com/device +Enter code: ABCD-EFGH + +Waiting for authentication... +✓ Successfully authenticated! + +Logged in as: user@example.com +``` + +## Session Management + +- Authentication tokens are stored locally on your device +- Tokens include expiration timestamps +- The session persists across CLI sessions +- Other commands will automatically use your stored credentials +- Use `npx base44 logout` to clear your session +- Use `npx base44 whoami` to check your current authentication status + +## Notes + +- You only need to login once per device +- If your session expires, you'll be prompted to login again when running authenticated commands +- The CLI automatically prompts for login when you run commands that require authentication diff --git a/evals/fixtures/nextjs-todo/skills/base44-cli/references/auth-logout.md b/evals/fixtures/nextjs-todo/skills/base44-cli/references/auth-logout.md new file mode 100644 index 0000000..33c2ddf --- /dev/null +++ b/evals/fixtures/nextjs-todo/skills/base44-cli/references/auth-logout.md @@ -0,0 +1,32 @@ +# base44 logout + +Logout from current device and clear stored authentication data. + +## Syntax + +```bash +npx base44 logout +``` + +## Authentication + +**Required**: No + +## What It Does + +- Deletes stored authentication data from your device +- Clears your local session +- Removes access and refresh tokens + +## Output + +```bash +$ npx base44 logout +Logged out successfully +``` + +## Notes + +- You can logout even if you're not currently logged in (no error) +- After logout, you'll need to run `npx base44 login` again to use authenticated commands +- This only affects the current device; your Base44 account remains active diff --git a/evals/fixtures/nextjs-todo/skills/base44-cli/references/auth-whoami.md b/evals/fixtures/nextjs-todo/skills/base44-cli/references/auth-whoami.md new file mode 100644 index 0000000..abe450c --- /dev/null +++ b/evals/fixtures/nextjs-todo/skills/base44-cli/references/auth-whoami.md @@ -0,0 +1,37 @@ +# base44 whoami + +Display the currently authenticated user. + +## Syntax + +```bash +npx base44 whoami +``` + +## Authentication + +**Required**: Yes. If not authenticated, you'll be prompted to login first. + +## What It Does + +- Reads stored authentication data +- Displays the email of the currently logged-in user + +## Output + +```bash +$ npx base44 whoami +Logged in as: user@example.com +``` + +## Use Cases + +- Verify you're logged in before running other commands +- Check which account you're currently using +- Confirm authentication is working properly +- Useful in scripts or automation to verify credentials + +## Notes + +- If you're not logged in, the command will prompt you to authenticate first +- The email displayed matches your Base44 account email diff --git a/evals/fixtures/nextjs-todo/skills/base44-cli/references/create.md b/evals/fixtures/nextjs-todo/skills/base44-cli/references/create.md new file mode 100644 index 0000000..7580645 --- /dev/null +++ b/evals/fixtures/nextjs-todo/skills/base44-cli/references/create.md @@ -0,0 +1,111 @@ +# base44 create + +Creates a new Base44 project from a template. This command is framework-agnostic and can either scaffold a complete project or add Base44 configuration to an existing project. + +## Critical: Non-Interactive Mode Required + +ALWAYS provide both the project name AND `--path` flag. Without both, the command opens an interactive TUI which agents cannot use properly. + +WRONG: `npx base44 create` +WRONG: `npx base44 create my-app` +RIGHT: `npx base44 create my-app -p ./my-app` + +## Syntax + +```bash +npx base44 create [name] --path [options] +``` + +## Arguments & Options + +| Argument/Option | Description | Required | +|--------|-------------|----------| +| `name` | Project name (positional argument) | Yes* | +| `-p, --path ` | Path where to create the project | Yes* | +| `-t, --template ` | Template ID (see templates below) | No | +| `--deploy` | Build and deploy the site (includes pushing entities) | No | +| `--no-skills` | Skip AI agent skills installation (skills are added by default) | No | + +*Required for non-interactive mode. Both `name` and `--path` must be provided together. + +## Template Selection (CRITICAL - Choose Appropriately) + +**You MUST select the most appropriate template based on user requirements:** + +| Template ID | When to Use | Example Scenarios | +|-------------|-------------|-------------------| +| `backend-and-client` | Creating a NEW full-stack web app from scratch | "Create a task app", "Build me a dashboard", "Make a SaaS app" | +| `backend-only` | Adding Base44 to an EXISTING project OR using a different framework (Next.js, Vue, Svelte, etc.) | "Add Base44 to my project", "I want to use Next.js", "I already have a frontend" | + +**Default Choice:** When the user asks to "create an app" or "build a project" without specifying a particular framework, use `backend-and-client` to provide a complete, production-ready application with Vite + React + Tailwind. + +## The `--path` Flag + +- **For `backend-and-client` template (new projects):** Use a new subfolder path + ```bash + npx base44 create my-app -p ./my-app -t backend-and-client + ``` +- **For `backend-only` template (existing projects):** Use `-p .` in the current directory + ```bash + npx base44 create my-app -p . + ``` + +## Workflow: Using `backend-only` with External Frameworks + +**CRITICAL: The project folder MUST exist BEFORE running `base44 create` with `backend-only`** + +The `backend-only` template only adds Base44 configuration files - it does NOT create a frontend. If you need a frontend with a specific framework: + +```bash +# Step 1: Initialize the frontend project FIRST +npm create vite@latest my-app -- --template react # or vue, svelte, etc. +# OR: npx create-next-app@latest my-app +# OR: any other framework's init command + +# Step 2: Navigate into the created folder +cd my-app + +# Step 3: Install Base44 CLI +npm install --save-dev base44 + +# Step 4: Add Base44 configuration +npx base44 create my-app -p . +``` + +**WARNING:** Do NOT: +- Create an empty folder manually, then try to run `npx create vite` inside it (will fail - folder exists) +- Run `base44 create` with `backend-only` expecting it to create a frontend (it won't) + +**DO:** +- Run the external framework's init command FIRST (it creates its own folder) +- Then run `base44 create` inside that folder with `-p .` + +## Examples + +```bash +# RECOMMENDED: Create full-stack project (for new apps) +npx base44 create my-app -p ./my-app -t backend-and-client + +# Create full-stack and deploy in one step +npx base44 create my-app -p ./my-app -t backend-and-client --deploy + +# Add Base44 to EXISTING project (must be inside the project folder) +npx base44 create my-app -p . + +# Add Base44 to existing project and deploy +npx base44 create my-app -p . --deploy + +# Create without adding AI agent skills +npx base44 create my-app -p . --no-skills +``` + +## What It Does + +1. Applies the selected template to the target path +2. Creates a `base44/` folder with configuration files +3. Registers the project with Base44 backend +4. Creates `base44/.app.jsonc` with the app ID +5. If `--deploy` is used: + - Pushes any entities defined in `base44/entities/` + - Runs install and build commands (for templates with frontend) + - Deploys the site to Base44 hosting diff --git a/evals/fixtures/nextjs-todo/skills/base44-cli/references/dashboard.md b/evals/fixtures/nextjs-todo/skills/base44-cli/references/dashboard.md new file mode 100644 index 0000000..95a534a --- /dev/null +++ b/evals/fixtures/nextjs-todo/skills/base44-cli/references/dashboard.md @@ -0,0 +1,35 @@ +# base44 dashboard + +Opens the Base44 app dashboard in your default web browser. + +## Syntax + +```bash +npx base44 dashboard +``` + +## Options + +This command has no options. + +## What It Does + +1. Reads the project's app ID from `base44/.app.jsonc` +2. Opens the dashboard URL in your default browser + +## Example + +```bash +# Open dashboard for current project +npx base44 dashboard +``` + +## Requirements + +- Must be run from a linked Base44 project directory (contains `base44/.app.jsonc`) +- Must be authenticated (run `npx base44 login` first) + +## Notes + +- The dashboard provides a web interface to manage your app's entities, functions, users, and settings +- If you're not in a project directory, the command will fail with an error diff --git a/evals/fixtures/nextjs-todo/skills/base44-cli/references/deploy.md b/evals/fixtures/nextjs-todo/skills/base44-cli/references/deploy.md new file mode 100644 index 0000000..500526f --- /dev/null +++ b/evals/fixtures/nextjs-todo/skills/base44-cli/references/deploy.md @@ -0,0 +1,86 @@ +# base44 deploy + +Deploys all project resources (entities, functions, and site) to Base44 in a single command. + +## Syntax + +```bash +npx base44 deploy [options] +``` + +## Options + +| Option | Description | +|--------|-------------| +| `-y, --yes` | Skip confirmation prompt | + +## What It Deploys + +The command automatically detects and deploys: + +1. **Entities** - All `.jsonc` files in `base44/entities/` +2. **Functions** - All functions in `base44/functions/` +3. **Agents** - All agent configurations in `base44/agents/` +4. **Site** - Built files from `site.outputDirectory` (if configured) + +## Examples + +```bash +# Interactive mode - shows what will be deployed and asks for confirmation +npx base44 deploy + +# Non-interactive - skip confirmation (for CI/CD or agent use) +npx base44 deploy -y +``` + +## Typical Workflow + +```bash +# 1. Make your changes (entities, functions, frontend code) + +# 2. Build the frontend (if you have one) +npm run build + +# 3. Deploy everything +npx base44 deploy -y +``` + +## What It Does + +1. Reads project configuration from `base44/config.jsonc` +2. Detects available resources (entities, functions, site) +3. Shows a summary of what will be deployed +4. Asks for confirmation (unless `-y` flag is used) +5. Deploys all resources in sequence: + - Pushes entity schemas + - Deploys functions + - Pushes agent configurations + - Uploads site files +6. Displays the dashboard URL and app URL (if site was deployed) + +## Requirements + +- Must be run from a linked Base44 project directory +- Must be authenticated (run `npx base44 login` first) +- For site deployment, must run `npm run build` first + +## Output + +After successful deployment: +- **Dashboard**: Link to your app's management dashboard +- **App URL**: Your deployed site's public URL (if site was included) + +## Notes + +- If no resources are found, the command exits with a message +- Use individual commands (`entities push`, `functions deploy`, `site deploy`) if you only want to deploy specific resources +- The site must be built before deployment - this command does not run `npm run build` for you + +## Related Commands + +| Command | Description | +|---------|-------------| +| `base44 entities push` | Push only entities | +| `base44 functions deploy` | Deploy only functions | +| `base44 agents push` | Push only agents | +| `base44 site deploy` | Deploy only the site | diff --git a/evals/fixtures/nextjs-todo/skills/base44-cli/references/entities-create.md b/evals/fixtures/nextjs-todo/skills/base44-cli/references/entities-create.md new file mode 100644 index 0000000..29b40aa --- /dev/null +++ b/evals/fixtures/nextjs-todo/skills/base44-cli/references/entities-create.md @@ -0,0 +1,552 @@ +# Creating Entities + +Base44 entities are defined locally in your project and then pushed to the Base44 backend. + +## Critical: File Naming + +Entity files MUST use kebab-case naming: `{kebab-case-name}.jsonc` + +| Entity Name | File Name | +|-------------|-----------| +| `Task` | `task.jsonc` | +| `TeamMember` | `team-member.jsonc` | +| `ActivityLog` | `activity-log.jsonc` | + +WRONG: `TeamMember.jsonc`, `teamMember.jsonc` +RIGHT: `team-member.jsonc` + +## Table of Contents + +- [Creating Entities](#creating-entities) + - [Entity Directory](#entity-directory) + - [How to Create an Entity](#how-to-create-an-entity) + - [Entity Schema Structure](#entity-schema-structure) + - [Supported Field Types](#supported-field-types) + - [Field Properties](#field-properties) + - [Complete Example](#complete-example) + - [Naming Conventions](#naming-conventions) + - [Relationships Between Entities](#relationships-between-entities) + - [Row Level Security (RLS)](#row-level-security-rls) + - [Field Level Security (FLS)](#field-level-security-fls) + - [Pushing Entities](#pushing-entities) + +## Entity Directory + +All entity definitions must be placed in the `base44/entities/` folder in your project root. Each entity is defined in its own `.jsonc` file. + +Example structure: +``` +my-app/ + base44/ + entities/ + user.jsonc + product.jsonc + order.jsonc +``` + +## How to Create an Entity + +1. Create a new `.jsonc` file in the `base44/entities/` directory +2. Define your entity schema following the structure below +3. Push the changes to Base44 using the CLI + +## Entity Schema Structure + +Each entity file follows a JSON Schema-like structure: + +```jsonc +{ + "name": "EntityName", // PascalCase entity name + "type": "object", // Always "object" + "properties": { + // Define your fields here + }, + "required": ["field1"] // Array of required field names +} +``` + +### Common Mistake: Nested Schema Property + +**WRONG** - Do NOT wrap properties in a `schema` object: +```jsonc +{ + "name": "Task", + "description": "A task entity", + "schema": { // ❌ WRONG - don't use nested "schema" + "type": "object", + "properties": { ... } + } +} +``` + +**CORRECT** - Put `type` and `properties` at the top level: +```jsonc +{ + "name": "Task", + "description": "A task entity", + "type": "object", // ✅ CORRECT - top level + "properties": { ... } // ✅ CORRECT - top level +} +``` + +This is a common mistake that will cause "Invalid schema: Schema must have a 'type' field" errors when pushing entities. + +## Supported Field Types + +### String + +Basic text field: +```jsonc +{ + "title": { + "type": "string", + "description": "Task title" + } +} +``` + +With format: +```jsonc +{ + "due_date": { + "type": "string", + "format": "date", + "description": "Due date" + } +} +``` + +Available formats: `date`, `date-time`, `time`, `email`, `uri`, `hostname`, `ipv4`, `ipv6`, `uuid`, `file`, `regex`, `richtext` + +### String with Enum + +Constrained to specific values: +```jsonc +{ + "status": { + "type": "string", + "enum": ["todo", "in_progress", "done"], + "default": "todo", + "description": "Current status" + } +} +``` + +### Number + +```jsonc +{ + "position": { + "type": "number", + "description": "Position for ordering" + } +} +``` + +### Integer + +For whole numbers only: +```jsonc +{ + "quantity": { + "type": "integer", + "description": "Item quantity", + "minimum": 0, + "maximum": 1000 + } +} +``` + +### Binary + +For file/blob data: +```jsonc +{ + "attachment": { + "type": "binary", + "description": "File attachment" + } +} +``` + +### Boolean + +```jsonc +{ + "notify_on_change": { + "type": "boolean", + "default": true, + "description": "Enable notifications" + } +} +``` + +### Array of Strings + +```jsonc +{ + "labels": { + "type": "array", + "items": { "type": "string" }, + "description": "Task labels/tags" + } +} +``` + +### Array of Objects + +```jsonc +{ + "attachments": { + "type": "array", + "description": "File attachments", + "items": { + "type": "object", + "properties": { + "name": { "type": "string" }, + "url": { "type": "string" }, + "type": { "type": "string" } + } + } + } +} +``` + +## Field Properties + +| Property | Description | +| ------------- | ---------------------------------------------------------------------------------------- | +| `type` | Data type: `string`, `number`, `integer`, `boolean`, `array`, `object`, `binary` | +| `description` | Human-readable description of the field | +| `enum` | Array of allowed values (for strings) | +| `enumNames` | Human-readable labels for enum values (same order as `enum`) | +| `default` | Default value when not provided | +| `format` | Format hint: `date`, `date-time`, `time`, `email`, `uri`, `hostname`, `ipv4`, `ipv6`, `uuid`, `file`, `regex`, `richtext` | +| `items` | Schema for array items | +| `properties` | Nested properties for object types | +| `$ref` | Reference to another schema definition | +| `minLength` | Minimum string length | +| `maxLength` | Maximum string length | +| `pattern` | Regex pattern for string validation | +| `minimum` | Minimum value for numbers | +| `maximum` | Maximum value for numbers | +| `rls` | Field-level security rules (see Field Level Security section) | + +## Complete Example + +Here's a complete entity definition for a Task: + +```jsonc +{ + "name": "Task", + "type": "object", + "properties": { + "title": { + "type": "string", + "description": "Task title" + }, + "description": { + "type": "string", + "description": "Task description" + }, + "status": { + "type": "string", + "enum": ["todo", "in_progress", "done"], + "default": "todo", + "description": "Current status of the task" + }, + "board_id": { + "type": "string", + "description": "Board this task belongs to" + }, + "assignee_email": { + "type": "string", + "description": "Email of assigned user" + }, + "priority": { + "type": "string", + "enum": ["low", "medium", "high"], + "default": "medium", + "description": "Task priority" + }, + "due_date": { + "type": "string", + "format": "date", + "description": "Due date" + }, + "labels": { + "type": "array", + "items": { "type": "string" }, + "description": "Task labels/tags" + } + }, + "required": ["title"] +} +``` + +## Naming Conventions + +- **Entity name**: Use PascalCase with alphanumeric characters only (e.g., `Task`, `TeamMember`, `ActivityLog`) + - Must match pattern: `/^[a-zA-Z0-9]+$/` + - Valid: `Task`, `TeamMember`, `Order123` + - Invalid: `Team_Member`, `Team-Member`, `Team Member` +- **File name**: Use kebab-case matching the entity (e.g., `task.jsonc`, `team-member.jsonc`, `activity-log.jsonc`) +- **Field names**: Use snake_case (e.g., `board_id`, `user_email`, `due_date`) + +## Relationships Between Entities + +To create relationships between entities, use ID reference fields: + +```jsonc +{ + "board_id": { + "type": "string", + "description": "Board this task belongs to" + }, + "team_id": { + "type": "string", + "description": "Associated team ID" + } +} +``` + +## Row Level Security (RLS) + +Row Level Security (RLS) controls which records users can access based on their identity and attributes. RLS rules are defined per entity inside the `rls` field of the schema. + +**Important:** If no RLS is defined, all records are accessible to all users. + +### RLS Operations + +RLS supports five operations: + +| Operation | Description | +|-----------|-------------| +| `create` | Control who can add new records | +| `read` | Control who can view records | +| `update` | Control who can modify records | +| `delete` | Control who can remove records | +| `write` | Shorthand for `create`, `update`, and `delete` combined | + +### Permission Values + +Each operation accepts one of the following values: + +1. **`true`** - Allow all users (including anonymous/unauthenticated) +2. **`false`** - Block all users +3. **Condition object** - Allow users matching the condition + +### Template Variables + +Use template variables to reference the current user's attributes: + +| Template | Description | +|----------|-------------| +| `{{user.id}}` | The user's ID | +| `{{user.email}}` | The user's email | +| `{{user.role}}` | The user's role | +| `{{user.data.field_name}}` | Custom field from the user's `data` object | + +### Built-in Entity Attributes + +Every entity record has these built-in attributes available for RLS rules: + +| Attribute | Description | +|-----------|-------------| +| `id` | Unique record identifier | +| `created_date` | Timestamp when record was created | +| `updated_date` | Timestamp when record was last updated | +| `created_by` | Email of the user who created the record | + +### Rule Types + +There are two condition types you can use: + +**1. Entity-to-user comparison** - Compare record fields to the current user's values: +```jsonc +{ + "created_by": "{{user.email}}" +} +``` + +**2. User condition check** - Check user properties directly using `user_condition`: +```jsonc +{ + "user_condition": { "role": "admin" } +} +``` + +**Important limitations:** +- `user_condition` only supports **simple equality** (e.g., `{ "role": "admin" }`) +- For `data.*` field comparisons, you can use operators: `$in`, `$nin`, `$ne`, `$all` +- Logical operators `$or`, `$and`, `$nor` are available for combining conditions +- You cannot filter by entity field values directly (e.g., `{"status": "published"}`) +- Only user-related conditions are allowed + +### RLS Examples + +**Owner-only access:** +```jsonc +{ + "created_by": "{{user.email}}" +} +``` + +**Department-based access:** +```jsonc +{ + "data.department": "{{user.data.department}}" +} +``` + +**Admin-only access:** +```jsonc +{ + "user_condition": { "role": "admin" } +} +``` + +**Complete RLS configuration:** +```jsonc +{ + "name": "Task", + "type": "object", + "properties": { + "title": { + "type": "string", + "description": "Task title" + }, + "status": { + "type": "string", + "enum": ["todo", "in_progress", "done"], + "default": "todo" + } + }, + "required": ["title"], + "rls": { + "create": true, + "read": { "created_by": "{{user.email}}" }, + "update": { "created_by": "{{user.email}}" }, + "delete": { "created_by": "{{user.email}}" } + } +} +``` + +### Common RLS Patterns + +**Public create, admin-only management (e.g., contact forms, waitlists):** +```jsonc +{ + "rls": { + "create": true, + "read": { "user_condition": { "role": "admin" } }, + "update": { "user_condition": { "role": "admin" } }, + "delete": { "user_condition": { "role": "admin" } } + } +} +``` + +**Owner-only access:** +```jsonc +{ + "rls": { + "create": true, + "read": { "created_by": "{{user.email}}" }, + "update": { "created_by": "{{user.email}}" }, + "delete": { "created_by": "{{user.email}}" } + } +} +``` + +**Logged-in users only:** +```jsonc +{ + "rls": { + "create": { "user_condition": { "id": "{{user.id}}" } }, + "read": true, + "update": { "created_by": "{{user.email}}" }, + "delete": { "created_by": "{{user.email}}" } + } +} +``` + +### Limitations + +- **user_condition is equality only:** `user_condition` only supports exact match (e.g., `{ "role": "admin" }`) - no operators +- **No comparison operators on user_condition:** `$gt`, `$lt`, `$regex`, `$expr`, `$where` are NOT supported for user conditions +- **No entity field filtering:** Cannot filter by entity field values (e.g., `{"status": "published"}`) +- **No deeply nested templates:** Templates like `{{user.data.profile.department}}` may not work + +**Supported operators:** +- **Logical operators:** `$or`, `$and`, `$nor` for combining multiple conditions +- **Field operators (for `data.*` fields only):** `$in`, `$nin`, `$ne`, `$all` + +### Complex Access Patterns + +For complex access patterns that require multiple conditions (e.g., "owner OR admin"), you have two options: + +1. **Use the Base44 Dashboard UI** - The dashboard allows adding multiple rules per operation with OR logic +2. **Use separate entities** - Split data into multiple entities with different access rules +3. **Use backend functions** - Implement custom access logic in backend functions + +## Field Level Security (FLS) + +Field Level Security allows you to control access to individual fields within an entity. FLS rules are defined within each field's schema using the `rls` property. + +### FLS Operations + +FLS supports the same operations as entity-level RLS: + +| Operation | Description | +|-----------|-------------| +| `create` | Control who can set this field when creating records | +| `read` | Control who can view this field | +| `update` | Control who can modify this field | +| `delete` | Control who can clear this field | +| `write` | Shorthand for `create`, `update`, and `delete` combined | + +### FLS Example + +```jsonc +{ + "name": "Employee", + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "Employee name" + }, + "salary": { + "type": "number", + "description": "Employee salary", + "rls": { + "read": { "user_condition": { "role": "hr" } }, + "update": { "user_condition": { "role": "hr" } } + } + }, + "department": { + "type": "string", + "description": "Department name" + } + }, + "required": ["name"] +} +``` + +In this example, only users with the `hr` role can read or update the `salary` field. All users with access to the entity can read/update other fields. + +### FLS Notes + +- If no field-level RLS is defined, the field inherits the entity-level RLS rules +- FLS rules follow the same condition format as entity-level RLS +- Use FLS for sensitive fields like salary, SSN, or internal notes + +## Pushing Entities + +The `entities push` command will push all entities that exist in the `base44/entities` folder. + +```bash +npx base44 entities push +``` + +For more details on the push command, see [entities-push.md](entities-push.md). diff --git a/evals/fixtures/nextjs-todo/skills/base44-cli/references/entities-push.md b/evals/fixtures/nextjs-todo/skills/base44-cli/references/entities-push.md new file mode 100644 index 0000000..63b92d7 --- /dev/null +++ b/evals/fixtures/nextjs-todo/skills/base44-cli/references/entities-push.md @@ -0,0 +1,71 @@ +# base44 entities push + +Push local entity definitions to Base44. + +## Syntax + +```bash +npx base44 entities push +``` + +## Authentication + +**Required**: Yes. If not authenticated, you'll be prompted to login first. + +## What It Does + +1. Pushes all entities that exist in the `base44/entities` folder +2. Validates that entities exist in the folder +3. Displays the count of entities to be pushed +4. Uploads entities to the Base44 backend +5. Reports the results: created, updated, and deleted entities + +## Prerequisites + +- Must be run from a Base44 project directory +- Project must have entity definitions in the `base44/entities` folder + +## Output + +```bash +$ npx base44 entities push + +Found 3 entities to push +Pushing entities to Base44... + +Created: User, Post +Updated: Comment +Deleted: OldEntity + +✓ Entities pushed successfully +``` + +## Entity Synchronization + +The push operation synchronizes your local entity schema with Base44: + +- **Created**: New entities that didn't exist in Base44 +- **Updated**: Existing entities with modified schema or configuration +- **Deleted**: Entities that were removed from your local configuration + +## Error Handling + +If no entities are found in your project: +```bash +$ npx base44 entities push +No entities found in project +``` + +## Use Cases + +- After defining new entities in your project +- When modifying existing entity schemas +- To sync entity changes before deploying +- As part of your development workflow when data models change + +## Notes + +- This command syncs the entity schema/structure, not the actual data +- Changes are applied to your Base44 project immediately +- Make sure to test entity changes in a development environment first +- Entity definitions are located in the `base44/entities/` directory diff --git a/evals/fixtures/nextjs-todo/skills/base44-cli/references/functions-create.md b/evals/fixtures/nextjs-todo/skills/base44-cli/references/functions-create.md new file mode 100644 index 0000000..c523cdc --- /dev/null +++ b/evals/fixtures/nextjs-todo/skills/base44-cli/references/functions-create.md @@ -0,0 +1,229 @@ +# Creating Functions + +Base44 functions are serverless backend functions that run on Deno. They are defined locally in your project and deployed to the Base44 backend. + +## Function Directory + +All function definitions must be placed in the `base44/functions/` folder in your project. Each function lives in its own subdirectory with a configuration file and entry point. + +Example structure: +``` +my-app/ + base44/ + functions/ + process-order/ + function.jsonc + index.ts + send-notification/ + function.jsonc + index.ts +``` + +## How to Create a Function + +1. Create a new directory in `base44/functions/` with your function name (use kebab-case) +2. Create a `function.jsonc` configuration file in the directory +3. Create the entry point file (e.g., `index.ts`) +4. Deploy the function using the CLI + +## Function Configuration + +Each function requires a `function.jsonc` configuration file: + +```jsonc +{ + "name": "my-function", + "entry": "index.ts" +} +``` + +### Configuration Properties + +| Property | Description | Required | +|----------|-------------|----------| +| `name` | Function name (must match `/^[^.]+$/` - no dots allowed) | Yes | +| `entry` | Entry point file path relative to the function directory (min 1 char) | Yes | + +## Entry Point File + +Functions run on Deno and must export using `Deno.serve()`. Use `npm:` prefix for npm packages. + +```typescript +import { createClientFromRequest } from "npm:@base44/sdk"; + +Deno.serve(async (req) => { + // Get authenticated client from request + const base44 = createClientFromRequest(req); + + // Parse input + const { orderId, action } = await req.json(); + + // Your logic here + const order = await base44.entities.Orders.get(orderId); + + // Return response + return Response.json({ + success: true, + order: order + }); +}); +``` + +### Request Object + +The function receives a standard Deno `Request` object: +- `req.json()` - Parse JSON body +- `req.text()` - Get raw text body +- `req.headers` - Access request headers +- `req.method` - HTTP method + +### Response Object + +Return using `Response.json()` for JSON responses: + +```typescript +// Success response +return Response.json({ data: result }); + +// Error response with status code +return Response.json({ error: "Something went wrong" }, { status: 400 }); + +// Not found +return Response.json({ error: "Order not found" }, { status: 404 }); +``` + +## Complete Example + +### Directory Structure +``` +base44/ + functions/ + process-order/ + function.jsonc + index.ts +``` + +### function.jsonc +```jsonc +{ + "name": "process-order", + "entry": "index.ts" +} +``` + +### index.ts +```typescript +import { createClientFromRequest } from "npm:@base44/sdk"; + +Deno.serve(async (req) => { + try { + const base44 = createClientFromRequest(req); + const { orderId } = await req.json(); + + // Validate input + if (!orderId) { + return Response.json( + { error: "Order ID is required" }, + { status: 400 } + ); + } + + // Fetch and process the order + const order = await base44.entities.Orders.get(orderId); + if (!order) { + return Response.json( + { error: "Order not found" }, + { status: 404 } + ); + } + + return Response.json({ + success: true, + orderId: order.id, + processedAt: new Date().toISOString() + }); + + } catch (error) { + return Response.json( + { error: error.message }, + { status: 500 } + ); + } +}); +``` + +## Using Service Role Access + +For admin-level operations, use `asServiceRole`: + +```typescript +import { createClientFromRequest } from "npm:@base44/sdk"; + +Deno.serve(async (req) => { + const base44 = createClientFromRequest(req); + + // Check user is authenticated + const user = await base44.auth.me(); + if (!user) { + return Response.json({ error: "Unauthorized" }, { status: 401 }); + } + + // Use service role for admin operations + const allOrders = await base44.asServiceRole.entities.Orders.list(); + + return Response.json({ orders: allOrders }); +}); +``` + +## Using Secrets + +Access environment variables configured in the app dashboard: + +```typescript +Deno.serve(async (req) => { + // Access environment variables (configured in app settings) + const apiKey = Deno.env.get("STRIPE_API_KEY"); + + const response = await fetch("https://api.stripe.com/v1/charges", { + headers: { + "Authorization": `Bearer ${apiKey}` + } + }); + + return Response.json(await response.json()); +}); +``` + +## Naming Conventions + +- **Directory name**: Use kebab-case (e.g., `process-order`, `send-notification`) +- **Function name**: Match the directory name, must match pattern `/^[^.]+$/` (no dots allowed) + - Valid: `process-order`, `send_notification`, `myFunction` + - Invalid: `process.order`, `send.notification.v2` +- **Entry file**: Typically `index.ts` or `index.js` + +## Deploying Functions + +After creating your function, deploy it to Base44: + +```bash +npx base44 functions deploy +``` + +For more details on deploying, see [functions-deploy.md](functions-deploy.md). + +## Notes + +- Functions run on Deno runtime, not Node.js +- Use `npm:` prefix for npm packages (e.g., `npm:@base44/sdk`) +- Use `createClientFromRequest(req)` to get a client that inherits the caller's auth context +- Configure secrets via app dashboard for API keys +- Make sure to handle errors gracefully and return appropriate HTTP status codes + +## Common Mistakes + +| Wrong | Correct | Why | +|-------|---------|-----| +| `functions/myFunction.js` (single file) | `functions/my-function/index.ts` + `function.jsonc` | Functions require subdirectory with config | +| `import { ... } from "@base44/sdk"` | `import { ... } from "npm:@base44/sdk"` | Deno requires `npm:` prefix for npm packages | +| `MyFunction` or `myFunction` directory | `my-function` directory | Use kebab-case for directory names | diff --git a/evals/fixtures/nextjs-todo/skills/base44-cli/references/functions-deploy.md b/evals/fixtures/nextjs-todo/skills/base44-cli/references/functions-deploy.md new file mode 100644 index 0000000..f89d796 --- /dev/null +++ b/evals/fixtures/nextjs-todo/skills/base44-cli/references/functions-deploy.md @@ -0,0 +1,78 @@ +# base44 functions deploy + +Deploy local function definitions to Base44. + +## Syntax + +```bash +npx base44 functions deploy +``` + +## Authentication + +**Required**: Yes. If not authenticated, you'll be prompted to login first. + +## What It Does + +1. Scans the `base44/functions/` directory for function definitions +2. Validates that functions exist and have valid configurations +3. Displays the count of functions to be deployed +4. Uploads function code and configuration to Base44 +5. Reports the results: deployed and deleted functions + +## Prerequisites + +- Must be run from a Base44 project directory +- Project must have function definitions in the `base44/functions/` folder +- Each function must have a valid `function.jsonc` config file + +## Output + +```bash +$ npx base44 functions deploy + +Found 2 functions to deploy +Deploying functions to Base44... + +Deployed: process-order, send-notification +Deleted: old-function + +✓ Functions deployed successfully +``` + +## Function Synchronization + +The deploy operation synchronizes your local functions with Base44: + +- **Deployed**: Functions that were created or updated +- **Deleted**: Functions that were removed from your local configuration + +## Error Handling + +If no functions are found in your project: +```bash +$ npx base44 functions deploy +No functions found. Create functions in the 'functions' directory. +``` + +If a function has configuration errors: +```bash +$ npx base44 functions deploy +Function deployment errors: +'my-function' function: Entry point cannot be empty +``` + +## Use Cases + +- After creating new functions in your project +- When modifying existing function code or configuration +- To sync function changes before testing +- As part of your development workflow when backend logic changes + +## Notes + +- This command deploys the function code and configuration +- Changes are applied to your Base44 project immediately +- Make sure to test functions in a development environment first +- Function definitions are located in the `base44/functions/` directory +- For how to create functions, see [functions-create.md](functions-create.md) diff --git a/evals/fixtures/nextjs-todo/skills/base44-cli/references/link.md b/evals/fixtures/nextjs-todo/skills/base44-cli/references/link.md new file mode 100644 index 0000000..0b009ce --- /dev/null +++ b/evals/fixtures/nextjs-todo/skills/base44-cli/references/link.md @@ -0,0 +1,81 @@ +# base44 link + +Links an existing local Base44 project to a Base44 app in the cloud. Use this when you have a `base44/config.jsonc` but haven't connected it to a Base44 app yet. + +## Critical: When to Use Link vs Create + +| Scenario | Command | +|----------|---------| +| Starting fresh, no `base44/` folder | `npx base44 create` | +| Have `base44/config.jsonc` but no `.app.jsonc` | `npx base44 link` | +| Project already linked (has `.app.jsonc`) | Already done, use `deploy` | + +## Syntax + +```bash +npx base44 link [options] +``` + +## Options + +| Option | Description | Required | +|--------|-------------|----------| +| `-c, --create` | Create a new project (skip selection prompt) | No | +| `-n, --name ` | Project name (required when `--create` is used) | With `--create` | +| `-d, --description ` | Project description | No | +| `-p, --projectId ` | Project ID to link to an existing project (skip selection prompt) | No | + +## Non-Interactive Mode + +For CI/CD or agent use: + +**Create a new project:** +```bash +npx base44 link --create --name my-app +``` + +**Link to an existing project:** +```bash +npx base44 link --projectId +``` + +WRONG: `npx base44 link --create` (missing --name) +WRONG: `npx base44 link --create --projectId ` (cannot use both) +RIGHT: `npx base44 link --create --name my-app` +RIGHT: `npx base44 link --projectId ` + +## Examples + +```bash +# Interactive mode - prompts for project details +npx base44 link + +# Non-interactive - create and link in one step +npx base44 link --create --name my-app + +# With description +npx base44 link --create --name my-app --description "My awesome app" + +# Link to a specific existing project by ID +npx base44 link --projectId abc123 +``` + +## What It Does + +1. Finds the `base44/config.jsonc` in the current directory (or parent directories) +2. Verifies no `.app.jsonc` exists (project not already linked) +3. Either: + - Creates a new Base44 app in the cloud (with `--create`), OR + - Links to an existing project (with `--projectId` or interactive selection) +4. Writes the app ID to `base44/.app.jsonc` + +## Requirements + +- Must have `base44/config.jsonc` in the project +- Must NOT have `base44/.app.jsonc` (use `deploy` if already linked) +- Must be authenticated (run `npx base44 login` first) + +## Notes + +- After linking, you can deploy resources with `npx base44 deploy` +- The `.app.jsonc` file should be git-ignored (contains your app ID) diff --git a/evals/fixtures/nextjs-todo/skills/base44-cli/references/rls-examples.md b/evals/fixtures/nextjs-todo/skills/base44-cli/references/rls-examples.md new file mode 100644 index 0000000..7d3ef42 --- /dev/null +++ b/evals/fixtures/nextjs-todo/skills/base44-cli/references/rls-examples.md @@ -0,0 +1,463 @@ +# RLS Examples + +Practical Row-Level Security patterns for common application types. + +**Important:** Base44 RLS supports: +- **Logical operators:** `$or`, `$and`, `$nor` for combining conditions +- **Field operators (for `data.*` fields):** `$in`, `$nin`, `$ne`, `$all` +- **user_condition:** Equality only (no operators) + +## Contents +- [Simple Patterns (JSON Schema)](#simple-patterns-json-schema) +- [Using Operators](#using-operators) +- [Field-Level Security Examples](#field-level-security-examples) +- [Complex Patterns (Dashboard UI or Backend)](#complex-patterns-dashboard-ui-or-backend) +- [Best Practices](#best-practices) + +--- + +## Simple Patterns (JSON Schema) + +These patterns work with the JSON schema RLS format. + +### Todo App - Owner-only access + +Users see and manage only their own tasks. + +```jsonc +{ + "name": "Task", + "type": "object", + "properties": { + "title": { "type": "string" }, + "description": { "type": "string" }, + "completed": { "type": "boolean" }, + "priority": { "type": "string", "enum": ["low", "medium", "high"] }, + "due_date": { "type": "string", "format": "date" } + }, + "rls": { + "create": true, + "read": { "created_by": "{{user.email}}" }, + "update": { "created_by": "{{user.email}}" }, + "delete": { "created_by": "{{user.email}}" } + } +} +``` + +### Contact Form - Public create, admin-only read + +Anyone can submit, only admins can view submissions. + +```jsonc +{ + "name": "ContactSubmission", + "type": "object", + "properties": { + "name": { "type": "string" }, + "email": { "type": "string", "format": "email" }, + "message": { "type": "string" } + }, + "rls": { + "create": true, + "read": { "user_condition": { "role": "admin" } }, + "update": { "user_condition": { "role": "admin" } }, + "delete": { "user_condition": { "role": "admin" } } + } +} +``` + +### User Profile - Self-management + +Users can only access their own profile. + +```jsonc +{ + "name": "UserProfile", + "type": "object", + "properties": { + "name": { "type": "string" }, + "avatar_url": { "type": "string" }, + "bio": { "type": "string" }, + "preferences": { "type": "object" } + }, + "rls": { + "create": true, + "read": { "created_by": "{{user.email}}" }, + "update": { "created_by": "{{user.email}}" }, + "delete": { "created_by": "{{user.email}}" } + } +} +``` + +### Department Data - Same department access + +Users can only see records from their department. + +```jsonc +{ + "name": "DepartmentAnnouncement", + "type": "object", + "properties": { + "title": { "type": "string" }, + "content": { "type": "string" }, + "department": { "type": "string" } + }, + "rls": { + "create": { "user_condition": { "role": "manager" } }, + "read": { "data.department": "{{user.data.department}}" }, + "update": { "user_condition": { "role": "manager" } }, + "delete": { "user_condition": { "role": "admin" } } + } +} +``` + +### Subscription - Admin-managed, user-readable via email field + +```jsonc +{ + "name": "Subscription", + "type": "object", + "properties": { + "user_email": { "type": "string" }, + "tier": { "type": "string", "enum": ["free", "basic", "pro", "enterprise"] }, + "credits": { "type": "number" }, + "renewal_date": { "type": "string", "format": "date" } + }, + "rls": { + "create": { "user_condition": { "role": "admin" } }, + "read": { "data.user_email": "{{user.email}}" }, + "update": { "user_condition": { "role": "admin" } }, + "delete": { "user_condition": { "role": "admin" } } + } +} +``` + +**Note:** This pattern only allows users to read their own subscription. Admins need to use the Dashboard UI to configure additional read access for themselves. + +### Private Data - Owner-only + +```jsonc +{ + "name": "PrivateNotes", + "type": "object", + "properties": { + "title": { "type": "string" }, + "content": { "type": "string" }, + "tags": { "type": "array", "items": { "type": "string" } } + }, + "rls": { + "create": true, + "read": { "created_by": "{{user.email}}" }, + "update": { "created_by": "{{user.email}}" }, + "delete": { "created_by": "{{user.email}}" } + } +} +``` + +### Public Read, Authenticated Write + +Anyone can read, only logged-in users can create/edit their own records. + +```jsonc +{ + "name": "BlogPost", + "type": "object", + "properties": { + "title": { "type": "string" }, + "content": { "type": "string" }, + "author_email": { "type": "string" } + }, + "rls": { + "create": true, + "read": true, + "update": { "created_by": "{{user.email}}" }, + "delete": { "created_by": "{{user.email}}" } + } +} +``` + +--- + +## Using Operators + +### Logical Operators + +Combine multiple conditions using `$or`, `$and`, or `$nor`: + +**Owner OR Admin access:** +```jsonc +{ + "name": "Document", + "type": "object", + "properties": { + "title": { "type": "string" }, + "content": { "type": "string" } + }, + "rls": { + "create": true, + "read": { + "$or": [ + { "created_by": "{{user.email}}" }, + { "user_condition": { "role": "admin" } } + ] + }, + "update": { + "$or": [ + { "created_by": "{{user.email}}" }, + { "user_condition": { "role": "admin" } } + ] + }, + "delete": { "user_condition": { "role": "admin" } } + } +} +``` + +**Multiple roles with $or:** +```jsonc +{ + "rls": { + "read": { + "$or": [ + { "user_condition": { "role": "admin" } }, + { "user_condition": { "role": "manager" } }, + { "user_condition": { "role": "hr" } } + ] + } + } +} +``` + +### Field Operators for data.* Fields + +Use `$in`, `$nin`, `$ne`, `$all` for comparing entity data fields: + +**Access based on tags ($in):** +```jsonc +{ + "rls": { + "read": { + "data.category": { "$in": ["public", "shared"] } + } + } +} +``` + +**Exclude specific statuses ($nin):** +```jsonc +{ + "rls": { + "read": { + "data.status": { "$nin": ["archived", "deleted"] } + } + } +} +``` + +**Not equal ($ne):** +```jsonc +{ + "rls": { + "read": { + "data.visibility": { "$ne": "private" } + } + } +} +``` + +**All tags must match ($all):** +```jsonc +{ + "rls": { + "read": { + "data.required_tags": { "$all": ["approved", "reviewed"] } + } + } +} +``` + +### Combining Logical and Field Operators + +```jsonc +{ + "rls": { + "read": { + "$and": [ + { "data.status": { "$ne": "draft" } }, + { + "$or": [ + { "created_by": "{{user.email}}" }, + { "data.visibility": "public" } + ] + } + ] + } + } +} +``` + +--- + +## Field-Level Security Examples + +Control access to specific fields within an entity. + +### Sensitive Salary Field + +```jsonc +{ + "name": "Employee", + "type": "object", + "properties": { + "name": { "type": "string" }, + "email": { "type": "string", "format": "email" }, + "salary": { + "type": "number", + "description": "Annual salary", + "rls": { + "read": { "user_condition": { "role": "hr" } }, + "write": { "user_condition": { "role": "hr" } } + } + }, + "performance_notes": { + "type": "string", + "description": "Manager notes", + "rls": { + "read": { + "$or": [ + { "user_condition": { "role": "manager" } }, + { "user_condition": { "role": "hr" } } + ] + }, + "write": { "user_condition": { "role": "manager" } } + } + } + } +} +``` + +### Admin-Only Internal Fields + +```jsonc +{ + "name": "Order", + "type": "object", + "properties": { + "order_number": { "type": "string" }, + "total": { "type": "number" }, + "internal_notes": { + "type": "string", + "description": "Internal processing notes", + "rls": { + "read": { "user_condition": { "role": "admin" } }, + "write": { "user_condition": { "role": "admin" } } + } + }, + "profit_margin": { + "type": "number", + "description": "Profit margin percentage", + "rls": { + "read": { "user_condition": { "role": "admin" } }, + "write": false + } + } + } +} +``` + +--- + +## Complex Patterns (Dashboard UI or Backend) + +Some patterns may still require the Dashboard UI or backend functions. + +### Bidirectional Relationships (e.g., Friendships, Matches) + +**Requirement:** Either party in a relationship should have access. + +**Now possible with $or:** +```jsonc +{ + "rls": { + "read": { + "$or": [ + { "data.user_a_email": "{{user.email}}" }, + { "data.user_b_email": "{{user.email}}" } + ] + } + } +} +``` + +**Alternative solutions:** +1. **Entity redesign:** Store two records per relationship (one for each party) +2. **Backend function:** Query with custom logic + +### Complex Business Logic + +**Requirement:** Access depends on multiple entity fields with complex conditions. + +**JSON Schema limitation:** While operators help, very complex business logic may still be hard to express. + +**Solution options:** +1. **Backend function:** Implement custom access logic +2. **Combine simpler rules:** Break complex rules into simpler entity-level and field-level rules + +--- + +## Best Practices + +### Security Strategy + +Use a combination of entity-level RLS and field-level security: + +| Data Type | Approach | Example | +|-----------|----------|---------| +| User-editable | Entity RLS: Owner-only | UserProfile with `created_by` check | +| Sensitive fields | Field-level RLS | Salary field with HR role check | +| Multi-role access | `$or` with user_condition | Admin OR Manager access | +| Conditional access | Field operators | `$in`, `$ne` on data fields | +| Public content | Entity RLS: `read: true` | PublicPost | +| Private content | Entity RLS: Owner-only | PrivateNote | + +### When to Use Each Approach + +| Requirement | Approach | +|-------------|----------| +| Single condition (owner, admin, department) | JSON Schema RLS | +| Multiple OR/AND conditions | JSON Schema RLS with `$or`/`$and` | +| Field value checks with `$in`/`$ne`/etc. | JSON Schema RLS for `data.*` fields | +| Field-level access control | JSON Schema FLS (field-level `rls`) | +| Complex comparison operators (`$gt`, `$lt`) | Backend functions | +| Very complex business logic | Backend functions | + +### Common Role Patterns + +| Role | Typical Access | +|------|----------------| +| `admin` | Full access to all records | +| `moderator` | Read/update access, limited delete | +| `manager` | Department-scoped access | +| `user` | Own records only | + +### Supported Operators Summary + +| Operator | Supported | Notes | +|----------|-----------|-------| +| `$or` | Yes | Combine multiple conditions | +| `$and` | Yes | All conditions must match | +| `$nor` | Yes | None of the conditions match | +| `$in` | Yes | For `data.*` fields only | +| `$nin` | Yes | For `data.*` fields only | +| `$ne` | Yes | For `data.*` fields only | +| `$all` | Yes | For `data.*` fields only | +| `$gt`, `$lt`, `$gte`, `$lte` | No | Use backend functions | +| `$regex` | No | Use backend functions | + +### Limitations Summary + +| Not Supported | Alternative | +|---------------|-------------| +| Operators on `user_condition` | Use equality only for user checks | +| Comparison operators (`$gt`, `$lt`) | Backend functions | +| Regex matching (`$regex`) | Backend functions | +| Cross-entity relationships | Backend functions | diff --git a/evals/fixtures/nextjs-todo/skills/base44-cli/references/site-deploy.md b/evals/fixtures/nextjs-todo/skills/base44-cli/references/site-deploy.md new file mode 100644 index 0000000..929d302 --- /dev/null +++ b/evals/fixtures/nextjs-todo/skills/base44-cli/references/site-deploy.md @@ -0,0 +1,118 @@ +# base44 site deploy + +Deploy built site files to Base44 hosting. + +## Table of Contents + +- [Syntax](#syntax) +- [Authentication](#authentication) +- [Prerequisites](#prerequisites) +- [How It Works](#how-it-works) +- [Interactive Flow](#interactive-flow) +- [Typical Workflow](#typical-workflow) +- [Configuration](#configuration) +- [Error Handling](#error-handling) +- [Use Cases](#use-cases) +- [Notes](#notes) + +## Syntax + +```bash +npx base44 site deploy [options] +``` + +## Options + +| Option | Description | +| ------------ | ------------------------- | +| `-y, --yes` | Skip confirmation prompt | + +Use `-y` flag for non-interactive/automated deployments: + +```bash +npx base44 site deploy -y +``` + +## Authentication + +**Required**: Yes. If not authenticated, you'll be prompted to login first. + +## Prerequisites + +- Must be run from a Base44 project directory +- Project must have `site.outputDirectory` configured in project config +- Site must be built before deploying (run your build command first) +- **SPA only**: Base44 hosting supports Single Page Applications with a single `index.html` entry point. All routes are served from `index.html` (client-side routing). + +## How It Works + +1. Reads project configuration +2. Validates that site configuration exists +3. Prompts for deployment confirmation showing the output directory +4. Creates an archive of site files from the output directory +5. Deploys to Base44 hosting +6. Returns the app URL + +## Interactive Flow + +```bash +$ npx base44 site deploy + +Deploy site from ./dist? (yes/no) yes + +Creating archive... +Uploading to Base44... +Deploying... + +✓ Deployment successful! + +Visit your site at: https://my-app.base44.app +``` + +## Typical Workflow + +```bash +# 1. Build your site using your framework's build command +npm run build + +# 2. Deploy to Base44 +npx base44 site deploy +``` + +## Configuration + +The `site.outputDirectory` in your project configuration should point to where your framework outputs built files: + +- Vite: typically `./dist` +- Next.js: typically `./.next` or `./out` +- Create React App: typically `./build` +- Custom: whatever your build tool outputs to + +## Error Handling + +If site configuration is missing: +```bash +$ npx base44 site deploy +Error: No site configuration found in project +``` + +If you cancel the deployment: +```bash +Deploy site from ./dist? (yes/no) no +Deployment cancelled +``` + +## Use Cases + +- Deploy your site after making changes +- Push new versions of your application +- Deploy after updating content or functionality +- Part of your CI/CD pipeline + +## Notes + +- Always build your site before deploying +- The command deploys whatever is in your output directory +- Make sure your build completed successfully before deploying +- Previous deployments are preserved (versioned) in Base44 +- Deployment is immediate and updates your live site diff --git a/evals/fixtures/nextjs-todo/skills/base44-sdk/SKILL.md b/evals/fixtures/nextjs-todo/skills/base44-sdk/SKILL.md new file mode 100644 index 0000000..01ddd7f --- /dev/null +++ b/evals/fixtures/nextjs-todo/skills/base44-sdk/SKILL.md @@ -0,0 +1,296 @@ +--- +name: base44-sdk +description: "The base44 SDK is the library to communicate with base44 services. In projects, you use it to communicate with remote resources (entities, backend functions, ai agents) and to write backend functions. This skill is the place for learning about available modules and types. When you plan or implement a feature, you must learn this skill" +--- + +# Base44 Coder + +Build apps on the Base44 platform using the Base44 JavaScript SDK. + +## ⚡ IMMEDIATE ACTION REQUIRED - Read This First + +This skill activates on ANY mention of "base44" or when a `base44/` folder exists. **DO NOT read documentation files or search the web before acting.** + +**Your first action MUST be:** +1. Check if `base44/config.jsonc` exists in the current directory +2. If **YES** (existing project scenario): + - This skill (base44-sdk) handles the request + - Implement features using Base44 SDK + - Do NOT use base44-cli unless user explicitly requests CLI commands +3. If **NO** (new project scenario): + - Transfer to base44-cli skill for project initialization + - This skill cannot help until project is initialized + +## When to Use This Skill vs base44-cli + +**Use base44-sdk when:** +- Building features in an **EXISTING** Base44 project +- `base44/config.jsonc` already exists in the project +- Base44 SDK imports are present (`@base44/sdk`) +- Writing JavaScript/TypeScript code using Base44 SDK modules +- Implementing functionality, components, or features +- User mentions: "implement", "build a feature", "add functionality", "write code for" +- User says "create a [type] app" **and** a Base44 project already exists + +**DO NOT USE base44-sdk for:** +- ❌ Initializing new Base44 projects (use `base44-cli` instead) +- ❌ Empty directories without Base44 configuration +- ❌ When user says "create a new Base44 project/app/site" and no project exists +- ❌ CLI commands like `npx base44 create`, `npx base44 deploy`, `npx base44 login` (use `base44-cli`) + +**Skill Dependencies:** +- `base44-sdk` assumes a Base44 project is **already initialized** +- `base44-cli` is a **prerequisite** for `base44-sdk` in new projects +- If user wants to "create an app" and no Base44 project exists, use `base44-cli` first + +**State Check Logic:** +Before selecting this skill, verify: +- IF (user mentions "create/build app" OR "make a project"): + - IF (directory is empty OR no `base44/config.jsonc` exists): + → Use **base44-cli** (project initialization needed) + - ELSE: + → Use **base44-sdk** (project exists, build features) + +## Quick Start + +```javascript +// In Base44-generated apps, base44 client is pre-configured and available + +// CRUD operations +const task = await base44.entities.Task.create({ title: "New task", status: "pending" }); +const tasks = await base44.entities.Task.list(); +await base44.entities.Task.update(task.id, { status: "done" }); + +// Get current user +const user = await base44.auth.me(); +``` + +```javascript +// External apps +import { createClient } from "@base44/sdk"; + +// IMPORTANT: Use 'appId' (NOT 'clientId' or 'id') +const base44 = createClient({ appId: "your-app-id" }); +await base44.auth.loginViaEmailPassword("user@example.com", "password"); +``` + +## ⚠️ CRITICAL: Do Not Hallucinate APIs + +**Before writing ANY Base44 code, verify method names against this table or [QUICK_REFERENCE.md](references/QUICK_REFERENCE.md).** + +Base44 SDK has unique method names. Do NOT assume patterns from Firebase, Supabase, or other SDKs. + +### Authentication - WRONG vs CORRECT + +| ❌ WRONG (hallucinated) | ✅ CORRECT | +|------------------------|-----------| +| `signInWithGoogle()` | `loginWithProvider('google')` | +| `signInWithProvider('google')` | `loginWithProvider('google')` | +| `auth.google()` | `loginWithProvider('google')` | +| `signInWithEmailAndPassword(email, pw)` | `loginViaEmailPassword(email, pw)` | +| `signIn(email, pw)` | `loginViaEmailPassword(email, pw)` | +| `createUser()` / `signUp()` | `register({email, password})` | +| `onAuthStateChanged()` | `me()` (no listener, call when needed) | +| `currentUser` | `await auth.me()` | + +### Functions - WRONG vs CORRECT + +| ❌ WRONG (hallucinated) | ✅ CORRECT | +|------------------------|-----------| +| `functions.call('name', data)` | `functions.invoke('name', data)` | +| `functions.run('name', data)` | `functions.invoke('name', data)` | +| `callFunction('name', data)` | `functions.invoke('name', data)` | +| `httpsCallable('name')(data)` | `functions.invoke('name', data)` | + +### Integrations - WRONG vs CORRECT + +| ❌ WRONG (hallucinated) | ✅ CORRECT | +|------------------------|-----------| +| `ai.generate(prompt)` | `integrations.Core.InvokeLLM({prompt})` | +| `openai.chat(prompt)` | `integrations.Core.InvokeLLM({prompt})` | +| `llm(prompt)` | `integrations.Core.InvokeLLM({prompt})` | +| `sendEmail(to, subject, body)` | `integrations.Core.SendEmail({to, subject, body})` | +| `email.send()` | `integrations.Core.SendEmail({to, subject, body})` | +| `uploadFile(file)` | `integrations.Core.UploadFile({file})` | +| `storage.upload(file)` | `integrations.Core.UploadFile({file})` | + +### Entities - WRONG vs CORRECT + +| ❌ WRONG (hallucinated) | ✅ CORRECT | +|------------------------|-----------| +| `entities.Task.find({...})` | `entities.Task.filter({...})` | +| `entities.Task.findOne(id)` | `entities.Task.get(id)` | +| `entities.Task.insert(data)` | `entities.Task.create(data)` | +| `entities.Task.remove(id)` | `entities.Task.delete(id)` | +| `entities.Task.onChange(cb)` | `entities.Task.subscribe(cb)` | + +## SDK Modules + +| Module | Purpose | Reference | +|--------|---------|-----------| +| `entities` | CRUD operations on data models | [entities.md](references/entities.md) | +| `auth` | Login, register, user management | [auth.md](references/auth.md) | +| `agents` | AI conversations and messages | [base44-agents.md](references/base44-agents.md) | +| `functions` | Backend function invocation | [functions.md](references/functions.md) | +| `integrations` | AI, email, file uploads, custom APIs | [integrations.md](references/integrations.md) | +| `connectors` | OAuth tokens (service role only) | [connectors.md](references/connectors.md) | +| `analytics` | Track custom events and user activity | [analytics.md](references/analytics.md) | +| `appLogs` | Log user activity in app | [app-logs.md](references/app-logs.md) | +| `users` | Invite users to the app | [users.md](references/users.md) | + +For client setup and authentication modes, see [client.md](references/client.md). + +**TypeScript Support:** Each reference file includes a "Type Definitions" section with TypeScript interfaces and types for the module's methods, parameters, and return values. + +## Installation + +Install the Base44 SDK: + +```bash +npm install @base44/sdk +``` + +**Important:** Never assume or hardcode the `@base44/sdk` package version. Always install without a version specifier to get the latest version. + +## Creating a Client (External Apps) + +When creating a client in external apps, **ALWAYS use `appId` as the parameter name**: + +```javascript +import { createClient } from "@base44/sdk"; + +// ✅ CORRECT +const base44 = createClient({ appId: "your-app-id" }); + +// ❌ WRONG - Do NOT use these: +// const base44 = createClient({ clientId: "your-app-id" }); // WRONG +// const base44 = createClient({ id: "your-app-id" }); // WRONG +``` + +**Required parameter:** `appId` (string) - Your Base44 application ID + +**Optional parameters:** +- `token` (string) - Pre-authenticated user token +- `options` (object) - Configuration options + - `options.onError` (function) - Global error handler + +**Example with error handler:** +```javascript +const base44 = createClient({ + appId: "your-app-id", + options: { + onError: (error) => { + console.error("Base44 error:", error); + } + } +}); +``` + +## Module Selection + +**Working with app data?** +- Create/read/update/delete records → `entities` +- Import data from file → `entities.importEntities()` +- Realtime updates → `entities.EntityName.subscribe()` + +**User management?** +- Login/register/logout → `auth` +- Get current user → `auth.me()` +- Update user profile → `auth.updateMe()` +- Invite users → `users.inviteUser()` + +**AI features?** +- Chat with AI agents → `agents` (requires logged-in user) +- Create new conversation → `agents.createConversation()` +- Manage conversations → `agents.getConversations()` +- Generate text/JSON with AI → `integrations.Core.InvokeLLM()` +- Generate images → `integrations.Core.GenerateImage()` + +**Custom backend logic?** +- Run server-side code → `functions.invoke()` +- Need admin access → `base44.asServiceRole.functions.invoke()` + +**External services?** +- Send emails → `integrations.Core.SendEmail()` +- Upload files → `integrations.Core.UploadFile()` +- Custom APIs → `integrations.custom.call()` +- OAuth tokens (Google, Slack) → `connectors` (backend only) + +**Tracking and analytics?** +- Track custom events → `analytics.track()` +- Log page views/activity → `appLogs.logUserInApp()` + +## Common Patterns + +### Filter and Sort Data + +```javascript +const pendingTasks = await base44.entities.Task.filter( + { status: "pending", assignedTo: userId }, // query + "-created_date", // sort (descending) + 10, // limit + 0 // skip +); +``` + +### Protected Routes (check auth) + +```javascript +const user = await base44.auth.me(); +if (!user) { + // Navigate to your custom login page + navigate('/login', { state: { returnTo: window.location.pathname } }); + return; +} +``` + +### Backend Function Call + +```javascript +// Frontend +const result = await base44.functions.invoke("processOrder", { + orderId: "123", + action: "ship" +}); + +// Backend function (Deno) +import { createClientFromRequest } from "npm:@base44/sdk"; + +Deno.serve(async (req) => { + const base44 = createClientFromRequest(req); + const { orderId, action } = await req.json(); + // Process with service role for admin access + const order = await base44.asServiceRole.entities.Orders.get(orderId); + return Response.json({ success: true }); +}); +``` + +### Service Role Access + +Use `asServiceRole` in backend functions for admin-level operations: + +```javascript +// User mode - respects permissions +const myTasks = await base44.entities.Task.list(); + +// Service role - full access (backend only) +const allTasks = await base44.asServiceRole.entities.Task.list(); +const token = await base44.asServiceRole.connectors.getAccessToken("slack"); +``` + +## Frontend vs Backend + +| Capability | Frontend | Backend | +|------------|----------|---------| +| `entities` (user's data) | Yes | Yes | +| `auth` | Yes | Yes | +| `agents` | Yes | Yes | +| `functions.invoke()` | Yes | Yes | +| `integrations` | Yes | Yes | +| `analytics` | Yes | Yes | +| `appLogs` | Yes | Yes | +| `users` | Yes | Yes | +| `asServiceRole.*` | No | Yes | +| `connectors` | No | Yes | + +Backend functions use `Deno.serve()` and `createClientFromRequest(req)` to get a properly authenticated client. diff --git a/evals/fixtures/nextjs-todo/skills/base44-sdk/references/QUICK_REFERENCE.md b/evals/fixtures/nextjs-todo/skills/base44-sdk/references/QUICK_REFERENCE.md new file mode 100644 index 0000000..d357384 --- /dev/null +++ b/evals/fixtures/nextjs-todo/skills/base44-sdk/references/QUICK_REFERENCE.md @@ -0,0 +1,155 @@ +# Base44 SDK Quick Reference + +Compact method signatures for all SDK modules. **Verify against this before writing code.** + +--- + +## Auth (`base44.auth.*`) + +``` +loginViaEmailPassword(email, password, turnstileToken?) → Promise<{access_token, user}> +loginWithProvider('google' | 'microsoft' | 'facebook', fromUrl?) → void +me() → Promise +updateMe(data) → Promise +isAuthenticated() → Promise +logout(redirectUrl?) → void +redirectToLogin(nextUrl) → void # ⚠️ Avoid - prefer custom login UI +register({email, password, turnstile_token?, referral_code?}) → Promise +verifyOtp({email, otpCode}) → Promise +resendOtp(email) → Promise +inviteUser(userEmail, role) → Promise +resetPasswordRequest(email) → Promise +resetPassword({resetToken, newPassword}) → Promise +changePassword({userId, currentPassword, newPassword}) → Promise +setToken(token, saveToStorage?) → void +``` + +--- + +## Entities (`base44.entities.EntityName.*`) + +``` +create(data) → Promise +bulkCreate(dataArray) → Promise +list(sort?, limit?, skip?, fields?) → Promise +filter(query, sort?, limit?, skip?, fields?) → Promise +get(id) → Promise +update(id, data) → Promise +delete(id) → Promise +deleteMany(query) → Promise +importEntities(file) → Promise // frontend only +subscribe(callback) → () => void // returns unsubscribe fn +``` + +**Sort:** Use `-fieldName` for descending (e.g., `-created_date`) + +--- + +## Functions (`base44.functions.*`) + +``` +invoke(functionName, data) → Promise +``` + +**Backend:** Use `base44.asServiceRole.functions.invoke()` for admin access. + +--- + +## Integrations (`base44.integrations.Core.*`) + +``` +InvokeLLM({prompt, add_context_from_internet?, response_json_schema?, file_urls?}) → Promise +GenerateImage({prompt}) → Promise<{url}> +SendEmail({to, subject, body, from_name?}) → Promise +UploadFile({file}) → Promise<{file_url}> +UploadPrivateFile({file}) → Promise<{file_uri}> +CreateFileSignedUrl({file_uri, expires_in?}) → Promise<{signed_url}> +ExtractDataFromUploadedFile({file_url, json_schema}) → Promise +``` + +### Custom Integrations (`base44.integrations.custom.*`) + +``` +call(slug, operationId, {payload?, pathParams?, queryParams?, headers?}?) → Promise<{success, status_code, data}> +``` + +**operationId format:** `"method:/path"` (e.g., `"get:/contacts"`, `"post:/users/{id}"`) + +--- + +## Analytics (`base44.analytics.*`) + +``` +track({eventName, properties?}) → void +``` + +--- + +## App Logs (`base44.appLogs.*`) + +``` +logUserInApp(pageName) → Promise +``` + +--- + +## Users (`base44.users.*`) + +``` +inviteUser(userEmail, role) → Promise // role: 'user' | 'admin' +``` + +--- + +## Connectors (`base44.asServiceRole.connectors.*`) + +**Backend only, service role required.** + +``` +getAccessToken(integrationType) → Promise +``` + +**Types:** `'googlecalendar'`, `'googledrive'`, `'slack'`, `'notion'`, `'salesforce'`, `'hubspot'`, `'linkedin'`, `'tiktok'`, `'github'` + +--- + +## Service Role Access + +**Backend functions only.** Prefix any module with `asServiceRole` for admin access: + +```javascript +base44.asServiceRole.entities.Task.list() +base44.asServiceRole.functions.invoke('name', data) +base44.asServiceRole.connectors.getAccessToken('slack') +``` + +--- + +## Backend Function Template + +```javascript +import { createClientFromRequest } from "@base44/sdk"; + +Deno.serve(async (req) => { + const base44 = createClientFromRequest(req); + const data = await req.json(); + + // User context + const user = await base44.auth.me(); + + // Service role for admin operations + const allRecords = await base44.asServiceRole.entities.Task.list(); + + return Response.json({ success: true }); +}); +``` + +--- + +## Client Initialization (External Apps) + +```javascript +import { createClient } from "@base44/sdk"; + +const base44 = createClient({ appId: "your-app-id" }); // MUST use 'appId' +``` diff --git a/evals/fixtures/nextjs-todo/skills/base44-sdk/references/analytics.md b/evals/fixtures/nextjs-todo/skills/base44-sdk/references/analytics.md new file mode 100644 index 0000000..d1b2c69 --- /dev/null +++ b/evals/fixtures/nextjs-todo/skills/base44-sdk/references/analytics.md @@ -0,0 +1,113 @@ +# Analytics Module + +Track custom events and user activity via `base44.analytics`. + +## Contents +- [Methods](#methods) +- [Examples](#examples) +- [Automatic Tracking](#automatic-tracking) +- [Best Practices](#best-practices) + +## Methods + +| Method | Signature | Description | +|--------|-----------|-------------| +| `track(params)` | `void` | Track a custom event | + +## Examples + +### Track Custom Event + +```javascript +// Track a simple event +base44.analytics.track({ + eventName: "button_clicked" +}); + +// Track event with properties +base44.analytics.track({ + eventName: "purchase_completed", + properties: { + product_id: "prod-123", + amount: 99.99, + currency: "USD" + } +}); +``` + +### Track User Actions + +```javascript +// Track page view +base44.analytics.track({ + eventName: "page_view", + properties: { + page: "/dashboard", + referrer: document.referrer + } +}); + +// Track feature usage +base44.analytics.track({ + eventName: "feature_used", + properties: { + feature: "export_data", + format: "csv" + } +}); +``` + +## Automatic Tracking + +The analytics module automatically tracks: +- **Initialization events**: When the app loads +- **Heartbeat events**: Periodic activity signals +- **Session duration**: Time spent in the app + +These internal events help measure user engagement without manual instrumentation. + +## Best Practices + +1. **Use descriptive event names**: `order_completed` instead of `click` +2. **Include relevant properties**: Add context that helps analyze the event +3. **Be consistent**: Use the same event names and property keys across your app +4. **Don't track sensitive data**: Avoid PII in event properties + +```javascript +// Good: Descriptive with relevant properties +base44.analytics.track({ + eventName: "subscription_started", + properties: { + plan: "pro", + billing_cycle: "annual" + } +}); + +// Avoid: Vague event name, no context +base44.analytics.track({ + eventName: "click" +}); +``` + +## Type Definitions + +```typescript +/** Properties that can be attached to a tracked event. */ +type TrackEventProperties = { + [key: string]: string | number | boolean | null | undefined; +}; + +/** Parameters for the track() method. */ +interface TrackEventParams { + /** The name of the event to track. */ + eventName: string; + /** Optional properties to attach to the event. */ + properties?: TrackEventProperties; +} + +/** The analytics module interface. */ +interface AnalyticsModule { + /** Track a custom event with optional properties. */ + track(params: TrackEventParams): void; +} +``` diff --git a/evals/fixtures/nextjs-todo/skills/base44-sdk/references/app-logs.md b/evals/fixtures/nextjs-todo/skills/base44-sdk/references/app-logs.md new file mode 100644 index 0000000..0439805 --- /dev/null +++ b/evals/fixtures/nextjs-todo/skills/base44-sdk/references/app-logs.md @@ -0,0 +1,78 @@ +# App Logs Module + +Log user activity in your app via `base44.appLogs`. + +## Contents +- [Methods](#methods) +- [Examples](#examples) +- [Use Cases](#use-cases) + +## Methods + +| Method | Signature | Description | +|--------|-----------|-------------| +| `logUserInApp(pageName)` | `Promise` | Log user activity on a page | + +## Examples + +### Log User Activity + +```javascript +// Log when user visits a page +await base44.appLogs.logUserInApp("dashboard"); + +// Log specific page visits +await base44.appLogs.logUserInApp("settings"); +await base44.appLogs.logUserInApp("profile"); + +// Log feature usage +await base44.appLogs.logUserInApp("export-button-click"); +``` + +The page name doesn't have to be an actual page - it can be any string you want to track. + +## Use Cases + +### Track Page Views in React + +```javascript +// Log page views on route change +useEffect(() => { + base44.appLogs.logUserInApp(window.location.pathname); +}, [location.pathname]); +``` + +### Track Feature Usage + +```javascript +// Log when user uses specific features +function handleExport() { + base44.appLogs.logUserInApp("export-data"); + // ... export logic +} + +function handleSettingsChange() { + base44.appLogs.logUserInApp("settings-updated"); + // ... save settings +} +``` + +## Notes + +- Logs appear in the Analytics page of your app dashboard +- App logs track page-level and feature-level activity +- Use `analytics.track()` for custom events with properties, `appLogs.logUserInApp()` for simple page/feature tracking + +## Type Definitions + +```typescript +/** App Logs module for tracking and analyzing app usage. */ +interface AppLogsModule { + /** + * Log user activity in the app. + * @param pageName - Name of the page or section being visited. + * @returns Promise that resolves when the log is recorded. + */ + logUserInApp(pageName: string): Promise; +} +``` diff --git a/evals/fixtures/nextjs-todo/skills/base44-sdk/references/auth.md b/evals/fixtures/nextjs-todo/skills/base44-sdk/references/auth.md new file mode 100644 index 0000000..5195b3a --- /dev/null +++ b/evals/fixtures/nextjs-todo/skills/base44-sdk/references/auth.md @@ -0,0 +1,761 @@ +# Auth Module + +User authentication, registration, and session management via `base44.auth`. + +## Contents +- [TypeScript Types](#typescript-types) +- [Methods](#methods) +- [Examples](#examples) +- [Error Handling](#error-handling) +- [Auth Providers](#auth-providers) +- [Environment Availability](#environment-availability) +- [App Visibility](#app-visibility) +- [Limitations](#limitations) + +--- + +## TypeScript Types + +### User Interface +```typescript +interface User { + id: string; + created_date: string; + updated_date: string; + email: string; + full_name: string | null; + disabled: boolean | null; + is_verified: boolean; + app_id: string; + is_service: boolean; + role: string; + [key: string]: any; // Custom schema fields +} +``` + +### LoginResponse Interface +```typescript +interface LoginResponse { + access_token: string; // JWT token + user: User; // Complete user object +} +``` + +### Parameter Interfaces + +#### RegisterParams +```typescript +interface RegisterParams { + email: string; // Required + password: string; // Required + turnstile_token?: string | null; // Optional: Cloudflare Turnstile for bot protection + referral_code?: string | null; // Optional: Referral code +} +``` + +#### VerifyOtpParams +```typescript +interface VerifyOtpParams { + email: string; // User's email + otpCode: string; // OTP code from email +} +``` + +#### ResetPasswordParams +```typescript +interface ResetPasswordParams { + resetToken: string; // Token from password reset email + newPassword: string; // New password to set +} +``` + +#### ChangePasswordParams +```typescript +interface ChangePasswordParams { + userId: string; // User ID + currentPassword: string; // Current password for verification + newPassword: string; // New password to set +} +``` + +### Provider Type +```typescript +type Provider = 'google' | 'microsoft' | 'facebook'; +``` + +--- + +## Methods + +### Module Interface +```typescript +interface AuthModule { + // User Info + me(): Promise; + updateMe(data: Partial>): Promise; + isAuthenticated(): Promise; + + // Login/Logout + loginViaEmailPassword(email: string, password: string, turnstileToken?: string): Promise; + loginWithProvider(provider: Provider, fromUrl?: string): void; + logout(redirectUrl?: string): void; + redirectToLogin(nextUrl: string): void; + + // Token Management + setToken(token: string, saveToStorage?: boolean): void; + + // Registration + register(params: RegisterParams): Promise; + verifyOtp(params: VerifyOtpParams): Promise; + resendOtp(email: string): Promise; + + // User Management + inviteUser(userEmail: string, role: string): Promise; + + // Password Management + resetPasswordRequest(email: string): Promise; + resetPassword(params: ResetPasswordParams): Promise; + changePassword(params: ChangePasswordParams): Promise; +} +``` + +### Method Reference Table + +| Method | Parameters | Return Type | Description | +|--------|-----------|-------------|-------------| +| `register()` | `params: RegisterParams` | `Promise` | Create new user account | +| `loginViaEmailPassword()` | `email: string, password: string, turnstileToken?: string` | `Promise` | Authenticate with email/password | +| `loginWithProvider()` | `provider: Provider, fromUrl?: string` | `void` | Initiate OAuth login flow | +| `me()` | None | `Promise` | Get current authenticated user | +| `updateMe()` | `data: Partial` | `Promise` | Update current user's profile | +| `logout()` | `redirectUrl?: string` | `void` | Clear session, optionally redirect | +| `redirectToLogin()` | `nextUrl: string` | `void` | ⚠️ **Avoid** - Prefer custom login UI with `loginViaEmailPassword()` or `loginWithProvider()` | +| `isAuthenticated()` | None | `Promise` | Check if user is logged in | +| `setToken()` | `token: string, saveToStorage?: boolean` | `void` | Manually set auth token | +| `inviteUser()` | `userEmail: string, role: string` | `Promise` | Send invitation email | +| `verifyOtp()` | `params: VerifyOtpParams` | `Promise` | Verify OTP code | +| `resendOtp()` | `email: string` | `Promise` | Resend OTP code | +| `resetPasswordRequest()` | `email: string` | `Promise` | Request password reset | +| `resetPassword()` | `params: ResetPasswordParams` | `Promise` | Reset password with token | +| `changePassword()` | `params: ChangePasswordParams` | `Promise` | Change user password | + +--- + +## Examples + +### Register New User (Complete Flow) + +Registration requires email verification before login. Complete flow: + +1. **Register** - Create the user account +2. **Verification email sent** - User receives an OTP code +3. **Verify OTP** - User enters code to verify email +4. **Login** - User can now log in + +```javascript +try { + // Step 1: Register the user + await base44.auth.register({ + email: "user@example.com", + password: "securePassword123", + referral_code: "OPTIONAL_CODE", // optional + turnstile_token: "CAPTCHA_TOKEN" // optional, for bot protection + }); + console.log('Registration successful. Check email for OTP code.'); + + // Step 2: User receives email with OTP code (e.g., "123456") + + // Step 3: Verify the OTP code + await base44.auth.verifyOtp({ + email: "user@example.com", + otpCode: "123456" // code from verification email + }); + console.log('Email verified successfully.'); + + // Step 4: Now the user can log in + const loginResponse = await base44.auth.loginViaEmailPassword( + "user@example.com", + "securePassword123" + ); + console.log('Login successful:', loginResponse.user); + +} catch (error) { + console.error('Registration flow failed:', error.message); + // Handle specific errors (see Error Handling section) +} +``` + +> **Important**: Users cannot log in until they complete OTP verification. Attempting to call `loginViaEmailPassword` before verification will fail. + +### Login with Email/Password + +```javascript +try { + const response = await base44.auth.loginViaEmailPassword( + "user@example.com", + "password123", + turnstileToken // optional: for bot protection + ); + + console.log('Login successful'); + console.log('User:', response.user); + console.log('Token:', response.access_token); + + // JWT is automatically stored for subsequent requests + +} catch (error) { + console.error('Login failed:', error.message); + if (error.status === 401) { + console.error('Invalid credentials'); + } else if (error.status === 403) { + console.error('Email not verified. Please check your email for OTP.'); + } +} +``` + +### Login with OAuth Provider + +```javascript +// Redirect to Google OAuth +base44.auth.loginWithProvider('google'); + +// Redirect to Google OAuth and return to current page after +base44.auth.loginWithProvider('google', window.location.href); + +// Supported providers: 'google', 'microsoft', 'facebook' +base44.auth.loginWithProvider('microsoft'); +``` + +### Get Current User + +```javascript +try { + const user = await base44.auth.me(); + + if (user) { + console.log('User ID:', user.id); + console.log('Email:', user.email); + console.log('Name:', user.full_name); + console.log('Role:', user.role); + console.log('Verified:', user.is_verified); + } else { + console.log('User not authenticated'); + } + +} catch (error) { + console.error('Failed to fetch user:', error.message); + if (error.status === 401) { + // Token expired or invalid - navigate to your custom login page + navigate('/login'); + } +} +``` + +### Update User Profile + +```javascript +try { + const updatedUser = await base44.auth.updateMe({ + full_name: "John Doe", + // Custom schema fields can be updated here + phone: "+1234567890", + preferences: { theme: "dark" } + }); + + console.log('Profile updated:', updatedUser); + +} catch (error) { + console.error('Profile update failed:', error.message); + if (error.status === 400) { + console.error('Invalid data provided'); + } else if (error.status === 401) { + console.error('Authentication required'); + navigate('/login'); + } +} +``` + +### Check Authentication Status + +```javascript +try { + const isLoggedIn = await base44.auth.isAuthenticated(); + + if (isLoggedIn) { + // Show authenticated UI + console.log('User is authenticated'); + } else { + // Show login button + console.log('User is not authenticated'); + } + +} catch (error) { + console.error('Failed to check authentication:', error.message); + // On error, treat as not authenticated +} +``` + +### Logout + +```javascript +// Simple logout +base44.auth.logout(); + +// Logout and redirect to goodbye page +base44.auth.logout("/goodbye"); + +// Logout and redirect to homepage +base44.auth.logout("/"); +``` + +### Protected Route Pattern + +```javascript +// Using a navigation function (e.g., React Router's useNavigate, Next.js router) +async function requireAuth(navigate) { + try { + const user = await base44.auth.me(); + + if (!user) { + // Navigate to your custom login page + navigate('/login', { state: { returnTo: window.location.pathname } }); + return null; + } + + return user; + + } catch (error) { + console.error('Authentication check failed:', error.message); + navigate('/login', { state: { returnTo: window.location.pathname } }); + return null; + } +} + +// Usage in your app +async function loadProtectedPage(navigate) { + const user = await requireAuth(navigate); + if (!user) { + // Will navigate to login + return; + } + + // Continue with authenticated logic + console.log('Welcome,', user.full_name); +} +``` + +### Set Authentication Token + +```javascript +// SECURITY WARNING: Never hardcode tokens or expose them in client code +// Tokens should only be received from secure authentication flows + +// Set token and save to localStorage (default) +base44.auth.setToken(receivedToken); + +// Set token without saving to localStorage (temporary session) +base44.auth.setToken(receivedToken, false); + +// Verify token was set +try { + const isAuthenticated = await base44.auth.isAuthenticated(); + if (!isAuthenticated) { + console.error('Token validation failed'); + } +} catch (error) { + console.error('Failed to set token:', error.message); +} +``` + +### Invite User to Application + +```javascript +try { + // Note: Typically requires admin privileges + const response = await base44.auth.inviteUser( + "newuser@example.com", + "user" // or "admin" + ); + + console.log('Invitation sent successfully'); + +} catch (error) { + console.error('Failed to invite user:', error.message); + if (error.status === 403) { + console.error('Insufficient permissions to invite users'); + } else if (error.status === 400) { + console.error('Invalid email or role'); + } +} +``` + +### OTP Verification + +```javascript +try { + // Verify OTP code sent to user's email + await base44.auth.verifyOtp({ + email: "user@example.com", + otpCode: "123456" + }); + + console.log('OTP verified successfully'); + +} catch (error) { + console.error('OTP verification failed:', error.message); + if (error.status === 400) { + console.error('Invalid or expired OTP code'); + } else if (error.status === 429) { + console.error('Too many attempts. Please try again later.'); + } +} + +// Resend OTP if needed +try { + await base44.auth.resendOtp("user@example.com"); + console.log('OTP resent successfully'); + +} catch (error) { + console.error('Failed to resend OTP:', error.message); + if (error.status === 429) { + console.error('Too many requests. Please wait before trying again.'); + } +} +``` + +### Password Reset Flow + +```javascript +// Step 1: Request password reset +try { + await base44.auth.resetPasswordRequest("user@example.com"); + console.log('Password reset email sent. Check your inbox.'); + +} catch (error) { + console.error('Password reset request failed:', error.message); + if (error.status === 429) { + console.error('Too many requests. Please try again later.'); + } + // Note: For security, don't reveal if email exists +} + +// Step 2: Reset password with token from email +try { + await base44.auth.resetPassword({ + resetToken: "token-from-email", + newPassword: "newSecurePassword123" + }); + + console.log('Password reset successfully. You can now log in.'); + +} catch (error) { + console.error('Password reset failed:', error.message); + if (error.status === 400) { + console.error('Invalid or expired reset token'); + } else if (error.status === 422) { + console.error('Password does not meet requirements'); + } +} +``` + +### Change Password + +```javascript +try { + const currentUser = await base44.auth.me(); + + if (!currentUser) { + throw new Error('User must be authenticated to change password'); + } + + await base44.auth.changePassword({ + userId: currentUser.id, + currentPassword: "oldPassword123", + newPassword: "newSecurePassword456" + }); + + console.log('Password changed successfully'); + +} catch (error) { + console.error('Password change failed:', error.message); + if (error.status === 401) { + console.error('Current password is incorrect'); + } else if (error.status === 422) { + console.error('New password does not meet requirements'); + } else if (error.status === 403) { + console.error('Not authorized to change this password'); + } +} +``` + +--- + +## Error Handling + +### Common Error Scenarios + +The auth module can throw various errors. Here are common scenarios and how to handle them: + +#### Authentication Errors (401/403) +```javascript +try { + const user = await base44.auth.me(); +} catch (error) { + if (error.status === 401) { + // Token expired or invalid - navigate to your custom login page + navigate('/login'); + } else if (error.status === 403) { + // Email not verified or insufficient permissions + console.error('Access denied:', error.message); + } +} +``` + +#### Validation Errors (400/422) +```javascript +try { + await base44.auth.register({ + email: "invalid-email", + password: "weak" + }); +} catch (error) { + if (error.status === 400) { + console.error('Invalid input:', error.message); + // Handle validation errors + } else if (error.status === 422) { + console.error('Data validation failed:', error.details); + } +} +``` + +#### Rate Limiting (429) +```javascript +try { + await base44.auth.resendOtp("user@example.com"); +} catch (error) { + if (error.status === 429) { + console.error('Too many requests. Please wait before trying again.'); + // Show countdown or disable button temporarily + } +} +``` + +#### Generic Error Handler +```javascript +function handleAuthError(error) { + switch (error.status) { + case 400: + return 'Invalid input. Please check your data.'; + case 401: + return 'Authentication required. Redirecting to login...'; + case 403: + return 'Access denied. You may need to verify your email.'; + case 404: + return 'Resource not found.'; + case 422: + return 'Validation failed. Please check your input.'; + case 429: + return 'Too many requests. Please try again later.'; + case 500: + return 'Server error. Please try again.'; + default: + return 'An unexpected error occurred.'; + } +} + +// Usage +try { + await base44.auth.loginViaEmailPassword(email, password); +} catch (error) { + console.error(handleAuthError(error)); +} +``` + +--- + +## Auth Providers + +Configure authentication providers in your app dashboard: + +### Available Providers + +**Built-in (All Plans):** +- **Email/Password** - Default, always enabled +- **Google** - OAuth authentication +- **Microsoft** - OAuth authentication +- **Facebook** - OAuth authentication + +**SSO Providers (Elite Plan):** +- **Okta** +- **Azure AD** +- **GitHub** + +### Using OAuth Providers + +```javascript +// Initiate OAuth login flow +base44.auth.loginWithProvider('google'); + +// Return to specific page after authentication +base44.auth.loginWithProvider('microsoft', '/dashboard'); + +// Supported values: 'google', 'microsoft', 'facebook' +``` + +--- + +## Environment Availability + +| Environment | Availability | Notes | +|------------|--------------|-------| +| **Frontend** | ✅ Yes | All methods available | +| **Backend Functions** | ✅ Yes | Use `createClientFromRequest(req)` for authenticated client | +| **Service Role** | ❌ No | Auth methods not available in service role context | + +### Frontend Usage +```javascript +// Standard browser usage +const user = await base44.auth.me(); +``` + +### Backend Functions Usage +```javascript +import { createClientFromRequest } from "@base44/sdk"; + +Deno.serve(async (req) => { + const base44 = createClientFromRequest(req); + + // Get user from request context + const user = await base44.auth.me(); + + // User operations here... + return Response.json({ user }); +}); +``` + +--- + +## App Visibility + +Control who can access your app in the app settings: + +### Public Apps +- No login required for basic access +- Users can view public content without authentication +- Authenticated users get additional features/data + +### Private Apps +- Login required to access any content +- Unauthenticated users are automatically redirected to login +- All content is protected by default + +--- + +## Limitations + +### Authentication UI Options +- **Recommended:** Build custom login/signup UI using `loginViaEmailPassword()` and `loginWithProvider()` for full control over user experience and branding +- **Alternative:** `redirectToLogin()` uses Base44's hosted authentication pages with limited customization + +### Hosted Login (via redirectToLogin) +- `redirectToLogin()` shows both login and signup options on the same page +- No separate `redirectToSignup()` method +- Users can switch between login/signup on the hosted page +- ⚠️ **Note:** Prefer building custom login UI for better user experience + +### Password Requirements +- Minimum length and complexity requirements enforced +- Requirements not exposed via API +- Validation errors returned when requirements not met + +### Rate Limiting +- OTP requests are rate-limited to prevent abuse +- Password reset requests are rate-limited +- Login attempts may be rate-limited with Turnstile protection + +### Token Management +- JWTs are automatically stored in localStorage by default +- Token expiration and refresh not exposed in API +- Call `me()` or `isAuthenticated()` to verify token validity + +--- + +## Best Practices + +### 1. Always Handle Errors +```javascript +try { + await base44.auth.loginViaEmailPassword(email, password); +} catch (error) { + // Always handle authentication errors + console.error('Login failed:', error.message); +} +``` + +### 2. Verify Authentication Before Protected Actions +```javascript +const user = await requireAuth(); +if (!user) return; // Will redirect to login +// Proceed with authenticated action +``` + +### 3. Use Type Safety with TypeScript +```typescript +import type { User, LoginResponse } from '@base44/sdk'; + +const user: User = await base44.auth.me(); +const response: LoginResponse = await base44.auth.loginViaEmailPassword(email, password); +``` + +### 4. Don't Hardcode Credentials +```javascript +// ❌ BAD +base44.auth.setToken("hardcoded-token-here"); + +// ✅ GOOD +const token = await secureAuthFlow(); +base44.auth.setToken(token); +``` + +### 5. Provide User Feedback +```javascript +try { + await base44.auth.register(params); + showSuccessMessage('Registration successful! Check your email for verification code.'); +} catch (error) { + showErrorMessage('Registration failed: ' + error.message); +} +``` + +### 6. Handle Token Expiration Gracefully +```javascript +try { + const user = await base44.auth.me(); +} catch (error) { + if (error.status === 401) { + // Clear local state and navigate to login + localStorage.clear(); + navigate('/login'); + } +} +``` + +### 7. Build Custom Login UI (Recommended) +```javascript +// ✅ RECOMMENDED - Custom login form with direct methods +const handleLogin = async (email, password) => { + try { + const { user } = await base44.auth.loginViaEmailPassword(email, password); + navigate('/dashboard'); + } catch (error) { + setError(error.message); + } +}; + +const handleGoogleLogin = () => { + base44.auth.loginWithProvider('google', '/dashboard'); +}; + +// ❌ AVOID - Redirecting to hosted login page +// base44.auth.redirectToLogin(window.location.href); +``` diff --git a/evals/fixtures/nextjs-todo/skills/base44-sdk/references/base44-agents.md b/evals/fixtures/nextjs-todo/skills/base44-sdk/references/base44-agents.md new file mode 100644 index 0000000..ee55dee --- /dev/null +++ b/evals/fixtures/nextjs-todo/skills/base44-sdk/references/base44-agents.md @@ -0,0 +1,364 @@ +# Agents Module + +AI agent conversations and messages via `base44.agents`. + +> **Note:** This module requires a logged-in user. All agent methods work in the context of the authenticated user. + +## Contents +- [Concepts](#concepts) +- [Methods](#methods) +- [Examples](#examples) (Create, Get Conversations, List, Subscribe, Send Message, WhatsApp) +- [Message Structure](#message-structure) +- [Conversation Structure](#conversation-structure) +- [Common Patterns](#common-patterns) + +## Concepts + +- **Conversation**: A dialogue between user and an AI agent. Has unique ID, agent name, user reference, and metadata. +- **Message**: Single message in a conversation. Has role (`user`, `assistant`, `system`), content, timestamps, and optional metadata. + +## Methods + +| Method | Signature | Description | +|--------|-----------|-------------| +| `createConversation(params)` | `Promise` | Create a new conversation with an agent | +| `getConversations()` | `Promise` | Get all user's conversations | +| `getConversation(id)` | `Promise` | Get conversation with messages | +| `listConversations(filterParams)` | `Promise` | Filter/sort/paginate conversations | +| `subscribeToConversation(id, onUpdate?)` | `() => void` | Real-time updates via WebSocket (returns unsubscribe function) | +| `addMessage(conversation, message)` | `Promise` | Send a message | +| `getWhatsAppConnectURL(agentName)` | `string` | Get WhatsApp connection URL for agent | + +## Examples + +### Create Conversation + +```javascript +const conversation = await base44.agents.createConversation({ + agent_name: "support-agent", + metadata: { + order_id: "ORD-123", + category: "billing" + } +}); + +console.log(conversation.id); +``` + +### Get All Conversations + +```javascript +const conversations = await base44.agents.getConversations(); + +conversations.forEach(conv => { + console.log(conv.id, conv.agent_name, conv.created_date); +}); +``` + +### Get Single Conversation (with messages) + +```javascript +const conversation = await base44.agents.getConversation("conv-id-123"); + +console.log(conversation.messages); +``` + +### List with Filters + +```javascript +// Using filterParams object with q, sort, limit, skip, fields +const recent = await base44.agents.listConversations({ + q: { agent_name: "support-agent" }, + sort: "-created_date", + limit: 10, + skip: 0 +}); + +// Filter by metadata +const highPriority = await base44.agents.listConversations({ + q: { + agent_name: "support-agent", + "metadata.priority": "high" + }, + sort: "-updated_date", + limit: 20 +}); +``` + +### Subscribe to Updates (Real-time) + +```javascript +const unsubscribe = base44.agents.subscribeToConversation( + "conv-id-123", + (updatedConversation) => { + // Called when new messages arrive + console.log("New messages:", updatedConversation.messages); + } +); + +// Later: unsubscribe +unsubscribe(); +``` + +### Send a Message + +```javascript +const conversation = await base44.agents.getConversation("conv-id-123"); + +await base44.agents.addMessage(conversation, { + role: "user", + content: "What's the weather like today?" +}); +``` + +### Get WhatsApp Connection URL + +```javascript +const whatsappUrl = base44.agents.getWhatsAppConnectURL("support-agent"); +// Returns URL for users to connect with agent via WhatsApp +console.log(whatsappUrl); +``` + +## Message Structure + +```javascript +{ + role: "user" | "assistant" | "system", + content: "Message text or structured object", + created_date: "2024-01-15T10:30:00Z", + updated_date: "2024-01-15T10:30:00Z", + + // Optional fields + reasoning: { + content: "Agent's reasoning process", + timing: 1500 + }, + tool_calls: [{ + name: "search", + arguments: { query: "weather" }, + result: { ... }, + status: "success" + }], + file_urls: ["https://..."], + usage: { + prompt_tokens: 150, + completion_tokens: 50 + }, + metadata: { ... }, + custom_context: { ... } +} +``` + +## Conversation Structure + +```javascript +{ + id: "conv-id-123", + app_id: "app-id", + agent_name: "support-agent", + created_by_id: "user-id", + created_date: "2024-01-15T10:00:00Z", + updated_date: "2024-01-15T10:30:00Z", + messages: [ ... ], + metadata: { ... } +} +``` + +## Common Patterns + +### Chat Interface + +```javascript +// Load conversation +const conv = await base44.agents.getConversation(conversationId); +setMessages(conv.messages); + +// Subscribe to updates +const unsubscribe = base44.agents.subscribeToConversation(conversationId, (updated) => { + setMessages(updated.messages); +}); + +// Send message +async function sendMessage(text) { + await base44.agents.addMessage(conv, { role: "user", content: text }); +} + +// Cleanup on unmount +return () => unsubscribe(); +``` + +## Type Definitions + +### AgentConversation + +```typescript +/** An agent conversation containing messages exchanged with an AI agent. */ +interface AgentConversation { + /** Unique identifier for the conversation. */ + id: string; + /** Application ID. */ + app_id: string; + /** Name of the agent in this conversation. */ + agent_name: string; + /** ID of the user who created the conversation. */ + created_by_id: string; + /** When the conversation was created. */ + created_date: string; + /** When the conversation was last updated. */ + updated_date: string; + /** Array of messages in the conversation. */ + messages: AgentMessage[]; + /** Optional metadata associated with the conversation. */ + metadata?: Record; +} +``` + +### AgentMessage + +```typescript +/** A message in an agent conversation. */ +interface AgentMessage { + /** Unique identifier for the message. */ + id: string; + /** Role of the message sender. */ + role: "user" | "assistant" | "system"; + /** When the message was created. */ + created_date: string; + /** When the message was last updated. */ + updated_date: string; + /** Message content. */ + content?: string | Record; + /** Optional reasoning information for the message. */ + reasoning?: AgentMessageReasoning | null; + /** URLs to files attached to the message. */ + file_urls?: string[]; + /** Tool calls made by the agent. */ + tool_calls?: AgentMessageToolCall[]; + /** Token usage statistics. */ + usage?: AgentMessageUsage; + /** Whether the message is hidden from the user. */ + hidden?: boolean; + /** Custom context provided with the message. */ + custom_context?: AgentMessageCustomContext[]; + /** Model used to generate the message. */ + model?: string; + /** Checkpoint ID for the message. */ + checkpoint_id?: string; + /** Metadata about when and by whom the message was created. */ + metadata?: AgentMessageMetadata; +} +``` + +### Supporting Types + +```typescript +/** Reasoning information for an agent message. */ +interface AgentMessageReasoning { + /** When reasoning started. */ + start_date: string; + /** When reasoning ended. */ + end_date?: string; + /** Reasoning content. */ + content: string; +} + +/** A tool call made by the agent. */ +interface AgentMessageToolCall { + /** Tool call ID. */ + id: string; + /** Name of the tool called. */ + name: string; + /** Arguments passed to the tool as JSON string. */ + arguments_string: string; + /** Status of the tool call. */ + status: "running" | "success" | "error" | "stopped"; + /** Results from the tool call. */ + results?: string; +} + +/** Token usage statistics for an agent message. */ +interface AgentMessageUsage { + /** Number of tokens in the prompt. */ + prompt_tokens?: number; + /** Number of tokens in the completion. */ + completion_tokens?: number; +} + +/** Custom context provided with an agent message. */ +interface AgentMessageCustomContext { + /** Context message. */ + message: string; + /** Associated data for the context. */ + data: Record; + /** Type of context. */ + type: string; +} + +/** Metadata about when and by whom a message was created. */ +interface AgentMessageMetadata { + /** When the message was created. */ + created_date: string; + /** Email of the user who created the message. */ + created_by_email: string; + /** Full name of the user who created the message. */ + created_by_full_name: string; +} +``` + +### CreateConversationParams + +```typescript +/** Parameters for creating a new conversation. */ +interface CreateConversationParams { + /** The name of the agent to create a conversation with. */ + agent_name: string; + /** Optional metadata to attach to the conversation. */ + metadata?: Record; +} +``` + +### ModelFilterParams + +```typescript +/** Parameters for filtering, sorting, and paginating conversations. */ +interface ModelFilterParams { + /** Query object with field-value pairs for filtering. */ + q?: Record; + /** Sort parameter (e.g., "-created_date" for descending). */ + sort?: string | null; + /** Maximum number of results to return. */ + limit?: number | null; + /** Number of results to skip for pagination. */ + skip?: number | null; + /** Array of field names to include in the response. */ + fields?: string[] | null; +} +``` + +### AgentsModule + +```typescript +/** Agents module for managing AI agent conversations. */ +interface AgentsModule { + /** Gets all conversations from all agents in the app. */ + getConversations(): Promise; + + /** Gets a specific conversation by ID. */ + getConversation(conversationId: string): Promise; + + /** Lists conversations with filtering, sorting, and pagination. */ + listConversations(filterParams: ModelFilterParams): Promise; + + /** Creates a new conversation with an agent. */ + createConversation(conversation: CreateConversationParams): Promise; + + /** Adds a message to a conversation. */ + addMessage(conversation: AgentConversation, message: Partial): Promise; + + /** Subscribes to real-time updates for a conversation. Returns unsubscribe function. */ + subscribeToConversation(conversationId: string, onUpdate?: (conversation: AgentConversation) => void): () => void; + + /** Gets WhatsApp connection URL for an agent. */ + getWhatsAppConnectURL(agentName: string): string; +} +``` diff --git a/evals/fixtures/nextjs-todo/skills/base44-sdk/references/client.md b/evals/fixtures/nextjs-todo/skills/base44-sdk/references/client.md new file mode 100644 index 0000000..554bf2b --- /dev/null +++ b/evals/fixtures/nextjs-todo/skills/base44-sdk/references/client.md @@ -0,0 +1,257 @@ +# Client Setup + +How to create and configure the Base44 client. + +## Contents +- [In Base44-Generated Apps](#in-base44-generated-apps) +- [In External Apps](#in-external-apps) +- [In Backend Functions](#in-backend-functions) +- [Authentication Modes](#authentication-modes) (Anonymous, User, Service Role) +- [Available Modules](#available-modules) +- [Client Methods](#client-methods) +- [Client Configuration Options](#client-configuration-options) + +## In Base44-Generated Apps + +The client is pre-configured and available as `base44`. Just use it: + +```javascript +const tasks = await base44.entities.Task.list(); +``` + +## In External Apps + +Install the SDK and create a client: + +```bash +npm install @base44/sdk +``` + +```javascript +import { createClient } from "@base44/sdk"; + +// IMPORTANT: The parameter name is 'appId' (NOT 'clientId', NOT 'id') +// IMPORTANT: onError must be nested inside 'options' object +const base44 = createClient({ + appId: "your-app-id", // Required: Use 'appId' parameter + token: "optional-user-token", // Optional: for pre-authenticated requests + options: { // Optional: configuration options + onError: (error) => { // Optional: error handler (must be in options) + console.error("Base44 error:", error); + } + } +}); +``` + +**Common Mistakes:** +- ❌ `createClient({ clientId: "..." })` - WRONG parameter name +- ❌ `createClient({ id: "..." })` - WRONG parameter name +- ❌ `createClient({ appId: "...", onError: ... })` - WRONG: onError must be in options +- ✅ `createClient({ appId: "..." })` - CORRECT parameter name +- ✅ `createClient({ appId: "...", options: { onError: ... } })` - CORRECT: onError in options + +## In Backend Functions + +Use `createClientFromRequest` to get a client with the caller's auth context: + +```javascript +import { createClientFromRequest } from "@base44/sdk"; + +Deno.serve(async (req) => { + const base44 = createClientFromRequest(req); + + // Client inherits authentication from the request + const user = await base44.auth.me(); + + return Response.json({ user }); +}); +``` + +## Authentication Modes + +| Mode | How to Get | Permissions | +|------|-----------|-------------| +| **Anonymous** | `createClient({ appId })` without token | Public data only | +| **User** | After `loginViaEmailPassword()` or via `createClientFromRequest` | User's own data | +| **Service Role** | `base44.asServiceRole.*` in backend | Full admin access | + +## Anonymous Mode + +No authentication. Can only access public resources. + +```javascript +const base44 = createClient({ appId: "your-app-id" }); + +// Only works if Task entity allows anonymous read +const publicTasks = await base44.entities.Task.list(); +``` + +## User Mode + +After user logs in, the client automatically includes their token. + +```javascript +const base44 = createClient({ appId: "your-app-id" }); + +// Login sets the token +await base44.auth.loginViaEmailPassword("user@example.com", "password"); + +// Subsequent requests are authenticated +const user = await base44.auth.me(); +const myTasks = await base44.entities.Task.list(); // filtered by permissions +``` + +## Service Role Mode + +Admin-level access. **Backend only.** + +```javascript +// Inside a backend function +Deno.serve(async (req) => { + const base44 = createClientFromRequest(req); + + // User mode - respects permissions + const myTasks = await base44.entities.Task.list(); + + // Service role - bypasses permissions + const allTasks = await base44.asServiceRole.entities.Task.list(); + const allUsers = await base44.asServiceRole.entities.User.list(); + const oauthToken = await base44.asServiceRole.connectors.getAccessToken("slack"); + + return Response.json({ myTasks, allTasks }); +}); +``` + +## Available Modules + +The client exposes these modules: + +```javascript +base44.entities // CRUD operations +base44.auth // Authentication +base44.agents // AI conversations +base44.functions // Backend function invocation +base44.integrations // Third-party services +base44.analytics // Event tracking +base44.appLogs // App usage logging +base44.users // User invitations + +// Service role only (backend) +base44.asServiceRole.entities +base44.asServiceRole.agents +base44.asServiceRole.functions +base44.asServiceRole.integrations +base44.asServiceRole.appLogs +base44.asServiceRole.connectors +``` + +## Client Methods + +The client provides these methods: + +```javascript +// Set authentication token for all subsequent requests +base44.setToken(newToken); + +// Cleanup WebSocket connections (call when done with client) +base44.cleanup(); +``` + +### setToken + +Updates the authentication token for all subsequent API requests and WebSocket connections. + +```javascript +// After receiving a token (e.g., from external auth) +base44.setToken("new-jwt-token"); +``` + +### cleanup + +Disconnects WebSocket connections. Call when you're done with the client or when the component unmounts. + +```javascript +// Cleanup on component unmount (React example) +useEffect(() => { + return () => base44.cleanup(); +}, []); +``` + +## Client Configuration Options + +```javascript +createClient({ + appId: "your-app-id", // Required: MUST use 'appId' (not 'clientId' or 'id') + token: "jwt-token", // Optional: pre-set auth token + options: { // Optional: configuration options + onError: (error) => {} // Optional: global error handler (must be in options) + } +}); +``` + +**⚠️ Critical:** +- The parameter name is `appId`, not `clientId` or `id`. Using the wrong parameter name will cause errors. +- The `onError` handler must be nested inside the `options` object, not at the top level. + +## Type Definitions + +### CreateClientConfig + +```typescript +/** Configuration for creating a Base44 client. */ +interface CreateClientConfig { + /** The Base44 app ID (required). */ + appId: string; + /** User authentication token. Used to authenticate as a specific user. */ + token?: string; + /** Service role authentication token (backend only). */ + serviceToken?: string; + /** Additional client options. */ + options?: CreateClientOptions; +} + +/** Options for creating a Base44 client. */ +interface CreateClientOptions { + /** Optional error handler called whenever an API error occurs. */ + onError?: (error: Error) => void; +} +``` + +### Base44Client + +```typescript +/** The Base44 client instance. */ +interface Base44Client { + /** Entities module for CRUD operations on your data models. */ + entities: EntitiesModule; + /** Integrations module for calling pre-built integration endpoints. */ + integrations: IntegrationsModule; + /** Auth module for user authentication and management. */ + auth: AuthModule; + /** Functions module for invoking custom backend functions. */ + functions: FunctionsModule; + /** Agents module for managing AI agent conversations. */ + agents: AgentsModule; + /** App logs module for tracking app usage. */ + appLogs: AppLogsModule; + /** Analytics module for tracking custom events. */ + analytics: AnalyticsModule; + + /** Cleanup function to disconnect WebSocket connections. */ + cleanup(): void; + + /** Sets a new authentication token for all subsequent requests. */ + setToken(newToken: string): void; + + /** Provides access to modules with elevated service role permissions (backend only). */ + readonly asServiceRole: { + entities: EntitiesModule; + integrations: IntegrationsModule; + connectors: ConnectorsModule; + functions: FunctionsModule; + agents: AgentsModule; + appLogs: AppLogsModule; + cleanup(): void; + }; +} +``` diff --git a/evals/fixtures/nextjs-todo/skills/base44-sdk/references/connectors.md b/evals/fixtures/nextjs-todo/skills/base44-sdk/references/connectors.md new file mode 100644 index 0000000..f3fbcb4 --- /dev/null +++ b/evals/fixtures/nextjs-todo/skills/base44-sdk/references/connectors.md @@ -0,0 +1,113 @@ +# Connectors Module + +OAuth token management for external services. **Service role only** (backend functions). + +## Method + +```javascript +base44.asServiceRole.connectors.getAccessToken(integrationType): Promise +``` + +Returns a raw OAuth access token for the specified service. + +## Supported Services + +| Integration Type | Service | +|-----------------|---------| +| `"googlecalendar"` | Google Calendar | +| `"googledrive"` | Google Drive | +| `"slack"` | Slack | +| `"notion"` | Notion | +| `"salesforce"` | Salesforce | +| `"hubspot"` | HubSpot | +| `"linkedin"` | LinkedIn | +| `"tiktok"` | TikTok | +| `"github"` | GitHub | + +## Example Usage + +```javascript +// Backend function only +Deno.serve(async (req) => { + const base44 = createClientFromRequest(req); + + // Get OAuth token for Slack + const slackToken = await base44.asServiceRole.connectors.getAccessToken("slack"); + + // Use token directly with Slack API + const response = await fetch("https://slack.com/api/chat.postMessage", { + method: "POST", + headers: { + "Authorization": `Bearer ${slackToken}`, + "Content-Type": "application/json" + }, + body: JSON.stringify({ + channel: "#general", + text: "Hello from Base44!" + }) + }); + + return Response.json(await response.json()); +}); +``` + +## Google Calendar Example + +```javascript +Deno.serve(async (req) => { + const base44 = createClientFromRequest(req); + + const token = await base44.asServiceRole.connectors.getAccessToken("googlecalendar"); + + // List upcoming events + const response = await fetch( + "https://www.googleapis.com/calendar/v3/calendars/primary/events?" + + new URLSearchParams({ + maxResults: "10", + orderBy: "startTime", + singleEvents: "true", + timeMin: new Date().toISOString() + }), + { + headers: { "Authorization": `Bearer ${token}` } + } + ); + + const events = await response.json(); + return Response.json(events); +}); +``` + +## Setup Requirements + +1. **Builder plan** or higher +2. **Backend functions** enabled +3. **Connector configured** in Base44 dashboard (OAuth flow completed) + +## Important Notes + +- **One account per connector per app**: All users share the same connected account +- **Backend only**: `connectors` module not available in frontend code +- **Service role required**: Must use `base44.asServiceRole.connectors` +- **You handle the API calls**: Base44 only provides the token; you make the actual API requests +- **Token refresh**: Base44 handles token refresh automatically + +## Type Definitions + +```typescript +/** + * The type of external integration/connector. + * Examples: 'googlecalendar', 'slack', 'github', 'notion', etc. + */ +type ConnectorIntegrationType = string; + +/** Connectors module for managing OAuth tokens for external services. */ +interface ConnectorsModule { + /** + * Retrieves an OAuth access token for a specific external integration type. + * @param integrationType - The type of integration (e.g., 'googlecalendar', 'slack'). + * @returns Promise resolving to the access token string. + */ + getAccessToken(integrationType: ConnectorIntegrationType): Promise; +} +``` diff --git a/evals/fixtures/nextjs-todo/skills/base44-sdk/references/entities.md b/evals/fixtures/nextjs-todo/skills/base44-sdk/references/entities.md new file mode 100644 index 0000000..a7bdf35 --- /dev/null +++ b/evals/fixtures/nextjs-todo/skills/base44-sdk/references/entities.md @@ -0,0 +1,253 @@ +# Entities Module + +CRUD operations on data models. Access via `base44.entities.EntityName.method()`. + +## Contents +- [Methods](#methods) +- [Examples](#examples) (Create, Bulk Create, List, Filter, Get, Update, Delete, Subscribe) +- [User Entity](#user-entity) +- [Service Role Access](#service-role-access) +- [Permissions](#permissions) + +## Methods + +| Method | Signature | Description | +|--------|-----------|-------------| +| `create(data)` | `Promise` | Create one record | +| `bulkCreate(dataArray)` | `Promise` | Create multiple records | +| `list(sort?, limit?, skip?, fields?)` | `Promise` | Get all records (paginated) | +| `filter(query, sort?, limit?, skip?, fields?)` | `Promise` | Get records matching conditions | +| `get(id)` | `Promise` | Get single record by ID | +| `update(id, data)` | `Promise` | Update record (partial update) | +| `delete(id)` | `Promise` | Delete record by ID | +| `deleteMany(query)` | `Promise` | Delete all matching records | +| `importEntities(file)` | `Promise` | Import from CSV (frontend only) | +| `subscribe(callback)` | `() => void` | Subscribe to realtime updates (returns unsubscribe function) | + +## Examples + +### Create + +```javascript +const task = await base44.entities.Task.create({ + title: "Complete documentation", + status: "pending", + dueDate: "2024-12-31" +}); +``` + +### Bulk Create + +```javascript +const tasks = await base44.entities.Task.bulkCreate([ + { title: "Task 1", status: "pending" }, + { title: "Task 2", status: "pending" } +]); +``` + +### List with Pagination + +```javascript +// Get first 10 records, sorted by created_date descending +const tasks = await base44.entities.Task.list( + "-created_date", // sort (string: prefix with - for descending) + 10, // limit + 0 // skip +); + +// Get next page +const page2 = await base44.entities.Task.list("-created_date", 10, 10); +``` + +### Filter + +```javascript +// Simple filter +const pending = await base44.entities.Task.filter({ status: "pending" }); + +// Multiple conditions +const myPending = await base44.entities.Task.filter({ + status: "pending", + assignedTo: userId +}); + +// With sort, limit, skip +const recent = await base44.entities.Task.filter( + { status: "pending" }, + "-created_date", // sort (string: prefix with - for descending) + 5, + 0 +); + +// Select specific fields +const titles = await base44.entities.Task.filter( + { status: "pending" }, + null, + null, + null, + ["id", "title"] +); +``` + +### Get by ID + +```javascript +const task = await base44.entities.Task.get("task-id-123"); +``` + +### Update + +```javascript +// Partial update - only specified fields change +await base44.entities.Task.update("task-id-123", { + status: "completed", + completedAt: new Date().toISOString() +}); +``` + +### Delete + +```javascript +// Single record +await base44.entities.Task.delete("task-id-123"); + +// Multiple records matching query +await base44.entities.Task.deleteMany({ status: "archived" }); +``` + +### Subscribe to Realtime Updates + +```javascript +// Subscribe to all changes on Task entity +const unsubscribe = base44.entities.Task.subscribe((event) => { + console.log(`Task ${event.id} was ${event.type}:`, event.data); + // event.type is "create", "update", or "delete" +}); + +// Later: unsubscribe to stop receiving updates +unsubscribe(); +``` + +**Event structure:** +```javascript +{ + type: "create" | "update" | "delete", + data: { ... }, // the entity data + id: "entity-id", // the affected entity's ID + timestamp: "2024-01-15T10:30:00Z" +} +``` + +## User Entity + +Every app has a built-in `User` entity with special rules: + +- Regular users can only read/update **their own** record +- Cannot create users via `entities.create()` - use `auth.register()` instead +- Service role has full access to all user records + +```javascript +// Get current user's record +const me = await base44.entities.User.get(currentUserId); + +// Service role: get any user +const anyUser = await base44.asServiceRole.entities.User.get(userId); +``` + +## Service Role Access + +For admin-level operations (bypass user permissions): + +```javascript +// Backend only +const allTasks = await base44.asServiceRole.entities.Task.list(); +const allUsers = await base44.asServiceRole.entities.User.list(); +``` + +## Permissions (RLS & FLS) + +Data access is controlled by **Row Level Security (RLS)** and **Field Level Security (FLS)** rules defined in entity schemas. + +1. **Authentication level**: anonymous, authenticated, or service role +2. **RLS rules**: Control which records (rows) users can create/read/update/delete +3. **FLS rules**: Control which fields users can read/write within accessible records + +Operations succeed or fail based on these rules - no partial results. + +RLS and FLS are configured in entity schema files (`base44/entities/*.jsonc`). See [entities-create.md](../../base44-cli/references/entities-create.md#row-level-security-rls) for configuration details. + +**Note:** `asServiceRole` sets the user's role to `"admin"` but does NOT bypass RLS. Your RLS rules must include admin access (e.g., `{ "user_condition": { "role": "admin" } }`) for service role operations to succeed. + +## Type Definitions + +### RealtimeEvent + +```typescript +/** Event types for realtime entity updates. */ +type RealtimeEventType = "create" | "update" | "delete"; + +/** Payload received when a realtime event occurs. */ +interface RealtimeEvent { + /** The type of change that occurred. */ + type: RealtimeEventType; + /** The entity data. */ + data: any; + /** The unique identifier of the affected entity. */ + id: string; + /** ISO 8601 timestamp of when the event occurred. */ + timestamp: string; +} + +/** Callback function invoked when a realtime event occurs. */ +type RealtimeCallback = (event: RealtimeEvent) => void; + +/** Function returned from subscribe, call it to unsubscribe. */ +type Subscription = () => void; +``` + +### EntityHandler + +```typescript +/** Entity handler providing CRUD operations for a specific entity type. */ +interface EntityHandler { + /** Lists records with optional pagination and sorting. */ + list(sort?: string, limit?: number, skip?: number, fields?: string[]): Promise; + + /** Filters records based on a query. */ + filter(query: Record, sort?: string, limit?: number, skip?: number, fields?: string[]): Promise; + + /** Gets a single record by ID. */ + get(id: string): Promise; + + /** Creates a new record. */ + create(data: Record): Promise; + + /** Updates an existing record. */ + update(id: string, data: Record): Promise; + + /** Deletes a single record by ID. */ + delete(id: string): Promise; + + /** Deletes multiple records matching a query. */ + deleteMany(query: Record): Promise; + + /** Creates multiple records in a single request. */ + bulkCreate(data: Record[]): Promise; + + /** Imports records from a file (frontend only). */ + importEntities(file: File): Promise; + + /** Subscribes to realtime updates. Returns unsubscribe function. */ + subscribe(callback: RealtimeCallback): Subscription; +} +``` + +### EntitiesModule + +```typescript +/** Entities module for managing app data. */ +interface EntitiesModule { + /** Access any entity by name dynamically. */ + [entityName: string]: EntityHandler; +} +``` diff --git a/evals/fixtures/nextjs-todo/skills/base44-sdk/references/functions.md b/evals/fixtures/nextjs-todo/skills/base44-sdk/references/functions.md new file mode 100644 index 0000000..8072465 --- /dev/null +++ b/evals/fixtures/nextjs-todo/skills/base44-sdk/references/functions.md @@ -0,0 +1,216 @@ +# Functions Module + +Invoke custom backend functions via `base44.functions`. + +## Contents +- [Method](#method) +- [Invoking Functions](#invoking-functions) (Frontend, File Upload, Service Role, REST API) +- [Writing Backend Functions](#writing-backend-functions) (Basic, Service Role, Secrets, Errors) +- [Setup Requirements](#setup-requirements) +- [Authentication Modes](#authentication-modes) + +## Method + +```javascript +base44.functions.invoke(functionName, data): Promise +``` + +- `functionName`: Name of the backend function +- `data`: Object of parameters (sent as JSON, or multipart if contains File objects) +- Returns: Whatever the function returns + +## Invoking Functions + +### From Frontend + +```javascript +const result = await base44.functions.invoke("processOrder", { + orderId: "order-123", + action: "ship" +}); + +console.log(result); +``` + +### With File Upload + +```javascript +const fileInput = document.querySelector('input[type="file"]'); +const file = fileInput.files[0]; + +// Automatically uses multipart/form-data when File objects present +const result = await base44.functions.invoke("uploadDocument", { + file: file, + category: "invoices" +}); +``` + +### With Service Role (Backend) + +```javascript +// Inside another backend function +const result = await base44.asServiceRole.functions.invoke("adminTask", { + userId: "user-123" +}); +``` + +### Via REST API (curl) + +Functions can be called via HTTP POST to your app domain: + +```bash +curl -X POST "https:///functions/" \ + -H "Content-Type: application/json" \ + -d '{"key": "value"}' +``` + +## Writing Backend Functions + +Backend functions run on Deno. Must export using `Deno.serve()`. + +### Required Directory Structure + +Each function must be in its own subdirectory under `base44/functions/` with a configuration file: + +``` +base44/ + functions/ + process-order/ # kebab-case directory name + function.jsonc # required configuration + index.ts # entry point +``` + +**function.jsonc:** +```jsonc +{ + "name": "process-order", + "entry": "index.ts" +} +``` + +For complete setup and deployment instructions, see [functions-create.md](../../base44-cli/references/functions-create.md) in base44-cli. + +### Basic Structure + +```javascript +// base44/functions/process-order/index.ts +import { createClientFromRequest } from "npm:@base44/sdk"; + +Deno.serve(async (req) => { + // Get authenticated client from request + const base44 = createClientFromRequest(req); + + // Parse input + const { orderId, action } = await req.json(); + + // Your logic here + const order = await base44.entities.Orders.get(orderId); + + // Return response + return Response.json({ + success: true, + order: order + }); +}); +``` + +### With Service Role Access + +```javascript +import { createClientFromRequest } from "npm:@base44/sdk"; + +Deno.serve(async (req) => { + const base44 = createClientFromRequest(req); + + // Check user is authenticated + const user = await base44.auth.me(); + if (!user) { + return Response.json({ error: "Unauthorized" }, { status: 401 }); + } + + // Use service role for admin operations + const allOrders = await base44.asServiceRole.entities.Orders.list(); + + return Response.json({ orders: allOrders }); +}); +``` + +### Using Secrets + +```javascript +Deno.serve(async (req) => { + // Access environment variables (configured in app settings) + const apiKey = Deno.env.get("STRIPE_API_KEY"); + + const response = await fetch("https://api.stripe.com/v1/charges", { + headers: { + "Authorization": `Bearer ${apiKey}` + } + }); + + return Response.json(await response.json()); +}); +``` + +### Error Handling + +```javascript +import { createClientFromRequest } from "npm:@base44/sdk"; + +Deno.serve(async (req) => { + try { + const base44 = createClientFromRequest(req); + const { orderId } = await req.json(); + + const order = await base44.entities.Orders.get(orderId); + if (!order) { + return Response.json( + { error: "Order not found" }, + { status: 404 } + ); + } + + return Response.json({ order }); + + } catch (error) { + return Response.json( + { error: error.message }, + { status: 500 } + ); + } +}); +``` + +## Setup Requirements + +1. Enable Backend Functions in app settings (requires appropriate plan) +2. Create function files in `/functions` folder +3. Configure secrets via app dashboard for API keys + +## Authentication Modes + +| Mode | Context | Permissions | +|------|---------|-------------| +| User | `base44.functions.invoke()` | Runs under calling user's permissions | +| Service Role | `base44.asServiceRole.functions.invoke()` | Admin-level access | + +Inside the function, use `createClientFromRequest(req)` to get a client that inherits the caller's auth context. + +## Type Definitions + +```typescript +/** Functions module for invoking custom backend functions. */ +interface FunctionsModule { + /** + * Invokes a custom backend function by name. + * + * If any parameter is a File object, the request will automatically be + * sent as multipart/form-data. Otherwise, it will be sent as JSON. + * + * @param functionName - The name of the function to invoke. + * @param data - An object containing named parameters for the function. + * @returns Promise resolving to the function's response. + */ + invoke(functionName: string, data: Record): Promise; +} +``` diff --git a/evals/fixtures/nextjs-todo/skills/base44-sdk/references/integrations.md b/evals/fixtures/nextjs-todo/skills/base44-sdk/references/integrations.md new file mode 100644 index 0000000..f356a18 --- /dev/null +++ b/evals/fixtures/nextjs-todo/skills/base44-sdk/references/integrations.md @@ -0,0 +1,377 @@ +# Integrations Module + +Access third-party services via `base44.integrations`. + +## Types of Integrations + +1. **Core/Built-in**: AI, email, file uploads (available by default) +2. **Catalog integrations**: Pre-built connectors from Base44 catalog +3. **Custom integrations**: Your own OpenAPI-based integrations + +## Accessing Integrations + +```javascript +// Core integrations +base44.integrations.Core.FunctionName(params) + +// Custom integrations +base44.integrations.custom.call(slug, operationId, params) +``` + +## Core Integrations + +### InvokeLLM (AI Text Generation) + +Generate text or structured JSON data using AI models. + +```javascript +// Basic prompt - returns string +const response = await base44.integrations.Core.InvokeLLM({ + prompt: "Summarize this text: ..." +}); + +// With internet context (uses Google Search, Maps, News) +const response = await base44.integrations.Core.InvokeLLM({ + prompt: "What's the current weather in NYC?", + add_context_from_internet: true +}); + +// Structured JSON response - returns object +const response = await base44.integrations.Core.InvokeLLM({ + prompt: "Analyze the sentiment of: 'Great product but slow shipping'", + response_json_schema: { + type: "object", + properties: { + sentiment: { type: "string", enum: ["positive", "negative", "mixed"] }, + score: { type: "number", description: "Score from 1-10" }, + key_points: { type: "array", items: { type: "string" } } + } + } +}); +// Returns: { sentiment: "mixed", score: 7, key_points: ["great product", "slow shipping"] } + +// With file attachments (uploaded via UploadFile) +const response = await base44.integrations.Core.InvokeLLM({ + prompt: "Describe what's in this image", + file_urls: ["https://...uploaded_image.png"] +}); +``` + +**Parameters:** +- `prompt` (string, required): The prompt text to send to the model +- `add_context_from_internet` (boolean, optional): If true, uses Google Search/Maps/News for real-time context +- `response_json_schema` (object, optional): JSON schema for structured output +- `file_urls` (string[], optional): URLs of uploaded files for context + +### GenerateImage + +Create AI-generated images from text prompts. + +```javascript +const { url } = await base44.integrations.Core.GenerateImage({ + prompt: "A serene mountain landscape with a lake" +}); +console.log(url); // https://...generated_image.png +``` + +### SendEmail + +Send emails to registered users. Every app gets this integration (no plan upgrade required). + +```javascript +await base44.integrations.Core.SendEmail({ + to: "user@example.com", + subject: "Welcome!", + body: "

Hello

Welcome to our app.

", + from_name: "My App" // optional, defaults to app name +}); +``` + +**Parameters:** +- `to` (string, required): Recipient email address +- `subject` (string, required): Email subject line +- `body` (string, required): Plain text or HTML email body +- `from_name` (string, optional): Sender name displayed to recipient + +**Limitations:** +- 1 credit per email (2 credits with custom domain) + +### UploadFile (Public) + +Upload files to public storage. + +```javascript +const fileInput = document.querySelector('input[type="file"]'); +const { file_url } = await base44.integrations.Core.UploadFile({ + file: fileInput.files[0] +}); +console.log(file_url); // https://...uploaded_file.pdf +``` + +### UploadPrivateFile + +Upload files to private storage that requires a signed URL to access. + +```javascript +const { file_uri } = await base44.integrations.Core.UploadPrivateFile({ + file: fileInput.files[0] +}); +console.log(file_uri); // "private/user123/document.pdf" + +// Create a signed URL to access the file +const { signed_url } = await base44.integrations.Core.CreateFileSignedUrl({ + file_uri, + expires_in: 3600 // URL expires in 1 hour (default: 300 seconds) +}); +console.log(signed_url); // Temporary URL to access the private file +``` + +### CreateFileSignedUrl + +Generate temporary access links for private files. + +```javascript +const { signed_url } = await base44.integrations.Core.CreateFileSignedUrl({ + file_uri: "private/user123/document.pdf", + expires_in: 7200 // 2 hours +}); +``` + +**Parameters:** +- `file_uri` (string, required): URI from UploadPrivateFile +- `expires_in` (number, optional): Expiration time in seconds (default: 300) + +### ExtractDataFromUploadedFile + +Extract structured data from uploaded files using AI. + +```javascript +// First upload the file +const { file_url } = await base44.integrations.Core.UploadFile({ file }); + +// Then extract structured data +const result = await base44.integrations.Core.ExtractDataFromUploadedFile({ + file_url, + json_schema: { + type: "object", + properties: { + invoice_number: { type: "string" }, + total_amount: { type: "number" }, + date: { type: "string" }, + vendor_name: { type: "string" } + } + } +}); +console.log(result); // { invoice_number: "INV-12345", total_amount: 1250.00, ... } +``` + +## Custom Integrations + +Custom integrations allow workspace administrators to connect any external API by importing an OpenAPI specification. Use `base44.integrations.custom.call()` to invoke them. + +### Syntax + +```javascript +const response = await base44.integrations.custom.call( + slug, // Integration identifier (set by admin) + operationId, // Endpoint in "method:path" format + params // Optional: payload, pathParams, queryParams +); +``` + +### Examples + +```javascript +// GET request with query params +const response = await base44.integrations.custom.call( + "my-crm", + "get:/contacts", + { queryParams: { limit: 10, status: "active" } } +); + +// POST request with body +const response = await base44.integrations.custom.call( + "my-crm", + "post:/contacts", + { payload: { name: "John Doe", email: "john@example.com" } } +); + +// Request with path parameters +const response = await base44.integrations.custom.call( + "github", + "post:/repos/{owner}/{repo}/issues", + { + pathParams: { owner: "myorg", repo: "myrepo" }, + payload: { title: "Bug report", body: "Something is broken" } + } +); +``` + +### Response Structure + +```javascript +{ + success: true, // Whether external API returned 2xx + status_code: 200, // HTTP status code + data: { ... } // Response from external API +} +``` + +## Requirements + +- **Core integrations**: Available on all plans +- **Catalog/Custom integrations**: Require Builder plan or higher + +## Type Definitions + +### Core Integration Parameters + +```typescript +/** Parameters for the InvokeLLM function. */ +interface InvokeLLMParams { + /** The prompt text to send to the model. */ + prompt: string; + /** If true, uses Google Search/Maps/News for real-time context. */ + add_context_from_internet?: boolean; + /** JSON schema for structured output. If provided, returns object instead of string. */ + response_json_schema?: object; + /** File URLs (from UploadFile) to provide as context. */ + file_urls?: string[]; +} + +/** Parameters for the GenerateImage function. */ +interface GenerateImageParams { + /** Description of the image to generate. */ + prompt: string; +} + +/** Result from GenerateImage. */ +interface GenerateImageResult { + /** URL of the generated image. */ + url: string; +} + +/** Parameters for the UploadFile function. */ +interface UploadFileParams { + /** The file object to upload. */ + file: File; +} + +/** Result from UploadFile. */ +interface UploadFileResult { + /** URL of the uploaded file. */ + file_url: string; +} + +/** Parameters for the SendEmail function. */ +interface SendEmailParams { + /** Recipient email address. */ + to: string; + /** Email subject line. */ + subject: string; + /** Plain text or HTML email body. */ + body: string; + /** Sender name (defaults to app name). */ + from_name?: string; +} + +/** Parameters for ExtractDataFromUploadedFile. */ +interface ExtractDataFromUploadedFileParams { + /** URL of the uploaded file. */ + file_url: string; + /** JSON schema defining fields to extract. */ + json_schema: object; +} + +/** Parameters for UploadPrivateFile. */ +interface UploadPrivateFileParams { + /** The file object to upload. */ + file: File; +} + +/** Result from UploadPrivateFile. */ +interface UploadPrivateFileResult { + /** URI of the private file (used for signed URLs). */ + file_uri: string; +} + +/** Parameters for CreateFileSignedUrl. */ +interface CreateFileSignedUrlParams { + /** URI from UploadPrivateFile. */ + file_uri: string; + /** Expiration time in seconds (default: 300). */ + expires_in?: number; +} + +/** Result from CreateFileSignedUrl. */ +interface CreateFileSignedUrlResult { + /** Temporary signed URL to access the file. */ + signed_url: string; +} +``` + +### CoreIntegrations + +```typescript +/** Core package containing built-in Base44 integration functions. */ +interface CoreIntegrations { + InvokeLLM(params: InvokeLLMParams): Promise; + GenerateImage(params: GenerateImageParams): Promise; + UploadFile(params: UploadFileParams): Promise; + SendEmail(params: SendEmailParams): Promise; + ExtractDataFromUploadedFile(params: ExtractDataFromUploadedFileParams): Promise; + UploadPrivateFile(params: UploadPrivateFileParams): Promise; + CreateFileSignedUrl(params: CreateFileSignedUrlParams): Promise; +} +``` + +### Custom Integrations + +```typescript +/** Parameters for calling a custom integration endpoint. */ +interface CustomIntegrationCallParams { + /** Request body payload. */ + payload?: Record; + /** Path parameters to substitute in the URL. */ + pathParams?: Record; + /** Query string parameters. */ + queryParams?: Record; + /** Additional headers for this request. */ + headers?: Record; +} + +/** Response from a custom integration call. */ +interface CustomIntegrationCallResponse { + /** Whether the external API returned a 2xx status code. */ + success: boolean; + /** The HTTP status code from the external API. */ + status_code: number; + /** The response data from the external API. */ + data: any; +} + +/** Module for calling custom workspace-level API integrations. */ +interface CustomIntegrationsModule { + /** + * Call a custom integration endpoint. + * @param slug - The integration's unique identifier. + * @param operationId - The operation ID from the OpenAPI spec. + * @param params - Optional payload, pathParams, queryParams, headers. + */ + call(slug: string, operationId: string, params?: CustomIntegrationCallParams): Promise; +} +``` + +### IntegrationsModule + +```typescript +/** Integrations module for calling integration endpoints. */ +type IntegrationsModule = { + /** Core package with built-in integrations. */ + Core: CoreIntegrations; + /** Custom integrations module. */ + custom: CustomIntegrationsModule; + /** Additional integration packages (dynamic). */ + [packageName: string]: any; +}; +``` diff --git a/evals/fixtures/nextjs-todo/skills/base44-sdk/references/users.md b/evals/fixtures/nextjs-todo/skills/base44-sdk/references/users.md new file mode 100644 index 0000000..4205b06 --- /dev/null +++ b/evals/fixtures/nextjs-todo/skills/base44-sdk/references/users.md @@ -0,0 +1,82 @@ +# Users Module + +Invite users to the app via `base44.users`. + +## Contents +- [Methods](#methods) +- [Examples](#examples) (Invite User) +- [Roles](#roles) +- [Notes](#notes) + +## Methods + +| Method | Signature | Description | +|--------|-----------|-------------| +| `inviteUser(user_email, role)` | `Promise` | Invite a user to the app | + +## Examples + +### Invite User + +```javascript +// Invite a user with "user" role +await base44.users.inviteUser("newuser@example.com", "user"); + +// Invite an admin +await base44.users.inviteUser("admin@example.com", "admin"); +``` + +### Invite Multiple Users + +```javascript +const usersToInvite = [ + { email: "user1@example.com", role: "user" }, + { email: "user2@example.com", role: "user" }, + { email: "manager@example.com", role: "admin" } +]; + +for (const user of usersToInvite) { + await base44.users.inviteUser(user.email, user.role); + console.log(`Invited ${user.email} as ${user.role}`); +} +``` + +## Roles + +The `role` parameter must be one of: + +| Role | Description | +|------|-------------| +| `"user"` | Standard user with default permissions | +| `"admin"` | Administrator with elevated permissions | + +**Note:** Only `"user"` and `"admin"` are valid role values. An error will be thrown if you pass any other value. + +## Notes + +- **Email invitation**: The invited user receives an email with a link to join the app +- **Duplicate handling**: Inviting an existing user will re-send the invitation +- **Also available in auth**: `base44.auth.inviteUser()` provides the same functionality +- **Role validation**: Only `"user"` or `"admin"` are accepted + +```javascript +// These are equivalent: +await base44.users.inviteUser("newuser@example.com", "user"); +await base44.auth.inviteUser("newuser@example.com", "user"); +``` + +## Type Definitions + +```typescript +/** Users module for inviting users to the app. */ +interface UsersModule { + /** + * Invite a user to the application. + * @param user_email - User's email address. + * @param role - User's role ('user' or 'admin'). + * @returns Promise resolving when the invitation is sent. + * @throws Error if role is not 'user' or 'admin'. + */ + inviteUser(user_email: string, role: "user" | "admin"): Promise; +} +``` diff --git a/evals/fixtures/react-task-manager/AGENTS.md b/evals/fixtures/react-task-manager/AGENTS.md new file mode 100644 index 0000000..e688fdc --- /dev/null +++ b/evals/fixtures/react-task-manager/AGENTS.md @@ -0,0 +1,20 @@ +# Task Manager + +A React + Vite application using Base44 for backend services. + +## Project Structure + +- `src/` - React source code +- `src/components/` - React components +- `src/lib/base44.ts` - Base44 SDK client +- `base44/` - Base44 configuration +- `base44/entities/` - Entity definitions (Task already exists) + +## Existing Entities + +- **Task**: title, description, status (pending/in_progress/completed), priority, due_date + +## Available Skills + +- **base44-cli**: CLI for managing Base44 entities, functions, and deployment +- **base44-sdk**: SDK for building features with Base44 APIs (entities, auth, etc.) diff --git a/evals/fixtures/react-task-manager/eval.json b/evals/fixtures/react-task-manager/eval.json new file mode 100644 index 0000000..0868397 --- /dev/null +++ b/evals/fixtures/react-task-manager/eval.json @@ -0,0 +1,68 @@ +{ + "$schema": "../../eval.schema.json", + "name": "react-task-manager-sdk", + "description": "Test implementing a TaskList component using Base44 SDK to fetch and display tasks", + "prompt": "Implement the TaskList component in src/components/TaskList.tsx. It should fetch all tasks using the Base44 SDK (base44.entities.Task.list()), display them in a list showing title, status, and due_date, and allow marking tasks as completed by clicking on them (using base44.entities.Task.update()).", + "expectedSkills": ["base44-sdk"], + "checks": [ + { + "type": "file-content", + "description": "Uses entities.Task.list()", + "filePath": "src/components/TaskList.tsx", + "pattern": "entities\\.Task\\.list\\(" + }, + { + "type": "file-content", + "description": "Uses entities.Task.update()", + "filePath": "src/components/TaskList.tsx", + "pattern": "entities\\.Task\\.update\\(" + }, + { + "type": "file-content", + "description": "Imports base44 client", + "filePath": "src/components/TaskList.tsx", + "pattern": "import.*base44|from ['\"].*base44" + }, + { + "type": "file-content", + "description": "Uses React hooks for state", + "filePath": "src/components/TaskList.tsx", + "pattern": "useState|useEffect" + }, + { + "type": "contains", + "description": "Output mentions SDK or entities", + "value": "entities" + }, + { + "type": "file-content", + "description": "Task.update called with (id, data) two-parameter signature", + "filePath": "src/components/TaskList.tsx", + "pattern": "Task\\.update\\s*\\(\\s*\\w+[^,]*,\\s*\\{" + }, + { + "type": "file-content", + "description": "Uses async/await pattern with entity calls", + "filePath": "src/components/TaskList.tsx", + "pattern": "await\\s+.*entities\\.Task\\." + }, + { + "type": "file-content-excluded", + "description": "Does not use .find() (hallucinated method — correct is .filter())", + "filePath": "src/components/TaskList.tsx", + "pattern": "Task\\.find\\(" + }, + { + "type": "file-content-excluded", + "description": "Does not use .findOne() (hallucinated — correct is .get())", + "filePath": "src/components/TaskList.tsx", + "pattern": "Task\\.findOne\\(" + }, + { + "type": "file-content-excluded", + "description": "Does not use .insert() (hallucinated — correct is .create())", + "filePath": "src/components/TaskList.tsx", + "pattern": "Task\\.insert\\(" + } + ] +} diff --git a/evals/fixtures/react-task-manager/project/.claude/settings.json b/evals/fixtures/react-task-manager/project/.claude/settings.json new file mode 100644 index 0000000..ab60505 --- /dev/null +++ b/evals/fixtures/react-task-manager/project/.claude/settings.json @@ -0,0 +1,6 @@ +{ + "permissions": { + "allow": ["Read", "Write", "Edit", "Bash", "Glob", "Grep"] + }, + "skills": ["./skills"] +} diff --git a/evals/fixtures/react-task-manager/project/AGENTS.md b/evals/fixtures/react-task-manager/project/AGENTS.md new file mode 100644 index 0000000..6c0f2be --- /dev/null +++ b/evals/fixtures/react-task-manager/project/AGENTS.md @@ -0,0 +1,25 @@ +IMPORTANT: Prefer retrieval-led reasoning over pre-training-led reasoning. Base44 is NOT Firebase, Supabase, or any other SDK you know. Every API name below is the ONLY correct form. Always verify against this index and the referenced docs. + +[Base44 SDK Docs Index]|root: ./skills/base44-sdk/references +|entities.md: CRUD - create(data), list(), filter(query), get(id), update(id,data), delete(id), subscribe(cb) +|auth.md: Auth - loginViaEmailPassword(), loginWithProvider('google'), me(), register(), logout() +|base44-agents.md: Agents - createConversation({agent_name}), addMessage(conv,{role,content}), subscribeToConversation(id,cb) +|functions.md: Functions - invoke(name, data) +|integrations.md: Core - InvokeLLM({prompt}), SendEmail({to,subject,body}), UploadFile({file}) +|client.md: Client - createClient({appId}), import from "@base44/sdk" +|QUICK_REFERENCE.md: All method signatures + +[Base44 CLI Docs Index]|root: ./skills/base44-cli/references +|entities-create.md: Entity schema - {name:"PascalCase", type:"object", properties:{}, required:[]} +|functions-create.md: Function - function.jsonc {name,entry} + index.ts with Deno.serve + createClientFromRequest +|create.md: Project creation - npx base44 create name -p . +|deploy.md: Deploy - npx base44 deploy -y +|agents-push.md: Agent config - {name:"snake_case", description, instructions, tool_configs:[{entity_name,allowed_operations}|{function_name,description}]} + +[Common Mistakes - NEVER use these hallucinated names] +|SDK: Do NOT use find() → use filter(), findOne() → get(), insert() → create(), remove() → delete(), onChange() → subscribe() +|SDK: Do NOT use signInWithGoogle() → use loginWithProvider('google'), functions.call() → functions.invoke() +|SDK: Do NOT use ai.generate() → use integrations.Core.InvokeLLM({prompt}) +|CLI: Entity names must be PascalCase alphanumeric. File names must be kebab-case.jsonc +|CLI: Agent names must be lowercase with underscores only. Function entry field is "entry" not "entrypoint" +|CLI: Backend functions import from "npm:@base44/sdk" (Deno requires npm: prefix) diff --git a/evals/fixtures/react-task-manager/project/CLAUDE.md b/evals/fixtures/react-task-manager/project/CLAUDE.md new file mode 100644 index 0000000..724ce4d --- /dev/null +++ b/evals/fixtures/react-task-manager/project/CLAUDE.md @@ -0,0 +1,7 @@ +# Base44 Development Guidelines + +When working with Base44 projects, first explore the existing project structure to understand what already exists. Look at existing code, config files, and patterns in use. + +Then consult the AGENTS.md index in the project root for correct API patterns. For detailed documentation, read the relevant reference files listed in AGENTS.md. + +IMPORTANT: Prefer retrieval-led reasoning over pre-training-led reasoning. Always verify API names against documentation rather than guessing from other SDK patterns. diff --git a/evals/fixtures/react-task-manager/project/base44/config.jsonc b/evals/fixtures/react-task-manager/project/base44/config.jsonc new file mode 100644 index 0000000..1a7bf1b --- /dev/null +++ b/evals/fixtures/react-task-manager/project/base44/config.jsonc @@ -0,0 +1,10 @@ +{ + "name": "Task Manager", + "description": "A task management application", + "entitiesDir": "./entities", + "functionsDir": "./functions", + "site": { + "buildCommand": "npm run build", + "outputDirectory": "dist" + } +} diff --git a/evals/fixtures/react-task-manager/project/base44/entities/task.jsonc b/evals/fixtures/react-task-manager/project/base44/entities/task.jsonc new file mode 100644 index 0000000..aaa2cb5 --- /dev/null +++ b/evals/fixtures/react-task-manager/project/base44/entities/task.jsonc @@ -0,0 +1,29 @@ +{ + "name": "Task", + "type": "object", + "properties": { + "title": { + "type": "string", + "description": "Task title" + }, + "description": { + "type": "string", + "description": "Task description" + }, + "status": { + "type": "string", + "enum": ["pending", "in_progress", "completed"], + "default": "pending" + }, + "priority": { + "type": "string", + "enum": ["low", "medium", "high"], + "default": "medium" + }, + "due_date": { + "type": "string", + "format": "date" + } + }, + "required": ["title"] +} diff --git a/evals/fixtures/react-task-manager/project/index.html b/evals/fixtures/react-task-manager/project/index.html new file mode 100644 index 0000000..80927a8 --- /dev/null +++ b/evals/fixtures/react-task-manager/project/index.html @@ -0,0 +1,12 @@ + + + + + + Task Manager + + +
+ + + diff --git a/evals/fixtures/react-task-manager/project/package.json b/evals/fixtures/react-task-manager/project/package.json new file mode 100644 index 0000000..f4cedd3 --- /dev/null +++ b/evals/fixtures/react-task-manager/project/package.json @@ -0,0 +1,30 @@ +{ + "name": "task-manager", + "version": "0.1.0", + "private": true, + "type": "module", + "scripts": { + "dev": "vite", + "build": "tsc && vite build", + "lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0", + "preview": "vite preview" + }, + "dependencies": { + "@base44/sdk": "^1.0.0", + "react": "^18.2.0", + "react-dom": "^18.2.0" + }, + "devDependencies": { + "@types/react": "^18.2.66", + "@types/react-dom": "^18.2.22", + "@typescript-eslint/eslint-plugin": "^7.2.0", + "@typescript-eslint/parser": "^7.2.0", + "@vitejs/plugin-react": "^4.2.1", + "base44": "^1.0.0", + "eslint": "^8.57.0", + "eslint-plugin-react-hooks": "^4.6.0", + "eslint-plugin-react-refresh": "^0.4.6", + "typescript": "^5.2.2", + "vite": "^5.2.0" + } +} diff --git a/evals/fixtures/react-task-manager/project/src/App.tsx b/evals/fixtures/react-task-manager/project/src/App.tsx new file mode 100644 index 0000000..0dbffa6 --- /dev/null +++ b/evals/fixtures/react-task-manager/project/src/App.tsx @@ -0,0 +1,12 @@ +import { TaskList } from './components/TaskList'; + +function App() { + return ( +
+

Task Manager

+ +
+ ); +} + +export default App; diff --git a/evals/fixtures/react-task-manager/project/src/components/TaskList.tsx b/evals/fixtures/react-task-manager/project/src/components/TaskList.tsx new file mode 100644 index 0000000..e62769e --- /dev/null +++ b/evals/fixtures/react-task-manager/project/src/components/TaskList.tsx @@ -0,0 +1,9 @@ +// TODO: Implement task list component that fetches and displays tasks +export function TaskList() { + return ( +
+

Tasks

+

Task list will be displayed here

+
+ ); +} diff --git a/evals/fixtures/react-task-manager/project/src/lib/base44.ts b/evals/fixtures/react-task-manager/project/src/lib/base44.ts new file mode 100644 index 0000000..9606ca2 --- /dev/null +++ b/evals/fixtures/react-task-manager/project/src/lib/base44.ts @@ -0,0 +1,5 @@ +import { createClient } from '@base44/sdk'; + +export const base44 = createClient({ + appId: import.meta.env.VITE_BASE44_APP_ID, +}); diff --git a/evals/fixtures/react-task-manager/project/src/main.tsx b/evals/fixtures/react-task-manager/project/src/main.tsx new file mode 100644 index 0000000..e5775c0 --- /dev/null +++ b/evals/fixtures/react-task-manager/project/src/main.tsx @@ -0,0 +1,9 @@ +import React from 'react'; +import ReactDOM from 'react-dom/client'; +import App from './App.tsx'; + +ReactDOM.createRoot(document.getElementById('root')!).render( + + + , +); diff --git a/evals/fixtures/react-task-manager/project/tsconfig.json b/evals/fixtures/react-task-manager/project/tsconfig.json new file mode 100644 index 0000000..3934b8f --- /dev/null +++ b/evals/fixtures/react-task-manager/project/tsconfig.json @@ -0,0 +1,21 @@ +{ + "compilerOptions": { + "target": "ES2020", + "useDefineForClassFields": true, + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "module": "ESNext", + "skipLibCheck": true, + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "react-jsx", + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true + }, + "include": ["src"], + "references": [{ "path": "./tsconfig.node.json" }] +} diff --git a/evals/fixtures/react-task-manager/project/tsconfig.node.json b/evals/fixtures/react-task-manager/project/tsconfig.node.json new file mode 100644 index 0000000..97ede7e --- /dev/null +++ b/evals/fixtures/react-task-manager/project/tsconfig.node.json @@ -0,0 +1,11 @@ +{ + "compilerOptions": { + "composite": true, + "skipLibCheck": true, + "module": "ESNext", + "moduleResolution": "bundler", + "allowSyntheticDefaultImports": true, + "strict": true + }, + "include": ["vite.config.ts"] +} diff --git a/evals/fixtures/react-task-manager/project/vite.config.ts b/evals/fixtures/react-task-manager/project/vite.config.ts new file mode 100644 index 0000000..0466183 --- /dev/null +++ b/evals/fixtures/react-task-manager/project/vite.config.ts @@ -0,0 +1,6 @@ +import { defineConfig } from 'vite'; +import react from '@vitejs/plugin-react'; + +export default defineConfig({ + plugins: [react()], +}); diff --git a/evals/fixtures/react-task-manager/skills/base44-cli/SKILL.md b/evals/fixtures/react-task-manager/skills/base44-cli/SKILL.md new file mode 100644 index 0000000..3c33f05 --- /dev/null +++ b/evals/fixtures/react-task-manager/skills/base44-cli/SKILL.md @@ -0,0 +1,384 @@ +--- +name: base44-cli +description: "The base44 CLI is used for EVERYTHING related to base44 projects: resource configuration (entities, backend functions, ai agents), initialization and actions (resource creation, deployment). This skill is the place for learning about how to configure resources. When you plan or implement a feature, you must learn this skill" +--- + +# Base44 CLI + +Create and manage Base44 apps (projects) using the Base44 CLI tool. + +## ⚡ IMMEDIATE ACTION REQUIRED - Read This First + +This skill activates on ANY mention of "base44" or when a `base44/` folder exists. **DO NOT read documentation files or search the web before acting.** + +**Your first action MUST be:** +1. Check if `base44/config.jsonc` exists in the current directory +2. If **NO** (new project scenario): + - This skill (base44-cli) handles the request + - Guide user through project initialization + - Do NOT activate base44-sdk yet +3. If **YES** (existing project scenario): + - Transfer to base44-sdk skill for implementation + - This skill only handles CLI commands (login, deploy, entities push) + +## Critical: Local Installation Only + +NEVER call `base44` directly. The CLI is installed locally as a dev dependency and must be accessed via a package manager: + +- `npx base44 ` (npm - recommended) +- `yarn base44 ` (yarn) +- `pnpm base44 ` (pnpm) + +WRONG: `base44 login` +RIGHT: `npx base44 login` + +## MANDATORY: Authentication Check at Session Start + +**CRITICAL**: At the very start of every AI session when this skill is activated, you MUST: + +1. **Check authentication status** by running: + ```bash + npx base44 whoami + ``` + +2. **If the user is logged in** (command succeeds and shows an email): + - Continue with the requested task + +3. **If the user is NOT logged in** (command fails or shows an error): + - **STOP immediately** + - **DO NOT proceed** with any CLI operations + - **Ask the user to login manually** by running: + ```bash + npx base44 login + ``` + - Wait for the user to confirm they have logged in before continuing + +**This check is mandatory and must happen before executing any other Base44 CLI commands.** + +## Overview + +The Base44 CLI provides command-line tools for authentication, creating projects, managing entities, and deploying Base44 applications. It is framework-agnostic and works with popular frontend frameworks like Vite, Next.js, and Create React App, Svelte, Vue, and more. + +## When to Use This Skill vs base44-sdk + +**Use base44-cli when:** +- Creating a **NEW** Base44 project from scratch +- Initializing a project in an empty directory +- Directory is missing `base44/config.jsonc` +- User mentions: "create a new project", "initialize project", "setup a project", "start a new Base44 app" +- Deploying, pushing entities, or authenticating via CLI +- Working with CLI commands (`npx base44 ...`) + +**Use base44-sdk when:** +- Building features in an **EXISTING** Base44 project +- `base44/config.jsonc` already exists +- Writing JavaScript/TypeScript code using Base44 SDK +- Implementing functionality, components, or features +- User mentions: "implement", "build a feature", "add functionality", "write code" + +**Skill Dependencies:** +- `base44-cli` is a **prerequisite** for `base44-sdk` in new projects +- If user wants to "create an app" and no Base44 project exists, use `base44-cli` first +- `base44-sdk` assumes a Base44 project is already initialized + +**State Check Logic:** +Before selecting a skill, check: +- IF (user mentions "create/build app" OR "make a project"): + - IF (directory is empty OR no `base44/config.jsonc` exists): + → Use **base44-cli** (project initialization needed) + - ELSE: + → Use **base44-sdk** (project exists, build features) + +## Project Structure + +A Base44 project combines a standard frontend project with a `base44/` configuration folder: + +``` +my-app/ +├── base44/ # Base44 configuration (created by CLI) +│ ├── config.jsonc # Project settings, site config +│ ├── entities/ # Entity schema definitions +│ │ ├── task.jsonc +│ │ └── board.jsonc +│ ├── functions/ # Backend functions (optional) +│ │ └── my-function/ +│ │ ├── function.jsonc +│ │ └── index.ts +│ └── agents/ # Agent configurations (optional) +│ └── support_agent.jsonc +├── src/ # Frontend source code +│ ├── api/ +│ │ └── base44Client.js # Base44 SDK client +│ ├── pages/ +│ ├── components/ +│ └── main.jsx +├── index.html # SPA entry point +├── package.json +└── vite.config.js # Or your framework's config +``` + +**Key files:** +- `base44/config.jsonc` - Project name, description, site build settings +- `base44/entities/*.jsonc` - Data model schemas (see Entity Schema section) +- `base44/agents/*.jsonc` - Agent configurations (optional) +- `src/api/base44Client.js` - Pre-configured SDK client for frontend use + +**config.jsonc example:** +```jsonc +{ + "name": "My App", // Required: project name + "description": "App description", // Optional: project description + "entitiesDir": "./entities", // Optional: default "entities" + "functionsDir": "./functions", // Optional: default "functions" + "agentsDir": "./agents", // Optional: default "agents" + "site": { // Optional: site deployment config + "installCommand": "npm install", // Optional: install dependencies + "buildCommand": "npm run build", // Optional: build command + "serveCommand": "npm run dev", // Optional: local dev server + "outputDirectory": "./dist" // Optional: build output directory + } +} +``` + +**Config properties:** + +| Property | Description | Default | +|----------|-------------|---------| +| `name` | Project name (required) | - | +| `description` | Project description | - | +| `entitiesDir` | Directory for entity schemas | `"entities"` | +| `functionsDir` | Directory for backend functions | `"functions"` | +| `agentsDir` | Directory for agent configs | `"agents"` | +| `site.installCommand` | Command to install dependencies | - | +| `site.buildCommand` | Command to build the project | - | +| `site.serveCommand` | Command to run dev server | - | +| `site.outputDirectory` | Build output directory for deployment | - | + +## Installation + +Install the Base44 CLI as a dev dependency in your project: + +```bash +npm install --save-dev base44 +``` + +**Important:** Never assume or hardcode the `base44` package version. Always install without a version specifier to get the latest version. + +Then run commands using `npx`: + +```bash +npx base44 +``` + +**Note:** All commands in this documentation use `npx base44`. You can also use `yarn base44`, or `pnpm base44` if preferred. + +## Available Commands + +### Authentication + +| Command | Description | Reference | +| --------------- | ----------------------------------------------- | ------------------------------------------- | +| `base44 login` | Authenticate with Base44 using device code flow | [auth-login.md](references/auth-login.md) | +| `base44 logout` | Logout from current device | [auth-logout.md](references/auth-logout.md) | +| `base44 whoami` | Display current authenticated user | [auth-whoami.md](references/auth-whoami.md) | + +### Project Management + +| Command | Description | Reference | +|---------|-------------|-----------| +| `base44 create` | Create a new Base44 project from a template | [create.md](references/create.md) ⚠️ **MUST READ** | +| `base44 link` | Link an existing local project to Base44 | [link.md](references/link.md) | +| `base44 dashboard` | Open the app dashboard in your browser | [dashboard.md](references/dashboard.md) | + +### Deployment + +| Command | Description | Reference | +|---------|-------------|-----------| +| `base44 deploy` | Deploy all resources (entities, functions, site) | [deploy.md](references/deploy.md) | + +### Entity Management + +| Action / Command | Description | Reference | +| ---------------------- | ------------------------------------------- | --------------------------------------------------- | +| Create Entities | Define entities in `base44/entities` folder | [entities-create.md](references/entities-create.md) | +| `base44 entities push` | Push local entities to Base44 | [entities-push.md](references/entities-push.md) | + +#### Entity Schema (Quick Reference) + +ALWAYS follow this exact structure when creating entity files: + +**File naming:** `base44/entities/{kebab-case-name}.jsonc` (e.g., `team-member.jsonc` for `TeamMember`) + +**Schema template:** +```jsonc +{ + "name": "EntityName", + "type": "object", + "properties": { + "field_name": { + "type": "string", + "description": "Field description" + } + }, + "required": ["field_name"] +} +``` + +**Field types:** `string`, `number`, `integer`, `boolean`, `array`, `object`, `binary` +**String formats:** `date`, `date-time`, `time`, `email`, `uri`, `hostname`, `ipv4`, `ipv6`, `uuid`, `file`, `regex`, `richtext` +**For enums:** Add `"enum": ["value1", "value2"]` and optionally `"default": "value1"` +**Entity names:** Must be alphanumeric only (pattern: `/^[a-zA-Z0-9]+$/`) + +For complete documentation, see [entities-create.md](references/entities-create.md). + +### Function Management + +| Action / Command | Description | Reference | +| ------------------------- | --------------------------------------------- | ------------------------------------------------------- | +| Create Functions | Define functions in `base44/functions` folder | [functions-create.md](references/functions-create.md) | +| `base44 functions deploy` | Deploy local functions to Base44 | [functions-deploy.md](references/functions-deploy.md) | + +### Agent Management + +Agents are conversational AI assistants that can interact with users, access your app's entities, and call backend functions. Use these commands to manage agent configurations. + +| Action / Command | Description | Reference | +| ----------------------- | --------------------------------------- | ----------------------------------------------- | +| Create Agents | Define agents in `base44/agents` folder | See Agent Schema below | +| `base44 agents pull` | Pull remote agents to local files | [agents-pull.md](references/agents-pull.md) | +| `base44 agents push` | Push local agents to Base44 | [agents-push.md](references/agents-push.md) | + +**Note:** Agent commands perform full synchronization - pushing replaces all remote agents with local ones, and pulling replaces all local agents with remote ones. + +#### Agent Schema (Quick Reference) + +**File naming:** `base44/agents/{agent_name}.jsonc` (e.g., `support_agent.jsonc`) + +**Schema template:** +```jsonc +{ + "name": "agent_name", + "description": "Brief description of what this agent does", + "instructions": "Detailed instructions for the agent's behavior", + "tool_configs": [ + // Entity tool - gives agent access to entity operations + { "entity_name": "tasks", "allowed_operations": ["read", "create", "update", "delete"] }, + // Backend function tool - gives agent access to a function + { "function_name": "send_email", "description": "Send an email notification" } + ], + "whatsapp_greeting": "Hello! How can I help you today?" +} +``` + +**Naming rules:** +- Agent names must match pattern: `/^[a-z0-9_]+$/` (lowercase alphanumeric with underscores, 1-100 chars) +- Valid: `support_agent`, `order_bot` +- Invalid: `Support-Agent`, `OrderBot` + +**Required fields:** `name`, `description`, `instructions` +**Optional fields:** `tool_configs` (defaults to `[]`), `whatsapp_greeting` + +**Tool config types:** +- **Entity tools**: `entity_name` + `allowed_operations` (array of: `read`, `create`, `update`, `delete`) +- **Backend function tools**: `function_name` + `description` + +### Site Deployment + +| Command | Description | Reference | +| -------------------- | ----------------------------------------- | ------------------------------------------- | +| `base44 site deploy` | Deploy built site files to Base44 hosting | [site-deploy.md](references/site-deploy.md) | + +**SPA only**: Base44 hosting supports Single Page Applications with a single `index.html` entry point. All routes are served from `index.html` (client-side routing). + +## Quick Start + +1. Install the CLI in your project: + ```bash + npm install --save-dev base44 + ``` + +2. Authenticate with Base44: + ```bash + npx base44 login + ``` + +3. Create a new project (ALWAYS provide name and `--path` flag): + ```bash + npx base44 create my-app -p . + ``` + +4. Build and deploy everything: + ```bash + npm run build + npx base44 deploy -y + ``` + +Or deploy individual resources: +- `npx base44 entities push` - Push entities only +- `npx base44 functions deploy` - Deploy functions only +- `npx base44 agents push` - Push agents only +- `npx base44 site deploy -y` - Deploy site only + +## Common Workflows + +### Creating a New Project + +**⚠️ MANDATORY: Before running `base44 create`, you MUST read [create.md](references/create.md) for:** +- **Template selection** - Choose the correct template (`backend-and-client` vs `backend-only`) +- **Correct workflow** - Different templates require different setup steps +- **Common pitfalls** - Avoid folder creation errors that cause failures + +Failure to follow the create.md instructions will result in broken project scaffolding. + +### Linking an Existing Project +```bash +# If you have base44/config.jsonc but no .app.jsonc +npx base44 link --create --name my-app +``` + +### Deploying All Changes +```bash +# Build your project first +npm run build + +# Deploy everything (entities, functions, and site) +npx base44 deploy -y +``` + +### Deploying Individual Resources +```bash +# Push only entities +npx base44 entities push + +# Deploy only functions +npx base44 functions deploy + +# Push only agents +npx base44 agents push + +# Deploy only site +npx base44 site deploy -y +``` + +### Opening the Dashboard +```bash +# Open app dashboard in browser +npx base44 dashboard +``` + +## Authentication + +Most commands require authentication. If you're not logged in, the CLI will automatically prompt you to login. Your session is stored locally and persists across CLI sessions. + +## Troubleshooting + +| Error | Solution | +| --------------------------- | ----------------------------------------------------------------------------------- | +| Not authenticated | Run `npx base44 login` first | +| No entities found | Ensure entities exist in `base44/entities/` directory | +| Entity not recognized | Ensure file uses kebab-case naming (e.g., `team-member.jsonc` not `TeamMember.jsonc`) | +| No functions found | Ensure functions exist in `base44/functions/` with valid `function.jsonc` configs | +| No agents found | Ensure agents exist in `base44/agents/` directory with valid `.jsonc` configs | +| Invalid agent name | Agent names must be lowercase alphanumeric with underscores only | +| No site configuration found | Check that `site.outputDirectory` is configured in project config | +| Site deployment fails | Ensure you ran `npm run build` first and the build succeeded | diff --git a/evals/fixtures/react-task-manager/skills/base44-cli/references/agents-pull.md b/evals/fixtures/react-task-manager/skills/base44-cli/references/agents-pull.md new file mode 100644 index 0000000..6a700f0 --- /dev/null +++ b/evals/fixtures/react-task-manager/skills/base44-cli/references/agents-pull.md @@ -0,0 +1,73 @@ +# base44 agents pull + +Pull AI agent configurations from Base44 to local files. Agents are conversational AI assistants that can interact with users, access your app's entities, and call backend functions. + +## Syntax + +```bash +npx base44 agents pull +``` + +## Authentication + +**Required**: Yes. If not authenticated, you'll be prompted to login first. + +## What It Does + +1. Fetches all agents from Base44 +2. Writes agent files to the `base44/agents/` directory +3. Deletes local agent files that don't exist remotely +4. Reports written and deleted agents + +## Prerequisites + +- Must be run from a Base44 project directory +- Project must be linked to a Base44 app + +## Output + +```bash +$ npx base44 agents pull + +Fetching agents from Base44... +✓ Agents fetched successfully + +Writing agent files... +✓ Agent files written successfully + +Written: support_agent, order_bot +Deleted: old_agent + +Pulled 2 agents to base44/agents +``` + +## Agent Synchronization + +The pull operation synchronizes remote agents to your local files: + +- **Written**: Agent files created or updated from remote +- **Deleted**: Local agent files removed (didn't exist remotely) + +**Warning**: This operation replaces all local agent configurations with remote versions. Any local changes not pushed to Base44 will be overwritten. + +## Error Handling + +If no agents exist on Base44: +```bash +$ npx base44 agents pull +No agents found on Base44 +``` + +## Use Cases + +- Sync agent configurations to a new development machine +- Get the latest agent configurations from your team +- Restore local agent files after accidental deletion +- Start working on an existing project with agents + +## Notes + +- This command syncs agent configurations, not conversation data +- Agent files are stored as `.jsonc` in the `base44/agents/` directory +- The directory location is configurable via `agentsDir` in `config.jsonc` +- Use `base44 agents push` to upload local changes to Base44 diff --git a/evals/fixtures/react-task-manager/skills/base44-cli/references/agents-push.md b/evals/fixtures/react-task-manager/skills/base44-cli/references/agents-push.md new file mode 100644 index 0000000..fa2795b --- /dev/null +++ b/evals/fixtures/react-task-manager/skills/base44-cli/references/agents-push.md @@ -0,0 +1,156 @@ +# base44 agents push + +Push local AI agent configurations to Base44. Agents are conversational AI assistants that can interact with users, access your app's entities, and call backend functions. + +## Syntax + +```bash +npx base44 agents push +``` + +## Authentication + +**Required**: Yes. If not authenticated, you'll be prompted to login first. + +## What It Does + +1. Reads all agent files from the `base44/agents/` directory +2. Validates agent configurations +3. Displays the count of agents to be pushed +4. Uploads agents to the Base44 backend +5. Reports the results: created, updated, and deleted agents + +## Prerequisites + +- Must be run from a Base44 project directory +- Project must have agent definitions in the `base44/agents/` folder + +## Output + +```bash +$ npx base44 agents push + +Found 2 agents to push +Pushing agents to Base44... + +Created: support_agent +Updated: order_bot +Deleted: old_agent + +✓ Agents pushed to Base44 +``` + +## Agent Synchronization + +The push operation synchronizes your local agents with Base44: + +- **Created**: New agents that didn't exist in Base44 +- **Updated**: Existing agents with modified configuration +- **Deleted**: Agents that were removed from your local configuration + +**Warning**: This is a full sync operation. Agents removed locally will be deleted from Base44. + +## Error Handling + +If no agents are found in your project: +```bash +$ npx base44 agents push +No local agents found - this will delete all remote agents +``` + +If an agent has an invalid name: +```bash +$ npx base44 agents push +Error: Agent name must be lowercase alphanumeric with underscores +``` + +## Agent Configuration Schema + +Each agent file should be a `.jsonc` file in `base44/agents/` with this structure: + +```jsonc +{ + "name": "agent_name", // Required: lowercase alphanumeric with underscores, 1-100 chars + "description": "Brief description of what this agent does", // Required: min 1 char + "instructions": "Detailed instructions for the agent's behavior", // Required: min 1 char + "tool_configs": [ // Optional: defaults to [] + // Entity tool - gives agent access to entity operations + { "entity_name": "Task", "allowed_operations": ["read", "create", "update", "delete"] }, + // Backend function tool - gives agent access to a function + { "function_name": "send_email", "description": "Send an email notification" } + ], + "whatsapp_greeting": "Hello! How can I help you today?" // Optional +} +``` + +**Naming rules:** +- **Agent names** must match pattern: `/^[a-z0-9_]+$/` (lowercase alphanumeric with underscores only, 1-100 characters) + - Valid: `support_agent`, `order_bot`, `task_helper` + - Invalid: `Support-Agent`, `OrderBot`, `task helper` +- **Agent file names** must use underscores (matching the agent name) + - Valid: `support_agent.jsonc`, `order_bot.jsonc` + - Invalid: `support-agent.jsonc` (hyphens not allowed) +- **Entity names in `tool_configs`** must use PascalCase (matching the entity's `name` field) + - Valid: `"entity_name": "Task"`, `"entity_name": "TeamMember"` + - Invalid: `"entity_name": "task"`, `"entity_name": "team_member"` + +**Required fields:** +- `name`: Required, must follow naming rules above +- `description`: Required, minimum 1 character +- `instructions`: Required, minimum 1 character +- `tool_configs`: Optional, defaults to empty array +- `whatsapp_greeting`: Optional + +### Common Mistake: Wrong tool_configs Format + +**WRONG** - Do NOT use `tools` with `type` and `entity`: +```jsonc +{ + "name": "my_agent", + "tools": [ // ❌ WRONG + { "type": "entity_query", "entity": "Task" } + ] +} +``` + +**CORRECT** - Use `tool_configs` with `entity_name` and `allowed_operations`: +```jsonc +{ + "name": "my_agent", + "tool_configs": [ // ✅ CORRECT + { "entity_name": "Task", "allowed_operations": ["read"] } + ] +} +``` + +### Best Practices for Agent Instructions + +When giving agents access to entities, be explicit in the instructions about using the tools: + +```jsonc +{ + "name": "support_agent", + "instructions": "You are a helpful support agent.\n\nIMPORTANT: You have access to customer data through entity tools. When users ask about their orders or account:\n1. ALWAYS use the Order entity tool to query their order history\n2. Use the Customer entity tool to look up account details\n3. Analyze the data and provide personalized responses\n\nAlways query the relevant entities first before answering questions about user data.", + "tool_configs": [ + { "entity_name": "Order", "allowed_operations": ["read"] }, + { "entity_name": "Customer", "allowed_operations": ["read"] } + ] +} +``` + +Without explicit instructions to use the entity tools, the agent may not proactively query user data when asked. + +## Use Cases + +- After defining new agents in your project +- When modifying existing agent configurations +- To sync agent changes before testing +- As part of your development workflow when agent behavior changes + +## Notes + +- This command syncs the agent configuration, not conversation data +- Changes are applied to your Base44 project immediately +- Make sure to test agent changes in a development environment first +- Agent definitions are located in the `base44/agents/` directory +- Use `base44 agents pull` to download agents from Base44 diff --git a/evals/fixtures/react-task-manager/skills/base44-cli/references/auth-login.md b/evals/fixtures/react-task-manager/skills/base44-cli/references/auth-login.md new file mode 100644 index 0000000..adebd27 --- /dev/null +++ b/evals/fixtures/react-task-manager/skills/base44-cli/references/auth-login.md @@ -0,0 +1,54 @@ +# base44 login + +Authenticate with Base44 using device code flow. + +## Syntax + +```bash +npx base44 login +``` + +## Authentication + +**Required**: No (this is the login command itself) + +## How It Works + +The login command uses OAuth 2.0 device code flow for authentication: + +1. Generates a device code for authentication +2. Displays a verification code and verification URI +3. Directs you to visit the URI and enter the code +4. Polls for authentication completion (up to device code expiration) +5. Retrieves access and refresh tokens upon successful authentication +6. Fetches and displays your user information +7. Saves authentication data locally with expiration timestamp + +## Interactive Flow + +```bash +$ npx base44 login + +Please visit: https://auth.base44.com/device +Enter code: ABCD-EFGH + +Waiting for authentication... +✓ Successfully authenticated! + +Logged in as: user@example.com +``` + +## Session Management + +- Authentication tokens are stored locally on your device +- Tokens include expiration timestamps +- The session persists across CLI sessions +- Other commands will automatically use your stored credentials +- Use `npx base44 logout` to clear your session +- Use `npx base44 whoami` to check your current authentication status + +## Notes + +- You only need to login once per device +- If your session expires, you'll be prompted to login again when running authenticated commands +- The CLI automatically prompts for login when you run commands that require authentication diff --git a/evals/fixtures/react-task-manager/skills/base44-cli/references/auth-logout.md b/evals/fixtures/react-task-manager/skills/base44-cli/references/auth-logout.md new file mode 100644 index 0000000..33c2ddf --- /dev/null +++ b/evals/fixtures/react-task-manager/skills/base44-cli/references/auth-logout.md @@ -0,0 +1,32 @@ +# base44 logout + +Logout from current device and clear stored authentication data. + +## Syntax + +```bash +npx base44 logout +``` + +## Authentication + +**Required**: No + +## What It Does + +- Deletes stored authentication data from your device +- Clears your local session +- Removes access and refresh tokens + +## Output + +```bash +$ npx base44 logout +Logged out successfully +``` + +## Notes + +- You can logout even if you're not currently logged in (no error) +- After logout, you'll need to run `npx base44 login` again to use authenticated commands +- This only affects the current device; your Base44 account remains active diff --git a/evals/fixtures/react-task-manager/skills/base44-cli/references/auth-whoami.md b/evals/fixtures/react-task-manager/skills/base44-cli/references/auth-whoami.md new file mode 100644 index 0000000..abe450c --- /dev/null +++ b/evals/fixtures/react-task-manager/skills/base44-cli/references/auth-whoami.md @@ -0,0 +1,37 @@ +# base44 whoami + +Display the currently authenticated user. + +## Syntax + +```bash +npx base44 whoami +``` + +## Authentication + +**Required**: Yes. If not authenticated, you'll be prompted to login first. + +## What It Does + +- Reads stored authentication data +- Displays the email of the currently logged-in user + +## Output + +```bash +$ npx base44 whoami +Logged in as: user@example.com +``` + +## Use Cases + +- Verify you're logged in before running other commands +- Check which account you're currently using +- Confirm authentication is working properly +- Useful in scripts or automation to verify credentials + +## Notes + +- If you're not logged in, the command will prompt you to authenticate first +- The email displayed matches your Base44 account email diff --git a/evals/fixtures/react-task-manager/skills/base44-cli/references/create.md b/evals/fixtures/react-task-manager/skills/base44-cli/references/create.md new file mode 100644 index 0000000..7580645 --- /dev/null +++ b/evals/fixtures/react-task-manager/skills/base44-cli/references/create.md @@ -0,0 +1,111 @@ +# base44 create + +Creates a new Base44 project from a template. This command is framework-agnostic and can either scaffold a complete project or add Base44 configuration to an existing project. + +## Critical: Non-Interactive Mode Required + +ALWAYS provide both the project name AND `--path` flag. Without both, the command opens an interactive TUI which agents cannot use properly. + +WRONG: `npx base44 create` +WRONG: `npx base44 create my-app` +RIGHT: `npx base44 create my-app -p ./my-app` + +## Syntax + +```bash +npx base44 create [name] --path [options] +``` + +## Arguments & Options + +| Argument/Option | Description | Required | +|--------|-------------|----------| +| `name` | Project name (positional argument) | Yes* | +| `-p, --path ` | Path where to create the project | Yes* | +| `-t, --template ` | Template ID (see templates below) | No | +| `--deploy` | Build and deploy the site (includes pushing entities) | No | +| `--no-skills` | Skip AI agent skills installation (skills are added by default) | No | + +*Required for non-interactive mode. Both `name` and `--path` must be provided together. + +## Template Selection (CRITICAL - Choose Appropriately) + +**You MUST select the most appropriate template based on user requirements:** + +| Template ID | When to Use | Example Scenarios | +|-------------|-------------|-------------------| +| `backend-and-client` | Creating a NEW full-stack web app from scratch | "Create a task app", "Build me a dashboard", "Make a SaaS app" | +| `backend-only` | Adding Base44 to an EXISTING project OR using a different framework (Next.js, Vue, Svelte, etc.) | "Add Base44 to my project", "I want to use Next.js", "I already have a frontend" | + +**Default Choice:** When the user asks to "create an app" or "build a project" without specifying a particular framework, use `backend-and-client` to provide a complete, production-ready application with Vite + React + Tailwind. + +## The `--path` Flag + +- **For `backend-and-client` template (new projects):** Use a new subfolder path + ```bash + npx base44 create my-app -p ./my-app -t backend-and-client + ``` +- **For `backend-only` template (existing projects):** Use `-p .` in the current directory + ```bash + npx base44 create my-app -p . + ``` + +## Workflow: Using `backend-only` with External Frameworks + +**CRITICAL: The project folder MUST exist BEFORE running `base44 create` with `backend-only`** + +The `backend-only` template only adds Base44 configuration files - it does NOT create a frontend. If you need a frontend with a specific framework: + +```bash +# Step 1: Initialize the frontend project FIRST +npm create vite@latest my-app -- --template react # or vue, svelte, etc. +# OR: npx create-next-app@latest my-app +# OR: any other framework's init command + +# Step 2: Navigate into the created folder +cd my-app + +# Step 3: Install Base44 CLI +npm install --save-dev base44 + +# Step 4: Add Base44 configuration +npx base44 create my-app -p . +``` + +**WARNING:** Do NOT: +- Create an empty folder manually, then try to run `npx create vite` inside it (will fail - folder exists) +- Run `base44 create` with `backend-only` expecting it to create a frontend (it won't) + +**DO:** +- Run the external framework's init command FIRST (it creates its own folder) +- Then run `base44 create` inside that folder with `-p .` + +## Examples + +```bash +# RECOMMENDED: Create full-stack project (for new apps) +npx base44 create my-app -p ./my-app -t backend-and-client + +# Create full-stack and deploy in one step +npx base44 create my-app -p ./my-app -t backend-and-client --deploy + +# Add Base44 to EXISTING project (must be inside the project folder) +npx base44 create my-app -p . + +# Add Base44 to existing project and deploy +npx base44 create my-app -p . --deploy + +# Create without adding AI agent skills +npx base44 create my-app -p . --no-skills +``` + +## What It Does + +1. Applies the selected template to the target path +2. Creates a `base44/` folder with configuration files +3. Registers the project with Base44 backend +4. Creates `base44/.app.jsonc` with the app ID +5. If `--deploy` is used: + - Pushes any entities defined in `base44/entities/` + - Runs install and build commands (for templates with frontend) + - Deploys the site to Base44 hosting diff --git a/evals/fixtures/react-task-manager/skills/base44-cli/references/dashboard.md b/evals/fixtures/react-task-manager/skills/base44-cli/references/dashboard.md new file mode 100644 index 0000000..95a534a --- /dev/null +++ b/evals/fixtures/react-task-manager/skills/base44-cli/references/dashboard.md @@ -0,0 +1,35 @@ +# base44 dashboard + +Opens the Base44 app dashboard in your default web browser. + +## Syntax + +```bash +npx base44 dashboard +``` + +## Options + +This command has no options. + +## What It Does + +1. Reads the project's app ID from `base44/.app.jsonc` +2. Opens the dashboard URL in your default browser + +## Example + +```bash +# Open dashboard for current project +npx base44 dashboard +``` + +## Requirements + +- Must be run from a linked Base44 project directory (contains `base44/.app.jsonc`) +- Must be authenticated (run `npx base44 login` first) + +## Notes + +- The dashboard provides a web interface to manage your app's entities, functions, users, and settings +- If you're not in a project directory, the command will fail with an error diff --git a/evals/fixtures/react-task-manager/skills/base44-cli/references/deploy.md b/evals/fixtures/react-task-manager/skills/base44-cli/references/deploy.md new file mode 100644 index 0000000..500526f --- /dev/null +++ b/evals/fixtures/react-task-manager/skills/base44-cli/references/deploy.md @@ -0,0 +1,86 @@ +# base44 deploy + +Deploys all project resources (entities, functions, and site) to Base44 in a single command. + +## Syntax + +```bash +npx base44 deploy [options] +``` + +## Options + +| Option | Description | +|--------|-------------| +| `-y, --yes` | Skip confirmation prompt | + +## What It Deploys + +The command automatically detects and deploys: + +1. **Entities** - All `.jsonc` files in `base44/entities/` +2. **Functions** - All functions in `base44/functions/` +3. **Agents** - All agent configurations in `base44/agents/` +4. **Site** - Built files from `site.outputDirectory` (if configured) + +## Examples + +```bash +# Interactive mode - shows what will be deployed and asks for confirmation +npx base44 deploy + +# Non-interactive - skip confirmation (for CI/CD or agent use) +npx base44 deploy -y +``` + +## Typical Workflow + +```bash +# 1. Make your changes (entities, functions, frontend code) + +# 2. Build the frontend (if you have one) +npm run build + +# 3. Deploy everything +npx base44 deploy -y +``` + +## What It Does + +1. Reads project configuration from `base44/config.jsonc` +2. Detects available resources (entities, functions, site) +3. Shows a summary of what will be deployed +4. Asks for confirmation (unless `-y` flag is used) +5. Deploys all resources in sequence: + - Pushes entity schemas + - Deploys functions + - Pushes agent configurations + - Uploads site files +6. Displays the dashboard URL and app URL (if site was deployed) + +## Requirements + +- Must be run from a linked Base44 project directory +- Must be authenticated (run `npx base44 login` first) +- For site deployment, must run `npm run build` first + +## Output + +After successful deployment: +- **Dashboard**: Link to your app's management dashboard +- **App URL**: Your deployed site's public URL (if site was included) + +## Notes + +- If no resources are found, the command exits with a message +- Use individual commands (`entities push`, `functions deploy`, `site deploy`) if you only want to deploy specific resources +- The site must be built before deployment - this command does not run `npm run build` for you + +## Related Commands + +| Command | Description | +|---------|-------------| +| `base44 entities push` | Push only entities | +| `base44 functions deploy` | Deploy only functions | +| `base44 agents push` | Push only agents | +| `base44 site deploy` | Deploy only the site | diff --git a/evals/fixtures/react-task-manager/skills/base44-cli/references/entities-create.md b/evals/fixtures/react-task-manager/skills/base44-cli/references/entities-create.md new file mode 100644 index 0000000..29b40aa --- /dev/null +++ b/evals/fixtures/react-task-manager/skills/base44-cli/references/entities-create.md @@ -0,0 +1,552 @@ +# Creating Entities + +Base44 entities are defined locally in your project and then pushed to the Base44 backend. + +## Critical: File Naming + +Entity files MUST use kebab-case naming: `{kebab-case-name}.jsonc` + +| Entity Name | File Name | +|-------------|-----------| +| `Task` | `task.jsonc` | +| `TeamMember` | `team-member.jsonc` | +| `ActivityLog` | `activity-log.jsonc` | + +WRONG: `TeamMember.jsonc`, `teamMember.jsonc` +RIGHT: `team-member.jsonc` + +## Table of Contents + +- [Creating Entities](#creating-entities) + - [Entity Directory](#entity-directory) + - [How to Create an Entity](#how-to-create-an-entity) + - [Entity Schema Structure](#entity-schema-structure) + - [Supported Field Types](#supported-field-types) + - [Field Properties](#field-properties) + - [Complete Example](#complete-example) + - [Naming Conventions](#naming-conventions) + - [Relationships Between Entities](#relationships-between-entities) + - [Row Level Security (RLS)](#row-level-security-rls) + - [Field Level Security (FLS)](#field-level-security-fls) + - [Pushing Entities](#pushing-entities) + +## Entity Directory + +All entity definitions must be placed in the `base44/entities/` folder in your project root. Each entity is defined in its own `.jsonc` file. + +Example structure: +``` +my-app/ + base44/ + entities/ + user.jsonc + product.jsonc + order.jsonc +``` + +## How to Create an Entity + +1. Create a new `.jsonc` file in the `base44/entities/` directory +2. Define your entity schema following the structure below +3. Push the changes to Base44 using the CLI + +## Entity Schema Structure + +Each entity file follows a JSON Schema-like structure: + +```jsonc +{ + "name": "EntityName", // PascalCase entity name + "type": "object", // Always "object" + "properties": { + // Define your fields here + }, + "required": ["field1"] // Array of required field names +} +``` + +### Common Mistake: Nested Schema Property + +**WRONG** - Do NOT wrap properties in a `schema` object: +```jsonc +{ + "name": "Task", + "description": "A task entity", + "schema": { // ❌ WRONG - don't use nested "schema" + "type": "object", + "properties": { ... } + } +} +``` + +**CORRECT** - Put `type` and `properties` at the top level: +```jsonc +{ + "name": "Task", + "description": "A task entity", + "type": "object", // ✅ CORRECT - top level + "properties": { ... } // ✅ CORRECT - top level +} +``` + +This is a common mistake that will cause "Invalid schema: Schema must have a 'type' field" errors when pushing entities. + +## Supported Field Types + +### String + +Basic text field: +```jsonc +{ + "title": { + "type": "string", + "description": "Task title" + } +} +``` + +With format: +```jsonc +{ + "due_date": { + "type": "string", + "format": "date", + "description": "Due date" + } +} +``` + +Available formats: `date`, `date-time`, `time`, `email`, `uri`, `hostname`, `ipv4`, `ipv6`, `uuid`, `file`, `regex`, `richtext` + +### String with Enum + +Constrained to specific values: +```jsonc +{ + "status": { + "type": "string", + "enum": ["todo", "in_progress", "done"], + "default": "todo", + "description": "Current status" + } +} +``` + +### Number + +```jsonc +{ + "position": { + "type": "number", + "description": "Position for ordering" + } +} +``` + +### Integer + +For whole numbers only: +```jsonc +{ + "quantity": { + "type": "integer", + "description": "Item quantity", + "minimum": 0, + "maximum": 1000 + } +} +``` + +### Binary + +For file/blob data: +```jsonc +{ + "attachment": { + "type": "binary", + "description": "File attachment" + } +} +``` + +### Boolean + +```jsonc +{ + "notify_on_change": { + "type": "boolean", + "default": true, + "description": "Enable notifications" + } +} +``` + +### Array of Strings + +```jsonc +{ + "labels": { + "type": "array", + "items": { "type": "string" }, + "description": "Task labels/tags" + } +} +``` + +### Array of Objects + +```jsonc +{ + "attachments": { + "type": "array", + "description": "File attachments", + "items": { + "type": "object", + "properties": { + "name": { "type": "string" }, + "url": { "type": "string" }, + "type": { "type": "string" } + } + } + } +} +``` + +## Field Properties + +| Property | Description | +| ------------- | ---------------------------------------------------------------------------------------- | +| `type` | Data type: `string`, `number`, `integer`, `boolean`, `array`, `object`, `binary` | +| `description` | Human-readable description of the field | +| `enum` | Array of allowed values (for strings) | +| `enumNames` | Human-readable labels for enum values (same order as `enum`) | +| `default` | Default value when not provided | +| `format` | Format hint: `date`, `date-time`, `time`, `email`, `uri`, `hostname`, `ipv4`, `ipv6`, `uuid`, `file`, `regex`, `richtext` | +| `items` | Schema for array items | +| `properties` | Nested properties for object types | +| `$ref` | Reference to another schema definition | +| `minLength` | Minimum string length | +| `maxLength` | Maximum string length | +| `pattern` | Regex pattern for string validation | +| `minimum` | Minimum value for numbers | +| `maximum` | Maximum value for numbers | +| `rls` | Field-level security rules (see Field Level Security section) | + +## Complete Example + +Here's a complete entity definition for a Task: + +```jsonc +{ + "name": "Task", + "type": "object", + "properties": { + "title": { + "type": "string", + "description": "Task title" + }, + "description": { + "type": "string", + "description": "Task description" + }, + "status": { + "type": "string", + "enum": ["todo", "in_progress", "done"], + "default": "todo", + "description": "Current status of the task" + }, + "board_id": { + "type": "string", + "description": "Board this task belongs to" + }, + "assignee_email": { + "type": "string", + "description": "Email of assigned user" + }, + "priority": { + "type": "string", + "enum": ["low", "medium", "high"], + "default": "medium", + "description": "Task priority" + }, + "due_date": { + "type": "string", + "format": "date", + "description": "Due date" + }, + "labels": { + "type": "array", + "items": { "type": "string" }, + "description": "Task labels/tags" + } + }, + "required": ["title"] +} +``` + +## Naming Conventions + +- **Entity name**: Use PascalCase with alphanumeric characters only (e.g., `Task`, `TeamMember`, `ActivityLog`) + - Must match pattern: `/^[a-zA-Z0-9]+$/` + - Valid: `Task`, `TeamMember`, `Order123` + - Invalid: `Team_Member`, `Team-Member`, `Team Member` +- **File name**: Use kebab-case matching the entity (e.g., `task.jsonc`, `team-member.jsonc`, `activity-log.jsonc`) +- **Field names**: Use snake_case (e.g., `board_id`, `user_email`, `due_date`) + +## Relationships Between Entities + +To create relationships between entities, use ID reference fields: + +```jsonc +{ + "board_id": { + "type": "string", + "description": "Board this task belongs to" + }, + "team_id": { + "type": "string", + "description": "Associated team ID" + } +} +``` + +## Row Level Security (RLS) + +Row Level Security (RLS) controls which records users can access based on their identity and attributes. RLS rules are defined per entity inside the `rls` field of the schema. + +**Important:** If no RLS is defined, all records are accessible to all users. + +### RLS Operations + +RLS supports five operations: + +| Operation | Description | +|-----------|-------------| +| `create` | Control who can add new records | +| `read` | Control who can view records | +| `update` | Control who can modify records | +| `delete` | Control who can remove records | +| `write` | Shorthand for `create`, `update`, and `delete` combined | + +### Permission Values + +Each operation accepts one of the following values: + +1. **`true`** - Allow all users (including anonymous/unauthenticated) +2. **`false`** - Block all users +3. **Condition object** - Allow users matching the condition + +### Template Variables + +Use template variables to reference the current user's attributes: + +| Template | Description | +|----------|-------------| +| `{{user.id}}` | The user's ID | +| `{{user.email}}` | The user's email | +| `{{user.role}}` | The user's role | +| `{{user.data.field_name}}` | Custom field from the user's `data` object | + +### Built-in Entity Attributes + +Every entity record has these built-in attributes available for RLS rules: + +| Attribute | Description | +|-----------|-------------| +| `id` | Unique record identifier | +| `created_date` | Timestamp when record was created | +| `updated_date` | Timestamp when record was last updated | +| `created_by` | Email of the user who created the record | + +### Rule Types + +There are two condition types you can use: + +**1. Entity-to-user comparison** - Compare record fields to the current user's values: +```jsonc +{ + "created_by": "{{user.email}}" +} +``` + +**2. User condition check** - Check user properties directly using `user_condition`: +```jsonc +{ + "user_condition": { "role": "admin" } +} +``` + +**Important limitations:** +- `user_condition` only supports **simple equality** (e.g., `{ "role": "admin" }`) +- For `data.*` field comparisons, you can use operators: `$in`, `$nin`, `$ne`, `$all` +- Logical operators `$or`, `$and`, `$nor` are available for combining conditions +- You cannot filter by entity field values directly (e.g., `{"status": "published"}`) +- Only user-related conditions are allowed + +### RLS Examples + +**Owner-only access:** +```jsonc +{ + "created_by": "{{user.email}}" +} +``` + +**Department-based access:** +```jsonc +{ + "data.department": "{{user.data.department}}" +} +``` + +**Admin-only access:** +```jsonc +{ + "user_condition": { "role": "admin" } +} +``` + +**Complete RLS configuration:** +```jsonc +{ + "name": "Task", + "type": "object", + "properties": { + "title": { + "type": "string", + "description": "Task title" + }, + "status": { + "type": "string", + "enum": ["todo", "in_progress", "done"], + "default": "todo" + } + }, + "required": ["title"], + "rls": { + "create": true, + "read": { "created_by": "{{user.email}}" }, + "update": { "created_by": "{{user.email}}" }, + "delete": { "created_by": "{{user.email}}" } + } +} +``` + +### Common RLS Patterns + +**Public create, admin-only management (e.g., contact forms, waitlists):** +```jsonc +{ + "rls": { + "create": true, + "read": { "user_condition": { "role": "admin" } }, + "update": { "user_condition": { "role": "admin" } }, + "delete": { "user_condition": { "role": "admin" } } + } +} +``` + +**Owner-only access:** +```jsonc +{ + "rls": { + "create": true, + "read": { "created_by": "{{user.email}}" }, + "update": { "created_by": "{{user.email}}" }, + "delete": { "created_by": "{{user.email}}" } + } +} +``` + +**Logged-in users only:** +```jsonc +{ + "rls": { + "create": { "user_condition": { "id": "{{user.id}}" } }, + "read": true, + "update": { "created_by": "{{user.email}}" }, + "delete": { "created_by": "{{user.email}}" } + } +} +``` + +### Limitations + +- **user_condition is equality only:** `user_condition` only supports exact match (e.g., `{ "role": "admin" }`) - no operators +- **No comparison operators on user_condition:** `$gt`, `$lt`, `$regex`, `$expr`, `$where` are NOT supported for user conditions +- **No entity field filtering:** Cannot filter by entity field values (e.g., `{"status": "published"}`) +- **No deeply nested templates:** Templates like `{{user.data.profile.department}}` may not work + +**Supported operators:** +- **Logical operators:** `$or`, `$and`, `$nor` for combining multiple conditions +- **Field operators (for `data.*` fields only):** `$in`, `$nin`, `$ne`, `$all` + +### Complex Access Patterns + +For complex access patterns that require multiple conditions (e.g., "owner OR admin"), you have two options: + +1. **Use the Base44 Dashboard UI** - The dashboard allows adding multiple rules per operation with OR logic +2. **Use separate entities** - Split data into multiple entities with different access rules +3. **Use backend functions** - Implement custom access logic in backend functions + +## Field Level Security (FLS) + +Field Level Security allows you to control access to individual fields within an entity. FLS rules are defined within each field's schema using the `rls` property. + +### FLS Operations + +FLS supports the same operations as entity-level RLS: + +| Operation | Description | +|-----------|-------------| +| `create` | Control who can set this field when creating records | +| `read` | Control who can view this field | +| `update` | Control who can modify this field | +| `delete` | Control who can clear this field | +| `write` | Shorthand for `create`, `update`, and `delete` combined | + +### FLS Example + +```jsonc +{ + "name": "Employee", + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "Employee name" + }, + "salary": { + "type": "number", + "description": "Employee salary", + "rls": { + "read": { "user_condition": { "role": "hr" } }, + "update": { "user_condition": { "role": "hr" } } + } + }, + "department": { + "type": "string", + "description": "Department name" + } + }, + "required": ["name"] +} +``` + +In this example, only users with the `hr` role can read or update the `salary` field. All users with access to the entity can read/update other fields. + +### FLS Notes + +- If no field-level RLS is defined, the field inherits the entity-level RLS rules +- FLS rules follow the same condition format as entity-level RLS +- Use FLS for sensitive fields like salary, SSN, or internal notes + +## Pushing Entities + +The `entities push` command will push all entities that exist in the `base44/entities` folder. + +```bash +npx base44 entities push +``` + +For more details on the push command, see [entities-push.md](entities-push.md). diff --git a/evals/fixtures/react-task-manager/skills/base44-cli/references/entities-push.md b/evals/fixtures/react-task-manager/skills/base44-cli/references/entities-push.md new file mode 100644 index 0000000..63b92d7 --- /dev/null +++ b/evals/fixtures/react-task-manager/skills/base44-cli/references/entities-push.md @@ -0,0 +1,71 @@ +# base44 entities push + +Push local entity definitions to Base44. + +## Syntax + +```bash +npx base44 entities push +``` + +## Authentication + +**Required**: Yes. If not authenticated, you'll be prompted to login first. + +## What It Does + +1. Pushes all entities that exist in the `base44/entities` folder +2. Validates that entities exist in the folder +3. Displays the count of entities to be pushed +4. Uploads entities to the Base44 backend +5. Reports the results: created, updated, and deleted entities + +## Prerequisites + +- Must be run from a Base44 project directory +- Project must have entity definitions in the `base44/entities` folder + +## Output + +```bash +$ npx base44 entities push + +Found 3 entities to push +Pushing entities to Base44... + +Created: User, Post +Updated: Comment +Deleted: OldEntity + +✓ Entities pushed successfully +``` + +## Entity Synchronization + +The push operation synchronizes your local entity schema with Base44: + +- **Created**: New entities that didn't exist in Base44 +- **Updated**: Existing entities with modified schema or configuration +- **Deleted**: Entities that were removed from your local configuration + +## Error Handling + +If no entities are found in your project: +```bash +$ npx base44 entities push +No entities found in project +``` + +## Use Cases + +- After defining new entities in your project +- When modifying existing entity schemas +- To sync entity changes before deploying +- As part of your development workflow when data models change + +## Notes + +- This command syncs the entity schema/structure, not the actual data +- Changes are applied to your Base44 project immediately +- Make sure to test entity changes in a development environment first +- Entity definitions are located in the `base44/entities/` directory diff --git a/evals/fixtures/react-task-manager/skills/base44-cli/references/functions-create.md b/evals/fixtures/react-task-manager/skills/base44-cli/references/functions-create.md new file mode 100644 index 0000000..c523cdc --- /dev/null +++ b/evals/fixtures/react-task-manager/skills/base44-cli/references/functions-create.md @@ -0,0 +1,229 @@ +# Creating Functions + +Base44 functions are serverless backend functions that run on Deno. They are defined locally in your project and deployed to the Base44 backend. + +## Function Directory + +All function definitions must be placed in the `base44/functions/` folder in your project. Each function lives in its own subdirectory with a configuration file and entry point. + +Example structure: +``` +my-app/ + base44/ + functions/ + process-order/ + function.jsonc + index.ts + send-notification/ + function.jsonc + index.ts +``` + +## How to Create a Function + +1. Create a new directory in `base44/functions/` with your function name (use kebab-case) +2. Create a `function.jsonc` configuration file in the directory +3. Create the entry point file (e.g., `index.ts`) +4. Deploy the function using the CLI + +## Function Configuration + +Each function requires a `function.jsonc` configuration file: + +```jsonc +{ + "name": "my-function", + "entry": "index.ts" +} +``` + +### Configuration Properties + +| Property | Description | Required | +|----------|-------------|----------| +| `name` | Function name (must match `/^[^.]+$/` - no dots allowed) | Yes | +| `entry` | Entry point file path relative to the function directory (min 1 char) | Yes | + +## Entry Point File + +Functions run on Deno and must export using `Deno.serve()`. Use `npm:` prefix for npm packages. + +```typescript +import { createClientFromRequest } from "npm:@base44/sdk"; + +Deno.serve(async (req) => { + // Get authenticated client from request + const base44 = createClientFromRequest(req); + + // Parse input + const { orderId, action } = await req.json(); + + // Your logic here + const order = await base44.entities.Orders.get(orderId); + + // Return response + return Response.json({ + success: true, + order: order + }); +}); +``` + +### Request Object + +The function receives a standard Deno `Request` object: +- `req.json()` - Parse JSON body +- `req.text()` - Get raw text body +- `req.headers` - Access request headers +- `req.method` - HTTP method + +### Response Object + +Return using `Response.json()` for JSON responses: + +```typescript +// Success response +return Response.json({ data: result }); + +// Error response with status code +return Response.json({ error: "Something went wrong" }, { status: 400 }); + +// Not found +return Response.json({ error: "Order not found" }, { status: 404 }); +``` + +## Complete Example + +### Directory Structure +``` +base44/ + functions/ + process-order/ + function.jsonc + index.ts +``` + +### function.jsonc +```jsonc +{ + "name": "process-order", + "entry": "index.ts" +} +``` + +### index.ts +```typescript +import { createClientFromRequest } from "npm:@base44/sdk"; + +Deno.serve(async (req) => { + try { + const base44 = createClientFromRequest(req); + const { orderId } = await req.json(); + + // Validate input + if (!orderId) { + return Response.json( + { error: "Order ID is required" }, + { status: 400 } + ); + } + + // Fetch and process the order + const order = await base44.entities.Orders.get(orderId); + if (!order) { + return Response.json( + { error: "Order not found" }, + { status: 404 } + ); + } + + return Response.json({ + success: true, + orderId: order.id, + processedAt: new Date().toISOString() + }); + + } catch (error) { + return Response.json( + { error: error.message }, + { status: 500 } + ); + } +}); +``` + +## Using Service Role Access + +For admin-level operations, use `asServiceRole`: + +```typescript +import { createClientFromRequest } from "npm:@base44/sdk"; + +Deno.serve(async (req) => { + const base44 = createClientFromRequest(req); + + // Check user is authenticated + const user = await base44.auth.me(); + if (!user) { + return Response.json({ error: "Unauthorized" }, { status: 401 }); + } + + // Use service role for admin operations + const allOrders = await base44.asServiceRole.entities.Orders.list(); + + return Response.json({ orders: allOrders }); +}); +``` + +## Using Secrets + +Access environment variables configured in the app dashboard: + +```typescript +Deno.serve(async (req) => { + // Access environment variables (configured in app settings) + const apiKey = Deno.env.get("STRIPE_API_KEY"); + + const response = await fetch("https://api.stripe.com/v1/charges", { + headers: { + "Authorization": `Bearer ${apiKey}` + } + }); + + return Response.json(await response.json()); +}); +``` + +## Naming Conventions + +- **Directory name**: Use kebab-case (e.g., `process-order`, `send-notification`) +- **Function name**: Match the directory name, must match pattern `/^[^.]+$/` (no dots allowed) + - Valid: `process-order`, `send_notification`, `myFunction` + - Invalid: `process.order`, `send.notification.v2` +- **Entry file**: Typically `index.ts` or `index.js` + +## Deploying Functions + +After creating your function, deploy it to Base44: + +```bash +npx base44 functions deploy +``` + +For more details on deploying, see [functions-deploy.md](functions-deploy.md). + +## Notes + +- Functions run on Deno runtime, not Node.js +- Use `npm:` prefix for npm packages (e.g., `npm:@base44/sdk`) +- Use `createClientFromRequest(req)` to get a client that inherits the caller's auth context +- Configure secrets via app dashboard for API keys +- Make sure to handle errors gracefully and return appropriate HTTP status codes + +## Common Mistakes + +| Wrong | Correct | Why | +|-------|---------|-----| +| `functions/myFunction.js` (single file) | `functions/my-function/index.ts` + `function.jsonc` | Functions require subdirectory with config | +| `import { ... } from "@base44/sdk"` | `import { ... } from "npm:@base44/sdk"` | Deno requires `npm:` prefix for npm packages | +| `MyFunction` or `myFunction` directory | `my-function` directory | Use kebab-case for directory names | diff --git a/evals/fixtures/react-task-manager/skills/base44-cli/references/functions-deploy.md b/evals/fixtures/react-task-manager/skills/base44-cli/references/functions-deploy.md new file mode 100644 index 0000000..f89d796 --- /dev/null +++ b/evals/fixtures/react-task-manager/skills/base44-cli/references/functions-deploy.md @@ -0,0 +1,78 @@ +# base44 functions deploy + +Deploy local function definitions to Base44. + +## Syntax + +```bash +npx base44 functions deploy +``` + +## Authentication + +**Required**: Yes. If not authenticated, you'll be prompted to login first. + +## What It Does + +1. Scans the `base44/functions/` directory for function definitions +2. Validates that functions exist and have valid configurations +3. Displays the count of functions to be deployed +4. Uploads function code and configuration to Base44 +5. Reports the results: deployed and deleted functions + +## Prerequisites + +- Must be run from a Base44 project directory +- Project must have function definitions in the `base44/functions/` folder +- Each function must have a valid `function.jsonc` config file + +## Output + +```bash +$ npx base44 functions deploy + +Found 2 functions to deploy +Deploying functions to Base44... + +Deployed: process-order, send-notification +Deleted: old-function + +✓ Functions deployed successfully +``` + +## Function Synchronization + +The deploy operation synchronizes your local functions with Base44: + +- **Deployed**: Functions that were created or updated +- **Deleted**: Functions that were removed from your local configuration + +## Error Handling + +If no functions are found in your project: +```bash +$ npx base44 functions deploy +No functions found. Create functions in the 'functions' directory. +``` + +If a function has configuration errors: +```bash +$ npx base44 functions deploy +Function deployment errors: +'my-function' function: Entry point cannot be empty +``` + +## Use Cases + +- After creating new functions in your project +- When modifying existing function code or configuration +- To sync function changes before testing +- As part of your development workflow when backend logic changes + +## Notes + +- This command deploys the function code and configuration +- Changes are applied to your Base44 project immediately +- Make sure to test functions in a development environment first +- Function definitions are located in the `base44/functions/` directory +- For how to create functions, see [functions-create.md](functions-create.md) diff --git a/evals/fixtures/react-task-manager/skills/base44-cli/references/link.md b/evals/fixtures/react-task-manager/skills/base44-cli/references/link.md new file mode 100644 index 0000000..0b009ce --- /dev/null +++ b/evals/fixtures/react-task-manager/skills/base44-cli/references/link.md @@ -0,0 +1,81 @@ +# base44 link + +Links an existing local Base44 project to a Base44 app in the cloud. Use this when you have a `base44/config.jsonc` but haven't connected it to a Base44 app yet. + +## Critical: When to Use Link vs Create + +| Scenario | Command | +|----------|---------| +| Starting fresh, no `base44/` folder | `npx base44 create` | +| Have `base44/config.jsonc` but no `.app.jsonc` | `npx base44 link` | +| Project already linked (has `.app.jsonc`) | Already done, use `deploy` | + +## Syntax + +```bash +npx base44 link [options] +``` + +## Options + +| Option | Description | Required | +|--------|-------------|----------| +| `-c, --create` | Create a new project (skip selection prompt) | No | +| `-n, --name ` | Project name (required when `--create` is used) | With `--create` | +| `-d, --description ` | Project description | No | +| `-p, --projectId ` | Project ID to link to an existing project (skip selection prompt) | No | + +## Non-Interactive Mode + +For CI/CD or agent use: + +**Create a new project:** +```bash +npx base44 link --create --name my-app +``` + +**Link to an existing project:** +```bash +npx base44 link --projectId +``` + +WRONG: `npx base44 link --create` (missing --name) +WRONG: `npx base44 link --create --projectId ` (cannot use both) +RIGHT: `npx base44 link --create --name my-app` +RIGHT: `npx base44 link --projectId ` + +## Examples + +```bash +# Interactive mode - prompts for project details +npx base44 link + +# Non-interactive - create and link in one step +npx base44 link --create --name my-app + +# With description +npx base44 link --create --name my-app --description "My awesome app" + +# Link to a specific existing project by ID +npx base44 link --projectId abc123 +``` + +## What It Does + +1. Finds the `base44/config.jsonc` in the current directory (or parent directories) +2. Verifies no `.app.jsonc` exists (project not already linked) +3. Either: + - Creates a new Base44 app in the cloud (with `--create`), OR + - Links to an existing project (with `--projectId` or interactive selection) +4. Writes the app ID to `base44/.app.jsonc` + +## Requirements + +- Must have `base44/config.jsonc` in the project +- Must NOT have `base44/.app.jsonc` (use `deploy` if already linked) +- Must be authenticated (run `npx base44 login` first) + +## Notes + +- After linking, you can deploy resources with `npx base44 deploy` +- The `.app.jsonc` file should be git-ignored (contains your app ID) diff --git a/evals/fixtures/react-task-manager/skills/base44-cli/references/rls-examples.md b/evals/fixtures/react-task-manager/skills/base44-cli/references/rls-examples.md new file mode 100644 index 0000000..7d3ef42 --- /dev/null +++ b/evals/fixtures/react-task-manager/skills/base44-cli/references/rls-examples.md @@ -0,0 +1,463 @@ +# RLS Examples + +Practical Row-Level Security patterns for common application types. + +**Important:** Base44 RLS supports: +- **Logical operators:** `$or`, `$and`, `$nor` for combining conditions +- **Field operators (for `data.*` fields):** `$in`, `$nin`, `$ne`, `$all` +- **user_condition:** Equality only (no operators) + +## Contents +- [Simple Patterns (JSON Schema)](#simple-patterns-json-schema) +- [Using Operators](#using-operators) +- [Field-Level Security Examples](#field-level-security-examples) +- [Complex Patterns (Dashboard UI or Backend)](#complex-patterns-dashboard-ui-or-backend) +- [Best Practices](#best-practices) + +--- + +## Simple Patterns (JSON Schema) + +These patterns work with the JSON schema RLS format. + +### Todo App - Owner-only access + +Users see and manage only their own tasks. + +```jsonc +{ + "name": "Task", + "type": "object", + "properties": { + "title": { "type": "string" }, + "description": { "type": "string" }, + "completed": { "type": "boolean" }, + "priority": { "type": "string", "enum": ["low", "medium", "high"] }, + "due_date": { "type": "string", "format": "date" } + }, + "rls": { + "create": true, + "read": { "created_by": "{{user.email}}" }, + "update": { "created_by": "{{user.email}}" }, + "delete": { "created_by": "{{user.email}}" } + } +} +``` + +### Contact Form - Public create, admin-only read + +Anyone can submit, only admins can view submissions. + +```jsonc +{ + "name": "ContactSubmission", + "type": "object", + "properties": { + "name": { "type": "string" }, + "email": { "type": "string", "format": "email" }, + "message": { "type": "string" } + }, + "rls": { + "create": true, + "read": { "user_condition": { "role": "admin" } }, + "update": { "user_condition": { "role": "admin" } }, + "delete": { "user_condition": { "role": "admin" } } + } +} +``` + +### User Profile - Self-management + +Users can only access their own profile. + +```jsonc +{ + "name": "UserProfile", + "type": "object", + "properties": { + "name": { "type": "string" }, + "avatar_url": { "type": "string" }, + "bio": { "type": "string" }, + "preferences": { "type": "object" } + }, + "rls": { + "create": true, + "read": { "created_by": "{{user.email}}" }, + "update": { "created_by": "{{user.email}}" }, + "delete": { "created_by": "{{user.email}}" } + } +} +``` + +### Department Data - Same department access + +Users can only see records from their department. + +```jsonc +{ + "name": "DepartmentAnnouncement", + "type": "object", + "properties": { + "title": { "type": "string" }, + "content": { "type": "string" }, + "department": { "type": "string" } + }, + "rls": { + "create": { "user_condition": { "role": "manager" } }, + "read": { "data.department": "{{user.data.department}}" }, + "update": { "user_condition": { "role": "manager" } }, + "delete": { "user_condition": { "role": "admin" } } + } +} +``` + +### Subscription - Admin-managed, user-readable via email field + +```jsonc +{ + "name": "Subscription", + "type": "object", + "properties": { + "user_email": { "type": "string" }, + "tier": { "type": "string", "enum": ["free", "basic", "pro", "enterprise"] }, + "credits": { "type": "number" }, + "renewal_date": { "type": "string", "format": "date" } + }, + "rls": { + "create": { "user_condition": { "role": "admin" } }, + "read": { "data.user_email": "{{user.email}}" }, + "update": { "user_condition": { "role": "admin" } }, + "delete": { "user_condition": { "role": "admin" } } + } +} +``` + +**Note:** This pattern only allows users to read their own subscription. Admins need to use the Dashboard UI to configure additional read access for themselves. + +### Private Data - Owner-only + +```jsonc +{ + "name": "PrivateNotes", + "type": "object", + "properties": { + "title": { "type": "string" }, + "content": { "type": "string" }, + "tags": { "type": "array", "items": { "type": "string" } } + }, + "rls": { + "create": true, + "read": { "created_by": "{{user.email}}" }, + "update": { "created_by": "{{user.email}}" }, + "delete": { "created_by": "{{user.email}}" } + } +} +``` + +### Public Read, Authenticated Write + +Anyone can read, only logged-in users can create/edit their own records. + +```jsonc +{ + "name": "BlogPost", + "type": "object", + "properties": { + "title": { "type": "string" }, + "content": { "type": "string" }, + "author_email": { "type": "string" } + }, + "rls": { + "create": true, + "read": true, + "update": { "created_by": "{{user.email}}" }, + "delete": { "created_by": "{{user.email}}" } + } +} +``` + +--- + +## Using Operators + +### Logical Operators + +Combine multiple conditions using `$or`, `$and`, or `$nor`: + +**Owner OR Admin access:** +```jsonc +{ + "name": "Document", + "type": "object", + "properties": { + "title": { "type": "string" }, + "content": { "type": "string" } + }, + "rls": { + "create": true, + "read": { + "$or": [ + { "created_by": "{{user.email}}" }, + { "user_condition": { "role": "admin" } } + ] + }, + "update": { + "$or": [ + { "created_by": "{{user.email}}" }, + { "user_condition": { "role": "admin" } } + ] + }, + "delete": { "user_condition": { "role": "admin" } } + } +} +``` + +**Multiple roles with $or:** +```jsonc +{ + "rls": { + "read": { + "$or": [ + { "user_condition": { "role": "admin" } }, + { "user_condition": { "role": "manager" } }, + { "user_condition": { "role": "hr" } } + ] + } + } +} +``` + +### Field Operators for data.* Fields + +Use `$in`, `$nin`, `$ne`, `$all` for comparing entity data fields: + +**Access based on tags ($in):** +```jsonc +{ + "rls": { + "read": { + "data.category": { "$in": ["public", "shared"] } + } + } +} +``` + +**Exclude specific statuses ($nin):** +```jsonc +{ + "rls": { + "read": { + "data.status": { "$nin": ["archived", "deleted"] } + } + } +} +``` + +**Not equal ($ne):** +```jsonc +{ + "rls": { + "read": { + "data.visibility": { "$ne": "private" } + } + } +} +``` + +**All tags must match ($all):** +```jsonc +{ + "rls": { + "read": { + "data.required_tags": { "$all": ["approved", "reviewed"] } + } + } +} +``` + +### Combining Logical and Field Operators + +```jsonc +{ + "rls": { + "read": { + "$and": [ + { "data.status": { "$ne": "draft" } }, + { + "$or": [ + { "created_by": "{{user.email}}" }, + { "data.visibility": "public" } + ] + } + ] + } + } +} +``` + +--- + +## Field-Level Security Examples + +Control access to specific fields within an entity. + +### Sensitive Salary Field + +```jsonc +{ + "name": "Employee", + "type": "object", + "properties": { + "name": { "type": "string" }, + "email": { "type": "string", "format": "email" }, + "salary": { + "type": "number", + "description": "Annual salary", + "rls": { + "read": { "user_condition": { "role": "hr" } }, + "write": { "user_condition": { "role": "hr" } } + } + }, + "performance_notes": { + "type": "string", + "description": "Manager notes", + "rls": { + "read": { + "$or": [ + { "user_condition": { "role": "manager" } }, + { "user_condition": { "role": "hr" } } + ] + }, + "write": { "user_condition": { "role": "manager" } } + } + } + } +} +``` + +### Admin-Only Internal Fields + +```jsonc +{ + "name": "Order", + "type": "object", + "properties": { + "order_number": { "type": "string" }, + "total": { "type": "number" }, + "internal_notes": { + "type": "string", + "description": "Internal processing notes", + "rls": { + "read": { "user_condition": { "role": "admin" } }, + "write": { "user_condition": { "role": "admin" } } + } + }, + "profit_margin": { + "type": "number", + "description": "Profit margin percentage", + "rls": { + "read": { "user_condition": { "role": "admin" } }, + "write": false + } + } + } +} +``` + +--- + +## Complex Patterns (Dashboard UI or Backend) + +Some patterns may still require the Dashboard UI or backend functions. + +### Bidirectional Relationships (e.g., Friendships, Matches) + +**Requirement:** Either party in a relationship should have access. + +**Now possible with $or:** +```jsonc +{ + "rls": { + "read": { + "$or": [ + { "data.user_a_email": "{{user.email}}" }, + { "data.user_b_email": "{{user.email}}" } + ] + } + } +} +``` + +**Alternative solutions:** +1. **Entity redesign:** Store two records per relationship (one for each party) +2. **Backend function:** Query with custom logic + +### Complex Business Logic + +**Requirement:** Access depends on multiple entity fields with complex conditions. + +**JSON Schema limitation:** While operators help, very complex business logic may still be hard to express. + +**Solution options:** +1. **Backend function:** Implement custom access logic +2. **Combine simpler rules:** Break complex rules into simpler entity-level and field-level rules + +--- + +## Best Practices + +### Security Strategy + +Use a combination of entity-level RLS and field-level security: + +| Data Type | Approach | Example | +|-----------|----------|---------| +| User-editable | Entity RLS: Owner-only | UserProfile with `created_by` check | +| Sensitive fields | Field-level RLS | Salary field with HR role check | +| Multi-role access | `$or` with user_condition | Admin OR Manager access | +| Conditional access | Field operators | `$in`, `$ne` on data fields | +| Public content | Entity RLS: `read: true` | PublicPost | +| Private content | Entity RLS: Owner-only | PrivateNote | + +### When to Use Each Approach + +| Requirement | Approach | +|-------------|----------| +| Single condition (owner, admin, department) | JSON Schema RLS | +| Multiple OR/AND conditions | JSON Schema RLS with `$or`/`$and` | +| Field value checks with `$in`/`$ne`/etc. | JSON Schema RLS for `data.*` fields | +| Field-level access control | JSON Schema FLS (field-level `rls`) | +| Complex comparison operators (`$gt`, `$lt`) | Backend functions | +| Very complex business logic | Backend functions | + +### Common Role Patterns + +| Role | Typical Access | +|------|----------------| +| `admin` | Full access to all records | +| `moderator` | Read/update access, limited delete | +| `manager` | Department-scoped access | +| `user` | Own records only | + +### Supported Operators Summary + +| Operator | Supported | Notes | +|----------|-----------|-------| +| `$or` | Yes | Combine multiple conditions | +| `$and` | Yes | All conditions must match | +| `$nor` | Yes | None of the conditions match | +| `$in` | Yes | For `data.*` fields only | +| `$nin` | Yes | For `data.*` fields only | +| `$ne` | Yes | For `data.*` fields only | +| `$all` | Yes | For `data.*` fields only | +| `$gt`, `$lt`, `$gte`, `$lte` | No | Use backend functions | +| `$regex` | No | Use backend functions | + +### Limitations Summary + +| Not Supported | Alternative | +|---------------|-------------| +| Operators on `user_condition` | Use equality only for user checks | +| Comparison operators (`$gt`, `$lt`) | Backend functions | +| Regex matching (`$regex`) | Backend functions | +| Cross-entity relationships | Backend functions | diff --git a/evals/fixtures/react-task-manager/skills/base44-cli/references/site-deploy.md b/evals/fixtures/react-task-manager/skills/base44-cli/references/site-deploy.md new file mode 100644 index 0000000..929d302 --- /dev/null +++ b/evals/fixtures/react-task-manager/skills/base44-cli/references/site-deploy.md @@ -0,0 +1,118 @@ +# base44 site deploy + +Deploy built site files to Base44 hosting. + +## Table of Contents + +- [Syntax](#syntax) +- [Authentication](#authentication) +- [Prerequisites](#prerequisites) +- [How It Works](#how-it-works) +- [Interactive Flow](#interactive-flow) +- [Typical Workflow](#typical-workflow) +- [Configuration](#configuration) +- [Error Handling](#error-handling) +- [Use Cases](#use-cases) +- [Notes](#notes) + +## Syntax + +```bash +npx base44 site deploy [options] +``` + +## Options + +| Option | Description | +| ------------ | ------------------------- | +| `-y, --yes` | Skip confirmation prompt | + +Use `-y` flag for non-interactive/automated deployments: + +```bash +npx base44 site deploy -y +``` + +## Authentication + +**Required**: Yes. If not authenticated, you'll be prompted to login first. + +## Prerequisites + +- Must be run from a Base44 project directory +- Project must have `site.outputDirectory` configured in project config +- Site must be built before deploying (run your build command first) +- **SPA only**: Base44 hosting supports Single Page Applications with a single `index.html` entry point. All routes are served from `index.html` (client-side routing). + +## How It Works + +1. Reads project configuration +2. Validates that site configuration exists +3. Prompts for deployment confirmation showing the output directory +4. Creates an archive of site files from the output directory +5. Deploys to Base44 hosting +6. Returns the app URL + +## Interactive Flow + +```bash +$ npx base44 site deploy + +Deploy site from ./dist? (yes/no) yes + +Creating archive... +Uploading to Base44... +Deploying... + +✓ Deployment successful! + +Visit your site at: https://my-app.base44.app +``` + +## Typical Workflow + +```bash +# 1. Build your site using your framework's build command +npm run build + +# 2. Deploy to Base44 +npx base44 site deploy +``` + +## Configuration + +The `site.outputDirectory` in your project configuration should point to where your framework outputs built files: + +- Vite: typically `./dist` +- Next.js: typically `./.next` or `./out` +- Create React App: typically `./build` +- Custom: whatever your build tool outputs to + +## Error Handling + +If site configuration is missing: +```bash +$ npx base44 site deploy +Error: No site configuration found in project +``` + +If you cancel the deployment: +```bash +Deploy site from ./dist? (yes/no) no +Deployment cancelled +``` + +## Use Cases + +- Deploy your site after making changes +- Push new versions of your application +- Deploy after updating content or functionality +- Part of your CI/CD pipeline + +## Notes + +- Always build your site before deploying +- The command deploys whatever is in your output directory +- Make sure your build completed successfully before deploying +- Previous deployments are preserved (versioned) in Base44 +- Deployment is immediate and updates your live site diff --git a/evals/fixtures/react-task-manager/skills/base44-sdk/SKILL.md b/evals/fixtures/react-task-manager/skills/base44-sdk/SKILL.md new file mode 100644 index 0000000..01ddd7f --- /dev/null +++ b/evals/fixtures/react-task-manager/skills/base44-sdk/SKILL.md @@ -0,0 +1,296 @@ +--- +name: base44-sdk +description: "The base44 SDK is the library to communicate with base44 services. In projects, you use it to communicate with remote resources (entities, backend functions, ai agents) and to write backend functions. This skill is the place for learning about available modules and types. When you plan or implement a feature, you must learn this skill" +--- + +# Base44 Coder + +Build apps on the Base44 platform using the Base44 JavaScript SDK. + +## ⚡ IMMEDIATE ACTION REQUIRED - Read This First + +This skill activates on ANY mention of "base44" or when a `base44/` folder exists. **DO NOT read documentation files or search the web before acting.** + +**Your first action MUST be:** +1. Check if `base44/config.jsonc` exists in the current directory +2. If **YES** (existing project scenario): + - This skill (base44-sdk) handles the request + - Implement features using Base44 SDK + - Do NOT use base44-cli unless user explicitly requests CLI commands +3. If **NO** (new project scenario): + - Transfer to base44-cli skill for project initialization + - This skill cannot help until project is initialized + +## When to Use This Skill vs base44-cli + +**Use base44-sdk when:** +- Building features in an **EXISTING** Base44 project +- `base44/config.jsonc` already exists in the project +- Base44 SDK imports are present (`@base44/sdk`) +- Writing JavaScript/TypeScript code using Base44 SDK modules +- Implementing functionality, components, or features +- User mentions: "implement", "build a feature", "add functionality", "write code for" +- User says "create a [type] app" **and** a Base44 project already exists + +**DO NOT USE base44-sdk for:** +- ❌ Initializing new Base44 projects (use `base44-cli` instead) +- ❌ Empty directories without Base44 configuration +- ❌ When user says "create a new Base44 project/app/site" and no project exists +- ❌ CLI commands like `npx base44 create`, `npx base44 deploy`, `npx base44 login` (use `base44-cli`) + +**Skill Dependencies:** +- `base44-sdk` assumes a Base44 project is **already initialized** +- `base44-cli` is a **prerequisite** for `base44-sdk` in new projects +- If user wants to "create an app" and no Base44 project exists, use `base44-cli` first + +**State Check Logic:** +Before selecting this skill, verify: +- IF (user mentions "create/build app" OR "make a project"): + - IF (directory is empty OR no `base44/config.jsonc` exists): + → Use **base44-cli** (project initialization needed) + - ELSE: + → Use **base44-sdk** (project exists, build features) + +## Quick Start + +```javascript +// In Base44-generated apps, base44 client is pre-configured and available + +// CRUD operations +const task = await base44.entities.Task.create({ title: "New task", status: "pending" }); +const tasks = await base44.entities.Task.list(); +await base44.entities.Task.update(task.id, { status: "done" }); + +// Get current user +const user = await base44.auth.me(); +``` + +```javascript +// External apps +import { createClient } from "@base44/sdk"; + +// IMPORTANT: Use 'appId' (NOT 'clientId' or 'id') +const base44 = createClient({ appId: "your-app-id" }); +await base44.auth.loginViaEmailPassword("user@example.com", "password"); +``` + +## ⚠️ CRITICAL: Do Not Hallucinate APIs + +**Before writing ANY Base44 code, verify method names against this table or [QUICK_REFERENCE.md](references/QUICK_REFERENCE.md).** + +Base44 SDK has unique method names. Do NOT assume patterns from Firebase, Supabase, or other SDKs. + +### Authentication - WRONG vs CORRECT + +| ❌ WRONG (hallucinated) | ✅ CORRECT | +|------------------------|-----------| +| `signInWithGoogle()` | `loginWithProvider('google')` | +| `signInWithProvider('google')` | `loginWithProvider('google')` | +| `auth.google()` | `loginWithProvider('google')` | +| `signInWithEmailAndPassword(email, pw)` | `loginViaEmailPassword(email, pw)` | +| `signIn(email, pw)` | `loginViaEmailPassword(email, pw)` | +| `createUser()` / `signUp()` | `register({email, password})` | +| `onAuthStateChanged()` | `me()` (no listener, call when needed) | +| `currentUser` | `await auth.me()` | + +### Functions - WRONG vs CORRECT + +| ❌ WRONG (hallucinated) | ✅ CORRECT | +|------------------------|-----------| +| `functions.call('name', data)` | `functions.invoke('name', data)` | +| `functions.run('name', data)` | `functions.invoke('name', data)` | +| `callFunction('name', data)` | `functions.invoke('name', data)` | +| `httpsCallable('name')(data)` | `functions.invoke('name', data)` | + +### Integrations - WRONG vs CORRECT + +| ❌ WRONG (hallucinated) | ✅ CORRECT | +|------------------------|-----------| +| `ai.generate(prompt)` | `integrations.Core.InvokeLLM({prompt})` | +| `openai.chat(prompt)` | `integrations.Core.InvokeLLM({prompt})` | +| `llm(prompt)` | `integrations.Core.InvokeLLM({prompt})` | +| `sendEmail(to, subject, body)` | `integrations.Core.SendEmail({to, subject, body})` | +| `email.send()` | `integrations.Core.SendEmail({to, subject, body})` | +| `uploadFile(file)` | `integrations.Core.UploadFile({file})` | +| `storage.upload(file)` | `integrations.Core.UploadFile({file})` | + +### Entities - WRONG vs CORRECT + +| ❌ WRONG (hallucinated) | ✅ CORRECT | +|------------------------|-----------| +| `entities.Task.find({...})` | `entities.Task.filter({...})` | +| `entities.Task.findOne(id)` | `entities.Task.get(id)` | +| `entities.Task.insert(data)` | `entities.Task.create(data)` | +| `entities.Task.remove(id)` | `entities.Task.delete(id)` | +| `entities.Task.onChange(cb)` | `entities.Task.subscribe(cb)` | + +## SDK Modules + +| Module | Purpose | Reference | +|--------|---------|-----------| +| `entities` | CRUD operations on data models | [entities.md](references/entities.md) | +| `auth` | Login, register, user management | [auth.md](references/auth.md) | +| `agents` | AI conversations and messages | [base44-agents.md](references/base44-agents.md) | +| `functions` | Backend function invocation | [functions.md](references/functions.md) | +| `integrations` | AI, email, file uploads, custom APIs | [integrations.md](references/integrations.md) | +| `connectors` | OAuth tokens (service role only) | [connectors.md](references/connectors.md) | +| `analytics` | Track custom events and user activity | [analytics.md](references/analytics.md) | +| `appLogs` | Log user activity in app | [app-logs.md](references/app-logs.md) | +| `users` | Invite users to the app | [users.md](references/users.md) | + +For client setup and authentication modes, see [client.md](references/client.md). + +**TypeScript Support:** Each reference file includes a "Type Definitions" section with TypeScript interfaces and types for the module's methods, parameters, and return values. + +## Installation + +Install the Base44 SDK: + +```bash +npm install @base44/sdk +``` + +**Important:** Never assume or hardcode the `@base44/sdk` package version. Always install without a version specifier to get the latest version. + +## Creating a Client (External Apps) + +When creating a client in external apps, **ALWAYS use `appId` as the parameter name**: + +```javascript +import { createClient } from "@base44/sdk"; + +// ✅ CORRECT +const base44 = createClient({ appId: "your-app-id" }); + +// ❌ WRONG - Do NOT use these: +// const base44 = createClient({ clientId: "your-app-id" }); // WRONG +// const base44 = createClient({ id: "your-app-id" }); // WRONG +``` + +**Required parameter:** `appId` (string) - Your Base44 application ID + +**Optional parameters:** +- `token` (string) - Pre-authenticated user token +- `options` (object) - Configuration options + - `options.onError` (function) - Global error handler + +**Example with error handler:** +```javascript +const base44 = createClient({ + appId: "your-app-id", + options: { + onError: (error) => { + console.error("Base44 error:", error); + } + } +}); +``` + +## Module Selection + +**Working with app data?** +- Create/read/update/delete records → `entities` +- Import data from file → `entities.importEntities()` +- Realtime updates → `entities.EntityName.subscribe()` + +**User management?** +- Login/register/logout → `auth` +- Get current user → `auth.me()` +- Update user profile → `auth.updateMe()` +- Invite users → `users.inviteUser()` + +**AI features?** +- Chat with AI agents → `agents` (requires logged-in user) +- Create new conversation → `agents.createConversation()` +- Manage conversations → `agents.getConversations()` +- Generate text/JSON with AI → `integrations.Core.InvokeLLM()` +- Generate images → `integrations.Core.GenerateImage()` + +**Custom backend logic?** +- Run server-side code → `functions.invoke()` +- Need admin access → `base44.asServiceRole.functions.invoke()` + +**External services?** +- Send emails → `integrations.Core.SendEmail()` +- Upload files → `integrations.Core.UploadFile()` +- Custom APIs → `integrations.custom.call()` +- OAuth tokens (Google, Slack) → `connectors` (backend only) + +**Tracking and analytics?** +- Track custom events → `analytics.track()` +- Log page views/activity → `appLogs.logUserInApp()` + +## Common Patterns + +### Filter and Sort Data + +```javascript +const pendingTasks = await base44.entities.Task.filter( + { status: "pending", assignedTo: userId }, // query + "-created_date", // sort (descending) + 10, // limit + 0 // skip +); +``` + +### Protected Routes (check auth) + +```javascript +const user = await base44.auth.me(); +if (!user) { + // Navigate to your custom login page + navigate('/login', { state: { returnTo: window.location.pathname } }); + return; +} +``` + +### Backend Function Call + +```javascript +// Frontend +const result = await base44.functions.invoke("processOrder", { + orderId: "123", + action: "ship" +}); + +// Backend function (Deno) +import { createClientFromRequest } from "npm:@base44/sdk"; + +Deno.serve(async (req) => { + const base44 = createClientFromRequest(req); + const { orderId, action } = await req.json(); + // Process with service role for admin access + const order = await base44.asServiceRole.entities.Orders.get(orderId); + return Response.json({ success: true }); +}); +``` + +### Service Role Access + +Use `asServiceRole` in backend functions for admin-level operations: + +```javascript +// User mode - respects permissions +const myTasks = await base44.entities.Task.list(); + +// Service role - full access (backend only) +const allTasks = await base44.asServiceRole.entities.Task.list(); +const token = await base44.asServiceRole.connectors.getAccessToken("slack"); +``` + +## Frontend vs Backend + +| Capability | Frontend | Backend | +|------------|----------|---------| +| `entities` (user's data) | Yes | Yes | +| `auth` | Yes | Yes | +| `agents` | Yes | Yes | +| `functions.invoke()` | Yes | Yes | +| `integrations` | Yes | Yes | +| `analytics` | Yes | Yes | +| `appLogs` | Yes | Yes | +| `users` | Yes | Yes | +| `asServiceRole.*` | No | Yes | +| `connectors` | No | Yes | + +Backend functions use `Deno.serve()` and `createClientFromRequest(req)` to get a properly authenticated client. diff --git a/evals/fixtures/react-task-manager/skills/base44-sdk/references/QUICK_REFERENCE.md b/evals/fixtures/react-task-manager/skills/base44-sdk/references/QUICK_REFERENCE.md new file mode 100644 index 0000000..d357384 --- /dev/null +++ b/evals/fixtures/react-task-manager/skills/base44-sdk/references/QUICK_REFERENCE.md @@ -0,0 +1,155 @@ +# Base44 SDK Quick Reference + +Compact method signatures for all SDK modules. **Verify against this before writing code.** + +--- + +## Auth (`base44.auth.*`) + +``` +loginViaEmailPassword(email, password, turnstileToken?) → Promise<{access_token, user}> +loginWithProvider('google' | 'microsoft' | 'facebook', fromUrl?) → void +me() → Promise +updateMe(data) → Promise +isAuthenticated() → Promise +logout(redirectUrl?) → void +redirectToLogin(nextUrl) → void # ⚠️ Avoid - prefer custom login UI +register({email, password, turnstile_token?, referral_code?}) → Promise +verifyOtp({email, otpCode}) → Promise +resendOtp(email) → Promise +inviteUser(userEmail, role) → Promise +resetPasswordRequest(email) → Promise +resetPassword({resetToken, newPassword}) → Promise +changePassword({userId, currentPassword, newPassword}) → Promise +setToken(token, saveToStorage?) → void +``` + +--- + +## Entities (`base44.entities.EntityName.*`) + +``` +create(data) → Promise +bulkCreate(dataArray) → Promise +list(sort?, limit?, skip?, fields?) → Promise +filter(query, sort?, limit?, skip?, fields?) → Promise +get(id) → Promise +update(id, data) → Promise +delete(id) → Promise +deleteMany(query) → Promise +importEntities(file) → Promise // frontend only +subscribe(callback) → () => void // returns unsubscribe fn +``` + +**Sort:** Use `-fieldName` for descending (e.g., `-created_date`) + +--- + +## Functions (`base44.functions.*`) + +``` +invoke(functionName, data) → Promise +``` + +**Backend:** Use `base44.asServiceRole.functions.invoke()` for admin access. + +--- + +## Integrations (`base44.integrations.Core.*`) + +``` +InvokeLLM({prompt, add_context_from_internet?, response_json_schema?, file_urls?}) → Promise +GenerateImage({prompt}) → Promise<{url}> +SendEmail({to, subject, body, from_name?}) → Promise +UploadFile({file}) → Promise<{file_url}> +UploadPrivateFile({file}) → Promise<{file_uri}> +CreateFileSignedUrl({file_uri, expires_in?}) → Promise<{signed_url}> +ExtractDataFromUploadedFile({file_url, json_schema}) → Promise +``` + +### Custom Integrations (`base44.integrations.custom.*`) + +``` +call(slug, operationId, {payload?, pathParams?, queryParams?, headers?}?) → Promise<{success, status_code, data}> +``` + +**operationId format:** `"method:/path"` (e.g., `"get:/contacts"`, `"post:/users/{id}"`) + +--- + +## Analytics (`base44.analytics.*`) + +``` +track({eventName, properties?}) → void +``` + +--- + +## App Logs (`base44.appLogs.*`) + +``` +logUserInApp(pageName) → Promise +``` + +--- + +## Users (`base44.users.*`) + +``` +inviteUser(userEmail, role) → Promise // role: 'user' | 'admin' +``` + +--- + +## Connectors (`base44.asServiceRole.connectors.*`) + +**Backend only, service role required.** + +``` +getAccessToken(integrationType) → Promise +``` + +**Types:** `'googlecalendar'`, `'googledrive'`, `'slack'`, `'notion'`, `'salesforce'`, `'hubspot'`, `'linkedin'`, `'tiktok'`, `'github'` + +--- + +## Service Role Access + +**Backend functions only.** Prefix any module with `asServiceRole` for admin access: + +```javascript +base44.asServiceRole.entities.Task.list() +base44.asServiceRole.functions.invoke('name', data) +base44.asServiceRole.connectors.getAccessToken('slack') +``` + +--- + +## Backend Function Template + +```javascript +import { createClientFromRequest } from "@base44/sdk"; + +Deno.serve(async (req) => { + const base44 = createClientFromRequest(req); + const data = await req.json(); + + // User context + const user = await base44.auth.me(); + + // Service role for admin operations + const allRecords = await base44.asServiceRole.entities.Task.list(); + + return Response.json({ success: true }); +}); +``` + +--- + +## Client Initialization (External Apps) + +```javascript +import { createClient } from "@base44/sdk"; + +const base44 = createClient({ appId: "your-app-id" }); // MUST use 'appId' +``` diff --git a/evals/fixtures/react-task-manager/skills/base44-sdk/references/analytics.md b/evals/fixtures/react-task-manager/skills/base44-sdk/references/analytics.md new file mode 100644 index 0000000..d1b2c69 --- /dev/null +++ b/evals/fixtures/react-task-manager/skills/base44-sdk/references/analytics.md @@ -0,0 +1,113 @@ +# Analytics Module + +Track custom events and user activity via `base44.analytics`. + +## Contents +- [Methods](#methods) +- [Examples](#examples) +- [Automatic Tracking](#automatic-tracking) +- [Best Practices](#best-practices) + +## Methods + +| Method | Signature | Description | +|--------|-----------|-------------| +| `track(params)` | `void` | Track a custom event | + +## Examples + +### Track Custom Event + +```javascript +// Track a simple event +base44.analytics.track({ + eventName: "button_clicked" +}); + +// Track event with properties +base44.analytics.track({ + eventName: "purchase_completed", + properties: { + product_id: "prod-123", + amount: 99.99, + currency: "USD" + } +}); +``` + +### Track User Actions + +```javascript +// Track page view +base44.analytics.track({ + eventName: "page_view", + properties: { + page: "/dashboard", + referrer: document.referrer + } +}); + +// Track feature usage +base44.analytics.track({ + eventName: "feature_used", + properties: { + feature: "export_data", + format: "csv" + } +}); +``` + +## Automatic Tracking + +The analytics module automatically tracks: +- **Initialization events**: When the app loads +- **Heartbeat events**: Periodic activity signals +- **Session duration**: Time spent in the app + +These internal events help measure user engagement without manual instrumentation. + +## Best Practices + +1. **Use descriptive event names**: `order_completed` instead of `click` +2. **Include relevant properties**: Add context that helps analyze the event +3. **Be consistent**: Use the same event names and property keys across your app +4. **Don't track sensitive data**: Avoid PII in event properties + +```javascript +// Good: Descriptive with relevant properties +base44.analytics.track({ + eventName: "subscription_started", + properties: { + plan: "pro", + billing_cycle: "annual" + } +}); + +// Avoid: Vague event name, no context +base44.analytics.track({ + eventName: "click" +}); +``` + +## Type Definitions + +```typescript +/** Properties that can be attached to a tracked event. */ +type TrackEventProperties = { + [key: string]: string | number | boolean | null | undefined; +}; + +/** Parameters for the track() method. */ +interface TrackEventParams { + /** The name of the event to track. */ + eventName: string; + /** Optional properties to attach to the event. */ + properties?: TrackEventProperties; +} + +/** The analytics module interface. */ +interface AnalyticsModule { + /** Track a custom event with optional properties. */ + track(params: TrackEventParams): void; +} +``` diff --git a/evals/fixtures/react-task-manager/skills/base44-sdk/references/app-logs.md b/evals/fixtures/react-task-manager/skills/base44-sdk/references/app-logs.md new file mode 100644 index 0000000..0439805 --- /dev/null +++ b/evals/fixtures/react-task-manager/skills/base44-sdk/references/app-logs.md @@ -0,0 +1,78 @@ +# App Logs Module + +Log user activity in your app via `base44.appLogs`. + +## Contents +- [Methods](#methods) +- [Examples](#examples) +- [Use Cases](#use-cases) + +## Methods + +| Method | Signature | Description | +|--------|-----------|-------------| +| `logUserInApp(pageName)` | `Promise` | Log user activity on a page | + +## Examples + +### Log User Activity + +```javascript +// Log when user visits a page +await base44.appLogs.logUserInApp("dashboard"); + +// Log specific page visits +await base44.appLogs.logUserInApp("settings"); +await base44.appLogs.logUserInApp("profile"); + +// Log feature usage +await base44.appLogs.logUserInApp("export-button-click"); +``` + +The page name doesn't have to be an actual page - it can be any string you want to track. + +## Use Cases + +### Track Page Views in React + +```javascript +// Log page views on route change +useEffect(() => { + base44.appLogs.logUserInApp(window.location.pathname); +}, [location.pathname]); +``` + +### Track Feature Usage + +```javascript +// Log when user uses specific features +function handleExport() { + base44.appLogs.logUserInApp("export-data"); + // ... export logic +} + +function handleSettingsChange() { + base44.appLogs.logUserInApp("settings-updated"); + // ... save settings +} +``` + +## Notes + +- Logs appear in the Analytics page of your app dashboard +- App logs track page-level and feature-level activity +- Use `analytics.track()` for custom events with properties, `appLogs.logUserInApp()` for simple page/feature tracking + +## Type Definitions + +```typescript +/** App Logs module for tracking and analyzing app usage. */ +interface AppLogsModule { + /** + * Log user activity in the app. + * @param pageName - Name of the page or section being visited. + * @returns Promise that resolves when the log is recorded. + */ + logUserInApp(pageName: string): Promise; +} +``` diff --git a/evals/fixtures/react-task-manager/skills/base44-sdk/references/auth.md b/evals/fixtures/react-task-manager/skills/base44-sdk/references/auth.md new file mode 100644 index 0000000..5195b3a --- /dev/null +++ b/evals/fixtures/react-task-manager/skills/base44-sdk/references/auth.md @@ -0,0 +1,761 @@ +# Auth Module + +User authentication, registration, and session management via `base44.auth`. + +## Contents +- [TypeScript Types](#typescript-types) +- [Methods](#methods) +- [Examples](#examples) +- [Error Handling](#error-handling) +- [Auth Providers](#auth-providers) +- [Environment Availability](#environment-availability) +- [App Visibility](#app-visibility) +- [Limitations](#limitations) + +--- + +## TypeScript Types + +### User Interface +```typescript +interface User { + id: string; + created_date: string; + updated_date: string; + email: string; + full_name: string | null; + disabled: boolean | null; + is_verified: boolean; + app_id: string; + is_service: boolean; + role: string; + [key: string]: any; // Custom schema fields +} +``` + +### LoginResponse Interface +```typescript +interface LoginResponse { + access_token: string; // JWT token + user: User; // Complete user object +} +``` + +### Parameter Interfaces + +#### RegisterParams +```typescript +interface RegisterParams { + email: string; // Required + password: string; // Required + turnstile_token?: string | null; // Optional: Cloudflare Turnstile for bot protection + referral_code?: string | null; // Optional: Referral code +} +``` + +#### VerifyOtpParams +```typescript +interface VerifyOtpParams { + email: string; // User's email + otpCode: string; // OTP code from email +} +``` + +#### ResetPasswordParams +```typescript +interface ResetPasswordParams { + resetToken: string; // Token from password reset email + newPassword: string; // New password to set +} +``` + +#### ChangePasswordParams +```typescript +interface ChangePasswordParams { + userId: string; // User ID + currentPassword: string; // Current password for verification + newPassword: string; // New password to set +} +``` + +### Provider Type +```typescript +type Provider = 'google' | 'microsoft' | 'facebook'; +``` + +--- + +## Methods + +### Module Interface +```typescript +interface AuthModule { + // User Info + me(): Promise; + updateMe(data: Partial>): Promise; + isAuthenticated(): Promise; + + // Login/Logout + loginViaEmailPassword(email: string, password: string, turnstileToken?: string): Promise; + loginWithProvider(provider: Provider, fromUrl?: string): void; + logout(redirectUrl?: string): void; + redirectToLogin(nextUrl: string): void; + + // Token Management + setToken(token: string, saveToStorage?: boolean): void; + + // Registration + register(params: RegisterParams): Promise; + verifyOtp(params: VerifyOtpParams): Promise; + resendOtp(email: string): Promise; + + // User Management + inviteUser(userEmail: string, role: string): Promise; + + // Password Management + resetPasswordRequest(email: string): Promise; + resetPassword(params: ResetPasswordParams): Promise; + changePassword(params: ChangePasswordParams): Promise; +} +``` + +### Method Reference Table + +| Method | Parameters | Return Type | Description | +|--------|-----------|-------------|-------------| +| `register()` | `params: RegisterParams` | `Promise` | Create new user account | +| `loginViaEmailPassword()` | `email: string, password: string, turnstileToken?: string` | `Promise` | Authenticate with email/password | +| `loginWithProvider()` | `provider: Provider, fromUrl?: string` | `void` | Initiate OAuth login flow | +| `me()` | None | `Promise` | Get current authenticated user | +| `updateMe()` | `data: Partial` | `Promise` | Update current user's profile | +| `logout()` | `redirectUrl?: string` | `void` | Clear session, optionally redirect | +| `redirectToLogin()` | `nextUrl: string` | `void` | ⚠️ **Avoid** - Prefer custom login UI with `loginViaEmailPassword()` or `loginWithProvider()` | +| `isAuthenticated()` | None | `Promise` | Check if user is logged in | +| `setToken()` | `token: string, saveToStorage?: boolean` | `void` | Manually set auth token | +| `inviteUser()` | `userEmail: string, role: string` | `Promise` | Send invitation email | +| `verifyOtp()` | `params: VerifyOtpParams` | `Promise` | Verify OTP code | +| `resendOtp()` | `email: string` | `Promise` | Resend OTP code | +| `resetPasswordRequest()` | `email: string` | `Promise` | Request password reset | +| `resetPassword()` | `params: ResetPasswordParams` | `Promise` | Reset password with token | +| `changePassword()` | `params: ChangePasswordParams` | `Promise` | Change user password | + +--- + +## Examples + +### Register New User (Complete Flow) + +Registration requires email verification before login. Complete flow: + +1. **Register** - Create the user account +2. **Verification email sent** - User receives an OTP code +3. **Verify OTP** - User enters code to verify email +4. **Login** - User can now log in + +```javascript +try { + // Step 1: Register the user + await base44.auth.register({ + email: "user@example.com", + password: "securePassword123", + referral_code: "OPTIONAL_CODE", // optional + turnstile_token: "CAPTCHA_TOKEN" // optional, for bot protection + }); + console.log('Registration successful. Check email for OTP code.'); + + // Step 2: User receives email with OTP code (e.g., "123456") + + // Step 3: Verify the OTP code + await base44.auth.verifyOtp({ + email: "user@example.com", + otpCode: "123456" // code from verification email + }); + console.log('Email verified successfully.'); + + // Step 4: Now the user can log in + const loginResponse = await base44.auth.loginViaEmailPassword( + "user@example.com", + "securePassword123" + ); + console.log('Login successful:', loginResponse.user); + +} catch (error) { + console.error('Registration flow failed:', error.message); + // Handle specific errors (see Error Handling section) +} +``` + +> **Important**: Users cannot log in until they complete OTP verification. Attempting to call `loginViaEmailPassword` before verification will fail. + +### Login with Email/Password + +```javascript +try { + const response = await base44.auth.loginViaEmailPassword( + "user@example.com", + "password123", + turnstileToken // optional: for bot protection + ); + + console.log('Login successful'); + console.log('User:', response.user); + console.log('Token:', response.access_token); + + // JWT is automatically stored for subsequent requests + +} catch (error) { + console.error('Login failed:', error.message); + if (error.status === 401) { + console.error('Invalid credentials'); + } else if (error.status === 403) { + console.error('Email not verified. Please check your email for OTP.'); + } +} +``` + +### Login with OAuth Provider + +```javascript +// Redirect to Google OAuth +base44.auth.loginWithProvider('google'); + +// Redirect to Google OAuth and return to current page after +base44.auth.loginWithProvider('google', window.location.href); + +// Supported providers: 'google', 'microsoft', 'facebook' +base44.auth.loginWithProvider('microsoft'); +``` + +### Get Current User + +```javascript +try { + const user = await base44.auth.me(); + + if (user) { + console.log('User ID:', user.id); + console.log('Email:', user.email); + console.log('Name:', user.full_name); + console.log('Role:', user.role); + console.log('Verified:', user.is_verified); + } else { + console.log('User not authenticated'); + } + +} catch (error) { + console.error('Failed to fetch user:', error.message); + if (error.status === 401) { + // Token expired or invalid - navigate to your custom login page + navigate('/login'); + } +} +``` + +### Update User Profile + +```javascript +try { + const updatedUser = await base44.auth.updateMe({ + full_name: "John Doe", + // Custom schema fields can be updated here + phone: "+1234567890", + preferences: { theme: "dark" } + }); + + console.log('Profile updated:', updatedUser); + +} catch (error) { + console.error('Profile update failed:', error.message); + if (error.status === 400) { + console.error('Invalid data provided'); + } else if (error.status === 401) { + console.error('Authentication required'); + navigate('/login'); + } +} +``` + +### Check Authentication Status + +```javascript +try { + const isLoggedIn = await base44.auth.isAuthenticated(); + + if (isLoggedIn) { + // Show authenticated UI + console.log('User is authenticated'); + } else { + // Show login button + console.log('User is not authenticated'); + } + +} catch (error) { + console.error('Failed to check authentication:', error.message); + // On error, treat as not authenticated +} +``` + +### Logout + +```javascript +// Simple logout +base44.auth.logout(); + +// Logout and redirect to goodbye page +base44.auth.logout("/goodbye"); + +// Logout and redirect to homepage +base44.auth.logout("/"); +``` + +### Protected Route Pattern + +```javascript +// Using a navigation function (e.g., React Router's useNavigate, Next.js router) +async function requireAuth(navigate) { + try { + const user = await base44.auth.me(); + + if (!user) { + // Navigate to your custom login page + navigate('/login', { state: { returnTo: window.location.pathname } }); + return null; + } + + return user; + + } catch (error) { + console.error('Authentication check failed:', error.message); + navigate('/login', { state: { returnTo: window.location.pathname } }); + return null; + } +} + +// Usage in your app +async function loadProtectedPage(navigate) { + const user = await requireAuth(navigate); + if (!user) { + // Will navigate to login + return; + } + + // Continue with authenticated logic + console.log('Welcome,', user.full_name); +} +``` + +### Set Authentication Token + +```javascript +// SECURITY WARNING: Never hardcode tokens or expose them in client code +// Tokens should only be received from secure authentication flows + +// Set token and save to localStorage (default) +base44.auth.setToken(receivedToken); + +// Set token without saving to localStorage (temporary session) +base44.auth.setToken(receivedToken, false); + +// Verify token was set +try { + const isAuthenticated = await base44.auth.isAuthenticated(); + if (!isAuthenticated) { + console.error('Token validation failed'); + } +} catch (error) { + console.error('Failed to set token:', error.message); +} +``` + +### Invite User to Application + +```javascript +try { + // Note: Typically requires admin privileges + const response = await base44.auth.inviteUser( + "newuser@example.com", + "user" // or "admin" + ); + + console.log('Invitation sent successfully'); + +} catch (error) { + console.error('Failed to invite user:', error.message); + if (error.status === 403) { + console.error('Insufficient permissions to invite users'); + } else if (error.status === 400) { + console.error('Invalid email or role'); + } +} +``` + +### OTP Verification + +```javascript +try { + // Verify OTP code sent to user's email + await base44.auth.verifyOtp({ + email: "user@example.com", + otpCode: "123456" + }); + + console.log('OTP verified successfully'); + +} catch (error) { + console.error('OTP verification failed:', error.message); + if (error.status === 400) { + console.error('Invalid or expired OTP code'); + } else if (error.status === 429) { + console.error('Too many attempts. Please try again later.'); + } +} + +// Resend OTP if needed +try { + await base44.auth.resendOtp("user@example.com"); + console.log('OTP resent successfully'); + +} catch (error) { + console.error('Failed to resend OTP:', error.message); + if (error.status === 429) { + console.error('Too many requests. Please wait before trying again.'); + } +} +``` + +### Password Reset Flow + +```javascript +// Step 1: Request password reset +try { + await base44.auth.resetPasswordRequest("user@example.com"); + console.log('Password reset email sent. Check your inbox.'); + +} catch (error) { + console.error('Password reset request failed:', error.message); + if (error.status === 429) { + console.error('Too many requests. Please try again later.'); + } + // Note: For security, don't reveal if email exists +} + +// Step 2: Reset password with token from email +try { + await base44.auth.resetPassword({ + resetToken: "token-from-email", + newPassword: "newSecurePassword123" + }); + + console.log('Password reset successfully. You can now log in.'); + +} catch (error) { + console.error('Password reset failed:', error.message); + if (error.status === 400) { + console.error('Invalid or expired reset token'); + } else if (error.status === 422) { + console.error('Password does not meet requirements'); + } +} +``` + +### Change Password + +```javascript +try { + const currentUser = await base44.auth.me(); + + if (!currentUser) { + throw new Error('User must be authenticated to change password'); + } + + await base44.auth.changePassword({ + userId: currentUser.id, + currentPassword: "oldPassword123", + newPassword: "newSecurePassword456" + }); + + console.log('Password changed successfully'); + +} catch (error) { + console.error('Password change failed:', error.message); + if (error.status === 401) { + console.error('Current password is incorrect'); + } else if (error.status === 422) { + console.error('New password does not meet requirements'); + } else if (error.status === 403) { + console.error('Not authorized to change this password'); + } +} +``` + +--- + +## Error Handling + +### Common Error Scenarios + +The auth module can throw various errors. Here are common scenarios and how to handle them: + +#### Authentication Errors (401/403) +```javascript +try { + const user = await base44.auth.me(); +} catch (error) { + if (error.status === 401) { + // Token expired or invalid - navigate to your custom login page + navigate('/login'); + } else if (error.status === 403) { + // Email not verified or insufficient permissions + console.error('Access denied:', error.message); + } +} +``` + +#### Validation Errors (400/422) +```javascript +try { + await base44.auth.register({ + email: "invalid-email", + password: "weak" + }); +} catch (error) { + if (error.status === 400) { + console.error('Invalid input:', error.message); + // Handle validation errors + } else if (error.status === 422) { + console.error('Data validation failed:', error.details); + } +} +``` + +#### Rate Limiting (429) +```javascript +try { + await base44.auth.resendOtp("user@example.com"); +} catch (error) { + if (error.status === 429) { + console.error('Too many requests. Please wait before trying again.'); + // Show countdown or disable button temporarily + } +} +``` + +#### Generic Error Handler +```javascript +function handleAuthError(error) { + switch (error.status) { + case 400: + return 'Invalid input. Please check your data.'; + case 401: + return 'Authentication required. Redirecting to login...'; + case 403: + return 'Access denied. You may need to verify your email.'; + case 404: + return 'Resource not found.'; + case 422: + return 'Validation failed. Please check your input.'; + case 429: + return 'Too many requests. Please try again later.'; + case 500: + return 'Server error. Please try again.'; + default: + return 'An unexpected error occurred.'; + } +} + +// Usage +try { + await base44.auth.loginViaEmailPassword(email, password); +} catch (error) { + console.error(handleAuthError(error)); +} +``` + +--- + +## Auth Providers + +Configure authentication providers in your app dashboard: + +### Available Providers + +**Built-in (All Plans):** +- **Email/Password** - Default, always enabled +- **Google** - OAuth authentication +- **Microsoft** - OAuth authentication +- **Facebook** - OAuth authentication + +**SSO Providers (Elite Plan):** +- **Okta** +- **Azure AD** +- **GitHub** + +### Using OAuth Providers + +```javascript +// Initiate OAuth login flow +base44.auth.loginWithProvider('google'); + +// Return to specific page after authentication +base44.auth.loginWithProvider('microsoft', '/dashboard'); + +// Supported values: 'google', 'microsoft', 'facebook' +``` + +--- + +## Environment Availability + +| Environment | Availability | Notes | +|------------|--------------|-------| +| **Frontend** | ✅ Yes | All methods available | +| **Backend Functions** | ✅ Yes | Use `createClientFromRequest(req)` for authenticated client | +| **Service Role** | ❌ No | Auth methods not available in service role context | + +### Frontend Usage +```javascript +// Standard browser usage +const user = await base44.auth.me(); +``` + +### Backend Functions Usage +```javascript +import { createClientFromRequest } from "@base44/sdk"; + +Deno.serve(async (req) => { + const base44 = createClientFromRequest(req); + + // Get user from request context + const user = await base44.auth.me(); + + // User operations here... + return Response.json({ user }); +}); +``` + +--- + +## App Visibility + +Control who can access your app in the app settings: + +### Public Apps +- No login required for basic access +- Users can view public content without authentication +- Authenticated users get additional features/data + +### Private Apps +- Login required to access any content +- Unauthenticated users are automatically redirected to login +- All content is protected by default + +--- + +## Limitations + +### Authentication UI Options +- **Recommended:** Build custom login/signup UI using `loginViaEmailPassword()` and `loginWithProvider()` for full control over user experience and branding +- **Alternative:** `redirectToLogin()` uses Base44's hosted authentication pages with limited customization + +### Hosted Login (via redirectToLogin) +- `redirectToLogin()` shows both login and signup options on the same page +- No separate `redirectToSignup()` method +- Users can switch between login/signup on the hosted page +- ⚠️ **Note:** Prefer building custom login UI for better user experience + +### Password Requirements +- Minimum length and complexity requirements enforced +- Requirements not exposed via API +- Validation errors returned when requirements not met + +### Rate Limiting +- OTP requests are rate-limited to prevent abuse +- Password reset requests are rate-limited +- Login attempts may be rate-limited with Turnstile protection + +### Token Management +- JWTs are automatically stored in localStorage by default +- Token expiration and refresh not exposed in API +- Call `me()` or `isAuthenticated()` to verify token validity + +--- + +## Best Practices + +### 1. Always Handle Errors +```javascript +try { + await base44.auth.loginViaEmailPassword(email, password); +} catch (error) { + // Always handle authentication errors + console.error('Login failed:', error.message); +} +``` + +### 2. Verify Authentication Before Protected Actions +```javascript +const user = await requireAuth(); +if (!user) return; // Will redirect to login +// Proceed with authenticated action +``` + +### 3. Use Type Safety with TypeScript +```typescript +import type { User, LoginResponse } from '@base44/sdk'; + +const user: User = await base44.auth.me(); +const response: LoginResponse = await base44.auth.loginViaEmailPassword(email, password); +``` + +### 4. Don't Hardcode Credentials +```javascript +// ❌ BAD +base44.auth.setToken("hardcoded-token-here"); + +// ✅ GOOD +const token = await secureAuthFlow(); +base44.auth.setToken(token); +``` + +### 5. Provide User Feedback +```javascript +try { + await base44.auth.register(params); + showSuccessMessage('Registration successful! Check your email for verification code.'); +} catch (error) { + showErrorMessage('Registration failed: ' + error.message); +} +``` + +### 6. Handle Token Expiration Gracefully +```javascript +try { + const user = await base44.auth.me(); +} catch (error) { + if (error.status === 401) { + // Clear local state and navigate to login + localStorage.clear(); + navigate('/login'); + } +} +``` + +### 7. Build Custom Login UI (Recommended) +```javascript +// ✅ RECOMMENDED - Custom login form with direct methods +const handleLogin = async (email, password) => { + try { + const { user } = await base44.auth.loginViaEmailPassword(email, password); + navigate('/dashboard'); + } catch (error) { + setError(error.message); + } +}; + +const handleGoogleLogin = () => { + base44.auth.loginWithProvider('google', '/dashboard'); +}; + +// ❌ AVOID - Redirecting to hosted login page +// base44.auth.redirectToLogin(window.location.href); +``` diff --git a/evals/fixtures/react-task-manager/skills/base44-sdk/references/base44-agents.md b/evals/fixtures/react-task-manager/skills/base44-sdk/references/base44-agents.md new file mode 100644 index 0000000..ee55dee --- /dev/null +++ b/evals/fixtures/react-task-manager/skills/base44-sdk/references/base44-agents.md @@ -0,0 +1,364 @@ +# Agents Module + +AI agent conversations and messages via `base44.agents`. + +> **Note:** This module requires a logged-in user. All agent methods work in the context of the authenticated user. + +## Contents +- [Concepts](#concepts) +- [Methods](#methods) +- [Examples](#examples) (Create, Get Conversations, List, Subscribe, Send Message, WhatsApp) +- [Message Structure](#message-structure) +- [Conversation Structure](#conversation-structure) +- [Common Patterns](#common-patterns) + +## Concepts + +- **Conversation**: A dialogue between user and an AI agent. Has unique ID, agent name, user reference, and metadata. +- **Message**: Single message in a conversation. Has role (`user`, `assistant`, `system`), content, timestamps, and optional metadata. + +## Methods + +| Method | Signature | Description | +|--------|-----------|-------------| +| `createConversation(params)` | `Promise` | Create a new conversation with an agent | +| `getConversations()` | `Promise` | Get all user's conversations | +| `getConversation(id)` | `Promise` | Get conversation with messages | +| `listConversations(filterParams)` | `Promise` | Filter/sort/paginate conversations | +| `subscribeToConversation(id, onUpdate?)` | `() => void` | Real-time updates via WebSocket (returns unsubscribe function) | +| `addMessage(conversation, message)` | `Promise` | Send a message | +| `getWhatsAppConnectURL(agentName)` | `string` | Get WhatsApp connection URL for agent | + +## Examples + +### Create Conversation + +```javascript +const conversation = await base44.agents.createConversation({ + agent_name: "support-agent", + metadata: { + order_id: "ORD-123", + category: "billing" + } +}); + +console.log(conversation.id); +``` + +### Get All Conversations + +```javascript +const conversations = await base44.agents.getConversations(); + +conversations.forEach(conv => { + console.log(conv.id, conv.agent_name, conv.created_date); +}); +``` + +### Get Single Conversation (with messages) + +```javascript +const conversation = await base44.agents.getConversation("conv-id-123"); + +console.log(conversation.messages); +``` + +### List with Filters + +```javascript +// Using filterParams object with q, sort, limit, skip, fields +const recent = await base44.agents.listConversations({ + q: { agent_name: "support-agent" }, + sort: "-created_date", + limit: 10, + skip: 0 +}); + +// Filter by metadata +const highPriority = await base44.agents.listConversations({ + q: { + agent_name: "support-agent", + "metadata.priority": "high" + }, + sort: "-updated_date", + limit: 20 +}); +``` + +### Subscribe to Updates (Real-time) + +```javascript +const unsubscribe = base44.agents.subscribeToConversation( + "conv-id-123", + (updatedConversation) => { + // Called when new messages arrive + console.log("New messages:", updatedConversation.messages); + } +); + +// Later: unsubscribe +unsubscribe(); +``` + +### Send a Message + +```javascript +const conversation = await base44.agents.getConversation("conv-id-123"); + +await base44.agents.addMessage(conversation, { + role: "user", + content: "What's the weather like today?" +}); +``` + +### Get WhatsApp Connection URL + +```javascript +const whatsappUrl = base44.agents.getWhatsAppConnectURL("support-agent"); +// Returns URL for users to connect with agent via WhatsApp +console.log(whatsappUrl); +``` + +## Message Structure + +```javascript +{ + role: "user" | "assistant" | "system", + content: "Message text or structured object", + created_date: "2024-01-15T10:30:00Z", + updated_date: "2024-01-15T10:30:00Z", + + // Optional fields + reasoning: { + content: "Agent's reasoning process", + timing: 1500 + }, + tool_calls: [{ + name: "search", + arguments: { query: "weather" }, + result: { ... }, + status: "success" + }], + file_urls: ["https://..."], + usage: { + prompt_tokens: 150, + completion_tokens: 50 + }, + metadata: { ... }, + custom_context: { ... } +} +``` + +## Conversation Structure + +```javascript +{ + id: "conv-id-123", + app_id: "app-id", + agent_name: "support-agent", + created_by_id: "user-id", + created_date: "2024-01-15T10:00:00Z", + updated_date: "2024-01-15T10:30:00Z", + messages: [ ... ], + metadata: { ... } +} +``` + +## Common Patterns + +### Chat Interface + +```javascript +// Load conversation +const conv = await base44.agents.getConversation(conversationId); +setMessages(conv.messages); + +// Subscribe to updates +const unsubscribe = base44.agents.subscribeToConversation(conversationId, (updated) => { + setMessages(updated.messages); +}); + +// Send message +async function sendMessage(text) { + await base44.agents.addMessage(conv, { role: "user", content: text }); +} + +// Cleanup on unmount +return () => unsubscribe(); +``` + +## Type Definitions + +### AgentConversation + +```typescript +/** An agent conversation containing messages exchanged with an AI agent. */ +interface AgentConversation { + /** Unique identifier for the conversation. */ + id: string; + /** Application ID. */ + app_id: string; + /** Name of the agent in this conversation. */ + agent_name: string; + /** ID of the user who created the conversation. */ + created_by_id: string; + /** When the conversation was created. */ + created_date: string; + /** When the conversation was last updated. */ + updated_date: string; + /** Array of messages in the conversation. */ + messages: AgentMessage[]; + /** Optional metadata associated with the conversation. */ + metadata?: Record; +} +``` + +### AgentMessage + +```typescript +/** A message in an agent conversation. */ +interface AgentMessage { + /** Unique identifier for the message. */ + id: string; + /** Role of the message sender. */ + role: "user" | "assistant" | "system"; + /** When the message was created. */ + created_date: string; + /** When the message was last updated. */ + updated_date: string; + /** Message content. */ + content?: string | Record; + /** Optional reasoning information for the message. */ + reasoning?: AgentMessageReasoning | null; + /** URLs to files attached to the message. */ + file_urls?: string[]; + /** Tool calls made by the agent. */ + tool_calls?: AgentMessageToolCall[]; + /** Token usage statistics. */ + usage?: AgentMessageUsage; + /** Whether the message is hidden from the user. */ + hidden?: boolean; + /** Custom context provided with the message. */ + custom_context?: AgentMessageCustomContext[]; + /** Model used to generate the message. */ + model?: string; + /** Checkpoint ID for the message. */ + checkpoint_id?: string; + /** Metadata about when and by whom the message was created. */ + metadata?: AgentMessageMetadata; +} +``` + +### Supporting Types + +```typescript +/** Reasoning information for an agent message. */ +interface AgentMessageReasoning { + /** When reasoning started. */ + start_date: string; + /** When reasoning ended. */ + end_date?: string; + /** Reasoning content. */ + content: string; +} + +/** A tool call made by the agent. */ +interface AgentMessageToolCall { + /** Tool call ID. */ + id: string; + /** Name of the tool called. */ + name: string; + /** Arguments passed to the tool as JSON string. */ + arguments_string: string; + /** Status of the tool call. */ + status: "running" | "success" | "error" | "stopped"; + /** Results from the tool call. */ + results?: string; +} + +/** Token usage statistics for an agent message. */ +interface AgentMessageUsage { + /** Number of tokens in the prompt. */ + prompt_tokens?: number; + /** Number of tokens in the completion. */ + completion_tokens?: number; +} + +/** Custom context provided with an agent message. */ +interface AgentMessageCustomContext { + /** Context message. */ + message: string; + /** Associated data for the context. */ + data: Record; + /** Type of context. */ + type: string; +} + +/** Metadata about when and by whom a message was created. */ +interface AgentMessageMetadata { + /** When the message was created. */ + created_date: string; + /** Email of the user who created the message. */ + created_by_email: string; + /** Full name of the user who created the message. */ + created_by_full_name: string; +} +``` + +### CreateConversationParams + +```typescript +/** Parameters for creating a new conversation. */ +interface CreateConversationParams { + /** The name of the agent to create a conversation with. */ + agent_name: string; + /** Optional metadata to attach to the conversation. */ + metadata?: Record; +} +``` + +### ModelFilterParams + +```typescript +/** Parameters for filtering, sorting, and paginating conversations. */ +interface ModelFilterParams { + /** Query object with field-value pairs for filtering. */ + q?: Record; + /** Sort parameter (e.g., "-created_date" for descending). */ + sort?: string | null; + /** Maximum number of results to return. */ + limit?: number | null; + /** Number of results to skip for pagination. */ + skip?: number | null; + /** Array of field names to include in the response. */ + fields?: string[] | null; +} +``` + +### AgentsModule + +```typescript +/** Agents module for managing AI agent conversations. */ +interface AgentsModule { + /** Gets all conversations from all agents in the app. */ + getConversations(): Promise; + + /** Gets a specific conversation by ID. */ + getConversation(conversationId: string): Promise; + + /** Lists conversations with filtering, sorting, and pagination. */ + listConversations(filterParams: ModelFilterParams): Promise; + + /** Creates a new conversation with an agent. */ + createConversation(conversation: CreateConversationParams): Promise; + + /** Adds a message to a conversation. */ + addMessage(conversation: AgentConversation, message: Partial): Promise; + + /** Subscribes to real-time updates for a conversation. Returns unsubscribe function. */ + subscribeToConversation(conversationId: string, onUpdate?: (conversation: AgentConversation) => void): () => void; + + /** Gets WhatsApp connection URL for an agent. */ + getWhatsAppConnectURL(agentName: string): string; +} +``` diff --git a/evals/fixtures/react-task-manager/skills/base44-sdk/references/client.md b/evals/fixtures/react-task-manager/skills/base44-sdk/references/client.md new file mode 100644 index 0000000..554bf2b --- /dev/null +++ b/evals/fixtures/react-task-manager/skills/base44-sdk/references/client.md @@ -0,0 +1,257 @@ +# Client Setup + +How to create and configure the Base44 client. + +## Contents +- [In Base44-Generated Apps](#in-base44-generated-apps) +- [In External Apps](#in-external-apps) +- [In Backend Functions](#in-backend-functions) +- [Authentication Modes](#authentication-modes) (Anonymous, User, Service Role) +- [Available Modules](#available-modules) +- [Client Methods](#client-methods) +- [Client Configuration Options](#client-configuration-options) + +## In Base44-Generated Apps + +The client is pre-configured and available as `base44`. Just use it: + +```javascript +const tasks = await base44.entities.Task.list(); +``` + +## In External Apps + +Install the SDK and create a client: + +```bash +npm install @base44/sdk +``` + +```javascript +import { createClient } from "@base44/sdk"; + +// IMPORTANT: The parameter name is 'appId' (NOT 'clientId', NOT 'id') +// IMPORTANT: onError must be nested inside 'options' object +const base44 = createClient({ + appId: "your-app-id", // Required: Use 'appId' parameter + token: "optional-user-token", // Optional: for pre-authenticated requests + options: { // Optional: configuration options + onError: (error) => { // Optional: error handler (must be in options) + console.error("Base44 error:", error); + } + } +}); +``` + +**Common Mistakes:** +- ❌ `createClient({ clientId: "..." })` - WRONG parameter name +- ❌ `createClient({ id: "..." })` - WRONG parameter name +- ❌ `createClient({ appId: "...", onError: ... })` - WRONG: onError must be in options +- ✅ `createClient({ appId: "..." })` - CORRECT parameter name +- ✅ `createClient({ appId: "...", options: { onError: ... } })` - CORRECT: onError in options + +## In Backend Functions + +Use `createClientFromRequest` to get a client with the caller's auth context: + +```javascript +import { createClientFromRequest } from "@base44/sdk"; + +Deno.serve(async (req) => { + const base44 = createClientFromRequest(req); + + // Client inherits authentication from the request + const user = await base44.auth.me(); + + return Response.json({ user }); +}); +``` + +## Authentication Modes + +| Mode | How to Get | Permissions | +|------|-----------|-------------| +| **Anonymous** | `createClient({ appId })` without token | Public data only | +| **User** | After `loginViaEmailPassword()` or via `createClientFromRequest` | User's own data | +| **Service Role** | `base44.asServiceRole.*` in backend | Full admin access | + +## Anonymous Mode + +No authentication. Can only access public resources. + +```javascript +const base44 = createClient({ appId: "your-app-id" }); + +// Only works if Task entity allows anonymous read +const publicTasks = await base44.entities.Task.list(); +``` + +## User Mode + +After user logs in, the client automatically includes their token. + +```javascript +const base44 = createClient({ appId: "your-app-id" }); + +// Login sets the token +await base44.auth.loginViaEmailPassword("user@example.com", "password"); + +// Subsequent requests are authenticated +const user = await base44.auth.me(); +const myTasks = await base44.entities.Task.list(); // filtered by permissions +``` + +## Service Role Mode + +Admin-level access. **Backend only.** + +```javascript +// Inside a backend function +Deno.serve(async (req) => { + const base44 = createClientFromRequest(req); + + // User mode - respects permissions + const myTasks = await base44.entities.Task.list(); + + // Service role - bypasses permissions + const allTasks = await base44.asServiceRole.entities.Task.list(); + const allUsers = await base44.asServiceRole.entities.User.list(); + const oauthToken = await base44.asServiceRole.connectors.getAccessToken("slack"); + + return Response.json({ myTasks, allTasks }); +}); +``` + +## Available Modules + +The client exposes these modules: + +```javascript +base44.entities // CRUD operations +base44.auth // Authentication +base44.agents // AI conversations +base44.functions // Backend function invocation +base44.integrations // Third-party services +base44.analytics // Event tracking +base44.appLogs // App usage logging +base44.users // User invitations + +// Service role only (backend) +base44.asServiceRole.entities +base44.asServiceRole.agents +base44.asServiceRole.functions +base44.asServiceRole.integrations +base44.asServiceRole.appLogs +base44.asServiceRole.connectors +``` + +## Client Methods + +The client provides these methods: + +```javascript +// Set authentication token for all subsequent requests +base44.setToken(newToken); + +// Cleanup WebSocket connections (call when done with client) +base44.cleanup(); +``` + +### setToken + +Updates the authentication token for all subsequent API requests and WebSocket connections. + +```javascript +// After receiving a token (e.g., from external auth) +base44.setToken("new-jwt-token"); +``` + +### cleanup + +Disconnects WebSocket connections. Call when you're done with the client or when the component unmounts. + +```javascript +// Cleanup on component unmount (React example) +useEffect(() => { + return () => base44.cleanup(); +}, []); +``` + +## Client Configuration Options + +```javascript +createClient({ + appId: "your-app-id", // Required: MUST use 'appId' (not 'clientId' or 'id') + token: "jwt-token", // Optional: pre-set auth token + options: { // Optional: configuration options + onError: (error) => {} // Optional: global error handler (must be in options) + } +}); +``` + +**⚠️ Critical:** +- The parameter name is `appId`, not `clientId` or `id`. Using the wrong parameter name will cause errors. +- The `onError` handler must be nested inside the `options` object, not at the top level. + +## Type Definitions + +### CreateClientConfig + +```typescript +/** Configuration for creating a Base44 client. */ +interface CreateClientConfig { + /** The Base44 app ID (required). */ + appId: string; + /** User authentication token. Used to authenticate as a specific user. */ + token?: string; + /** Service role authentication token (backend only). */ + serviceToken?: string; + /** Additional client options. */ + options?: CreateClientOptions; +} + +/** Options for creating a Base44 client. */ +interface CreateClientOptions { + /** Optional error handler called whenever an API error occurs. */ + onError?: (error: Error) => void; +} +``` + +### Base44Client + +```typescript +/** The Base44 client instance. */ +interface Base44Client { + /** Entities module for CRUD operations on your data models. */ + entities: EntitiesModule; + /** Integrations module for calling pre-built integration endpoints. */ + integrations: IntegrationsModule; + /** Auth module for user authentication and management. */ + auth: AuthModule; + /** Functions module for invoking custom backend functions. */ + functions: FunctionsModule; + /** Agents module for managing AI agent conversations. */ + agents: AgentsModule; + /** App logs module for tracking app usage. */ + appLogs: AppLogsModule; + /** Analytics module for tracking custom events. */ + analytics: AnalyticsModule; + + /** Cleanup function to disconnect WebSocket connections. */ + cleanup(): void; + + /** Sets a new authentication token for all subsequent requests. */ + setToken(newToken: string): void; + + /** Provides access to modules with elevated service role permissions (backend only). */ + readonly asServiceRole: { + entities: EntitiesModule; + integrations: IntegrationsModule; + connectors: ConnectorsModule; + functions: FunctionsModule; + agents: AgentsModule; + appLogs: AppLogsModule; + cleanup(): void; + }; +} +``` diff --git a/evals/fixtures/react-task-manager/skills/base44-sdk/references/connectors.md b/evals/fixtures/react-task-manager/skills/base44-sdk/references/connectors.md new file mode 100644 index 0000000..f3fbcb4 --- /dev/null +++ b/evals/fixtures/react-task-manager/skills/base44-sdk/references/connectors.md @@ -0,0 +1,113 @@ +# Connectors Module + +OAuth token management for external services. **Service role only** (backend functions). + +## Method + +```javascript +base44.asServiceRole.connectors.getAccessToken(integrationType): Promise +``` + +Returns a raw OAuth access token for the specified service. + +## Supported Services + +| Integration Type | Service | +|-----------------|---------| +| `"googlecalendar"` | Google Calendar | +| `"googledrive"` | Google Drive | +| `"slack"` | Slack | +| `"notion"` | Notion | +| `"salesforce"` | Salesforce | +| `"hubspot"` | HubSpot | +| `"linkedin"` | LinkedIn | +| `"tiktok"` | TikTok | +| `"github"` | GitHub | + +## Example Usage + +```javascript +// Backend function only +Deno.serve(async (req) => { + const base44 = createClientFromRequest(req); + + // Get OAuth token for Slack + const slackToken = await base44.asServiceRole.connectors.getAccessToken("slack"); + + // Use token directly with Slack API + const response = await fetch("https://slack.com/api/chat.postMessage", { + method: "POST", + headers: { + "Authorization": `Bearer ${slackToken}`, + "Content-Type": "application/json" + }, + body: JSON.stringify({ + channel: "#general", + text: "Hello from Base44!" + }) + }); + + return Response.json(await response.json()); +}); +``` + +## Google Calendar Example + +```javascript +Deno.serve(async (req) => { + const base44 = createClientFromRequest(req); + + const token = await base44.asServiceRole.connectors.getAccessToken("googlecalendar"); + + // List upcoming events + const response = await fetch( + "https://www.googleapis.com/calendar/v3/calendars/primary/events?" + + new URLSearchParams({ + maxResults: "10", + orderBy: "startTime", + singleEvents: "true", + timeMin: new Date().toISOString() + }), + { + headers: { "Authorization": `Bearer ${token}` } + } + ); + + const events = await response.json(); + return Response.json(events); +}); +``` + +## Setup Requirements + +1. **Builder plan** or higher +2. **Backend functions** enabled +3. **Connector configured** in Base44 dashboard (OAuth flow completed) + +## Important Notes + +- **One account per connector per app**: All users share the same connected account +- **Backend only**: `connectors` module not available in frontend code +- **Service role required**: Must use `base44.asServiceRole.connectors` +- **You handle the API calls**: Base44 only provides the token; you make the actual API requests +- **Token refresh**: Base44 handles token refresh automatically + +## Type Definitions + +```typescript +/** + * The type of external integration/connector. + * Examples: 'googlecalendar', 'slack', 'github', 'notion', etc. + */ +type ConnectorIntegrationType = string; + +/** Connectors module for managing OAuth tokens for external services. */ +interface ConnectorsModule { + /** + * Retrieves an OAuth access token for a specific external integration type. + * @param integrationType - The type of integration (e.g., 'googlecalendar', 'slack'). + * @returns Promise resolving to the access token string. + */ + getAccessToken(integrationType: ConnectorIntegrationType): Promise; +} +``` diff --git a/evals/fixtures/react-task-manager/skills/base44-sdk/references/entities.md b/evals/fixtures/react-task-manager/skills/base44-sdk/references/entities.md new file mode 100644 index 0000000..a7bdf35 --- /dev/null +++ b/evals/fixtures/react-task-manager/skills/base44-sdk/references/entities.md @@ -0,0 +1,253 @@ +# Entities Module + +CRUD operations on data models. Access via `base44.entities.EntityName.method()`. + +## Contents +- [Methods](#methods) +- [Examples](#examples) (Create, Bulk Create, List, Filter, Get, Update, Delete, Subscribe) +- [User Entity](#user-entity) +- [Service Role Access](#service-role-access) +- [Permissions](#permissions) + +## Methods + +| Method | Signature | Description | +|--------|-----------|-------------| +| `create(data)` | `Promise` | Create one record | +| `bulkCreate(dataArray)` | `Promise` | Create multiple records | +| `list(sort?, limit?, skip?, fields?)` | `Promise` | Get all records (paginated) | +| `filter(query, sort?, limit?, skip?, fields?)` | `Promise` | Get records matching conditions | +| `get(id)` | `Promise` | Get single record by ID | +| `update(id, data)` | `Promise` | Update record (partial update) | +| `delete(id)` | `Promise` | Delete record by ID | +| `deleteMany(query)` | `Promise` | Delete all matching records | +| `importEntities(file)` | `Promise` | Import from CSV (frontend only) | +| `subscribe(callback)` | `() => void` | Subscribe to realtime updates (returns unsubscribe function) | + +## Examples + +### Create + +```javascript +const task = await base44.entities.Task.create({ + title: "Complete documentation", + status: "pending", + dueDate: "2024-12-31" +}); +``` + +### Bulk Create + +```javascript +const tasks = await base44.entities.Task.bulkCreate([ + { title: "Task 1", status: "pending" }, + { title: "Task 2", status: "pending" } +]); +``` + +### List with Pagination + +```javascript +// Get first 10 records, sorted by created_date descending +const tasks = await base44.entities.Task.list( + "-created_date", // sort (string: prefix with - for descending) + 10, // limit + 0 // skip +); + +// Get next page +const page2 = await base44.entities.Task.list("-created_date", 10, 10); +``` + +### Filter + +```javascript +// Simple filter +const pending = await base44.entities.Task.filter({ status: "pending" }); + +// Multiple conditions +const myPending = await base44.entities.Task.filter({ + status: "pending", + assignedTo: userId +}); + +// With sort, limit, skip +const recent = await base44.entities.Task.filter( + { status: "pending" }, + "-created_date", // sort (string: prefix with - for descending) + 5, + 0 +); + +// Select specific fields +const titles = await base44.entities.Task.filter( + { status: "pending" }, + null, + null, + null, + ["id", "title"] +); +``` + +### Get by ID + +```javascript +const task = await base44.entities.Task.get("task-id-123"); +``` + +### Update + +```javascript +// Partial update - only specified fields change +await base44.entities.Task.update("task-id-123", { + status: "completed", + completedAt: new Date().toISOString() +}); +``` + +### Delete + +```javascript +// Single record +await base44.entities.Task.delete("task-id-123"); + +// Multiple records matching query +await base44.entities.Task.deleteMany({ status: "archived" }); +``` + +### Subscribe to Realtime Updates + +```javascript +// Subscribe to all changes on Task entity +const unsubscribe = base44.entities.Task.subscribe((event) => { + console.log(`Task ${event.id} was ${event.type}:`, event.data); + // event.type is "create", "update", or "delete" +}); + +// Later: unsubscribe to stop receiving updates +unsubscribe(); +``` + +**Event structure:** +```javascript +{ + type: "create" | "update" | "delete", + data: { ... }, // the entity data + id: "entity-id", // the affected entity's ID + timestamp: "2024-01-15T10:30:00Z" +} +``` + +## User Entity + +Every app has a built-in `User` entity with special rules: + +- Regular users can only read/update **their own** record +- Cannot create users via `entities.create()` - use `auth.register()` instead +- Service role has full access to all user records + +```javascript +// Get current user's record +const me = await base44.entities.User.get(currentUserId); + +// Service role: get any user +const anyUser = await base44.asServiceRole.entities.User.get(userId); +``` + +## Service Role Access + +For admin-level operations (bypass user permissions): + +```javascript +// Backend only +const allTasks = await base44.asServiceRole.entities.Task.list(); +const allUsers = await base44.asServiceRole.entities.User.list(); +``` + +## Permissions (RLS & FLS) + +Data access is controlled by **Row Level Security (RLS)** and **Field Level Security (FLS)** rules defined in entity schemas. + +1. **Authentication level**: anonymous, authenticated, or service role +2. **RLS rules**: Control which records (rows) users can create/read/update/delete +3. **FLS rules**: Control which fields users can read/write within accessible records + +Operations succeed or fail based on these rules - no partial results. + +RLS and FLS are configured in entity schema files (`base44/entities/*.jsonc`). See [entities-create.md](../../base44-cli/references/entities-create.md#row-level-security-rls) for configuration details. + +**Note:** `asServiceRole` sets the user's role to `"admin"` but does NOT bypass RLS. Your RLS rules must include admin access (e.g., `{ "user_condition": { "role": "admin" } }`) for service role operations to succeed. + +## Type Definitions + +### RealtimeEvent + +```typescript +/** Event types for realtime entity updates. */ +type RealtimeEventType = "create" | "update" | "delete"; + +/** Payload received when a realtime event occurs. */ +interface RealtimeEvent { + /** The type of change that occurred. */ + type: RealtimeEventType; + /** The entity data. */ + data: any; + /** The unique identifier of the affected entity. */ + id: string; + /** ISO 8601 timestamp of when the event occurred. */ + timestamp: string; +} + +/** Callback function invoked when a realtime event occurs. */ +type RealtimeCallback = (event: RealtimeEvent) => void; + +/** Function returned from subscribe, call it to unsubscribe. */ +type Subscription = () => void; +``` + +### EntityHandler + +```typescript +/** Entity handler providing CRUD operations for a specific entity type. */ +interface EntityHandler { + /** Lists records with optional pagination and sorting. */ + list(sort?: string, limit?: number, skip?: number, fields?: string[]): Promise; + + /** Filters records based on a query. */ + filter(query: Record, sort?: string, limit?: number, skip?: number, fields?: string[]): Promise; + + /** Gets a single record by ID. */ + get(id: string): Promise; + + /** Creates a new record. */ + create(data: Record): Promise; + + /** Updates an existing record. */ + update(id: string, data: Record): Promise; + + /** Deletes a single record by ID. */ + delete(id: string): Promise; + + /** Deletes multiple records matching a query. */ + deleteMany(query: Record): Promise; + + /** Creates multiple records in a single request. */ + bulkCreate(data: Record[]): Promise; + + /** Imports records from a file (frontend only). */ + importEntities(file: File): Promise; + + /** Subscribes to realtime updates. Returns unsubscribe function. */ + subscribe(callback: RealtimeCallback): Subscription; +} +``` + +### EntitiesModule + +```typescript +/** Entities module for managing app data. */ +interface EntitiesModule { + /** Access any entity by name dynamically. */ + [entityName: string]: EntityHandler; +} +``` diff --git a/evals/fixtures/react-task-manager/skills/base44-sdk/references/functions.md b/evals/fixtures/react-task-manager/skills/base44-sdk/references/functions.md new file mode 100644 index 0000000..8072465 --- /dev/null +++ b/evals/fixtures/react-task-manager/skills/base44-sdk/references/functions.md @@ -0,0 +1,216 @@ +# Functions Module + +Invoke custom backend functions via `base44.functions`. + +## Contents +- [Method](#method) +- [Invoking Functions](#invoking-functions) (Frontend, File Upload, Service Role, REST API) +- [Writing Backend Functions](#writing-backend-functions) (Basic, Service Role, Secrets, Errors) +- [Setup Requirements](#setup-requirements) +- [Authentication Modes](#authentication-modes) + +## Method + +```javascript +base44.functions.invoke(functionName, data): Promise +``` + +- `functionName`: Name of the backend function +- `data`: Object of parameters (sent as JSON, or multipart if contains File objects) +- Returns: Whatever the function returns + +## Invoking Functions + +### From Frontend + +```javascript +const result = await base44.functions.invoke("processOrder", { + orderId: "order-123", + action: "ship" +}); + +console.log(result); +``` + +### With File Upload + +```javascript +const fileInput = document.querySelector('input[type="file"]'); +const file = fileInput.files[0]; + +// Automatically uses multipart/form-data when File objects present +const result = await base44.functions.invoke("uploadDocument", { + file: file, + category: "invoices" +}); +``` + +### With Service Role (Backend) + +```javascript +// Inside another backend function +const result = await base44.asServiceRole.functions.invoke("adminTask", { + userId: "user-123" +}); +``` + +### Via REST API (curl) + +Functions can be called via HTTP POST to your app domain: + +```bash +curl -X POST "https:///functions/" \ + -H "Content-Type: application/json" \ + -d '{"key": "value"}' +``` + +## Writing Backend Functions + +Backend functions run on Deno. Must export using `Deno.serve()`. + +### Required Directory Structure + +Each function must be in its own subdirectory under `base44/functions/` with a configuration file: + +``` +base44/ + functions/ + process-order/ # kebab-case directory name + function.jsonc # required configuration + index.ts # entry point +``` + +**function.jsonc:** +```jsonc +{ + "name": "process-order", + "entry": "index.ts" +} +``` + +For complete setup and deployment instructions, see [functions-create.md](../../base44-cli/references/functions-create.md) in base44-cli. + +### Basic Structure + +```javascript +// base44/functions/process-order/index.ts +import { createClientFromRequest } from "npm:@base44/sdk"; + +Deno.serve(async (req) => { + // Get authenticated client from request + const base44 = createClientFromRequest(req); + + // Parse input + const { orderId, action } = await req.json(); + + // Your logic here + const order = await base44.entities.Orders.get(orderId); + + // Return response + return Response.json({ + success: true, + order: order + }); +}); +``` + +### With Service Role Access + +```javascript +import { createClientFromRequest } from "npm:@base44/sdk"; + +Deno.serve(async (req) => { + const base44 = createClientFromRequest(req); + + // Check user is authenticated + const user = await base44.auth.me(); + if (!user) { + return Response.json({ error: "Unauthorized" }, { status: 401 }); + } + + // Use service role for admin operations + const allOrders = await base44.asServiceRole.entities.Orders.list(); + + return Response.json({ orders: allOrders }); +}); +``` + +### Using Secrets + +```javascript +Deno.serve(async (req) => { + // Access environment variables (configured in app settings) + const apiKey = Deno.env.get("STRIPE_API_KEY"); + + const response = await fetch("https://api.stripe.com/v1/charges", { + headers: { + "Authorization": `Bearer ${apiKey}` + } + }); + + return Response.json(await response.json()); +}); +``` + +### Error Handling + +```javascript +import { createClientFromRequest } from "npm:@base44/sdk"; + +Deno.serve(async (req) => { + try { + const base44 = createClientFromRequest(req); + const { orderId } = await req.json(); + + const order = await base44.entities.Orders.get(orderId); + if (!order) { + return Response.json( + { error: "Order not found" }, + { status: 404 } + ); + } + + return Response.json({ order }); + + } catch (error) { + return Response.json( + { error: error.message }, + { status: 500 } + ); + } +}); +``` + +## Setup Requirements + +1. Enable Backend Functions in app settings (requires appropriate plan) +2. Create function files in `/functions` folder +3. Configure secrets via app dashboard for API keys + +## Authentication Modes + +| Mode | Context | Permissions | +|------|---------|-------------| +| User | `base44.functions.invoke()` | Runs under calling user's permissions | +| Service Role | `base44.asServiceRole.functions.invoke()` | Admin-level access | + +Inside the function, use `createClientFromRequest(req)` to get a client that inherits the caller's auth context. + +## Type Definitions + +```typescript +/** Functions module for invoking custom backend functions. */ +interface FunctionsModule { + /** + * Invokes a custom backend function by name. + * + * If any parameter is a File object, the request will automatically be + * sent as multipart/form-data. Otherwise, it will be sent as JSON. + * + * @param functionName - The name of the function to invoke. + * @param data - An object containing named parameters for the function. + * @returns Promise resolving to the function's response. + */ + invoke(functionName: string, data: Record): Promise; +} +``` diff --git a/evals/fixtures/react-task-manager/skills/base44-sdk/references/integrations.md b/evals/fixtures/react-task-manager/skills/base44-sdk/references/integrations.md new file mode 100644 index 0000000..f356a18 --- /dev/null +++ b/evals/fixtures/react-task-manager/skills/base44-sdk/references/integrations.md @@ -0,0 +1,377 @@ +# Integrations Module + +Access third-party services via `base44.integrations`. + +## Types of Integrations + +1. **Core/Built-in**: AI, email, file uploads (available by default) +2. **Catalog integrations**: Pre-built connectors from Base44 catalog +3. **Custom integrations**: Your own OpenAPI-based integrations + +## Accessing Integrations + +```javascript +// Core integrations +base44.integrations.Core.FunctionName(params) + +// Custom integrations +base44.integrations.custom.call(slug, operationId, params) +``` + +## Core Integrations + +### InvokeLLM (AI Text Generation) + +Generate text or structured JSON data using AI models. + +```javascript +// Basic prompt - returns string +const response = await base44.integrations.Core.InvokeLLM({ + prompt: "Summarize this text: ..." +}); + +// With internet context (uses Google Search, Maps, News) +const response = await base44.integrations.Core.InvokeLLM({ + prompt: "What's the current weather in NYC?", + add_context_from_internet: true +}); + +// Structured JSON response - returns object +const response = await base44.integrations.Core.InvokeLLM({ + prompt: "Analyze the sentiment of: 'Great product but slow shipping'", + response_json_schema: { + type: "object", + properties: { + sentiment: { type: "string", enum: ["positive", "negative", "mixed"] }, + score: { type: "number", description: "Score from 1-10" }, + key_points: { type: "array", items: { type: "string" } } + } + } +}); +// Returns: { sentiment: "mixed", score: 7, key_points: ["great product", "slow shipping"] } + +// With file attachments (uploaded via UploadFile) +const response = await base44.integrations.Core.InvokeLLM({ + prompt: "Describe what's in this image", + file_urls: ["https://...uploaded_image.png"] +}); +``` + +**Parameters:** +- `prompt` (string, required): The prompt text to send to the model +- `add_context_from_internet` (boolean, optional): If true, uses Google Search/Maps/News for real-time context +- `response_json_schema` (object, optional): JSON schema for structured output +- `file_urls` (string[], optional): URLs of uploaded files for context + +### GenerateImage + +Create AI-generated images from text prompts. + +```javascript +const { url } = await base44.integrations.Core.GenerateImage({ + prompt: "A serene mountain landscape with a lake" +}); +console.log(url); // https://...generated_image.png +``` + +### SendEmail + +Send emails to registered users. Every app gets this integration (no plan upgrade required). + +```javascript +await base44.integrations.Core.SendEmail({ + to: "user@example.com", + subject: "Welcome!", + body: "

Hello

Welcome to our app.

", + from_name: "My App" // optional, defaults to app name +}); +``` + +**Parameters:** +- `to` (string, required): Recipient email address +- `subject` (string, required): Email subject line +- `body` (string, required): Plain text or HTML email body +- `from_name` (string, optional): Sender name displayed to recipient + +**Limitations:** +- 1 credit per email (2 credits with custom domain) + +### UploadFile (Public) + +Upload files to public storage. + +```javascript +const fileInput = document.querySelector('input[type="file"]'); +const { file_url } = await base44.integrations.Core.UploadFile({ + file: fileInput.files[0] +}); +console.log(file_url); // https://...uploaded_file.pdf +``` + +### UploadPrivateFile + +Upload files to private storage that requires a signed URL to access. + +```javascript +const { file_uri } = await base44.integrations.Core.UploadPrivateFile({ + file: fileInput.files[0] +}); +console.log(file_uri); // "private/user123/document.pdf" + +// Create a signed URL to access the file +const { signed_url } = await base44.integrations.Core.CreateFileSignedUrl({ + file_uri, + expires_in: 3600 // URL expires in 1 hour (default: 300 seconds) +}); +console.log(signed_url); // Temporary URL to access the private file +``` + +### CreateFileSignedUrl + +Generate temporary access links for private files. + +```javascript +const { signed_url } = await base44.integrations.Core.CreateFileSignedUrl({ + file_uri: "private/user123/document.pdf", + expires_in: 7200 // 2 hours +}); +``` + +**Parameters:** +- `file_uri` (string, required): URI from UploadPrivateFile +- `expires_in` (number, optional): Expiration time in seconds (default: 300) + +### ExtractDataFromUploadedFile + +Extract structured data from uploaded files using AI. + +```javascript +// First upload the file +const { file_url } = await base44.integrations.Core.UploadFile({ file }); + +// Then extract structured data +const result = await base44.integrations.Core.ExtractDataFromUploadedFile({ + file_url, + json_schema: { + type: "object", + properties: { + invoice_number: { type: "string" }, + total_amount: { type: "number" }, + date: { type: "string" }, + vendor_name: { type: "string" } + } + } +}); +console.log(result); // { invoice_number: "INV-12345", total_amount: 1250.00, ... } +``` + +## Custom Integrations + +Custom integrations allow workspace administrators to connect any external API by importing an OpenAPI specification. Use `base44.integrations.custom.call()` to invoke them. + +### Syntax + +```javascript +const response = await base44.integrations.custom.call( + slug, // Integration identifier (set by admin) + operationId, // Endpoint in "method:path" format + params // Optional: payload, pathParams, queryParams +); +``` + +### Examples + +```javascript +// GET request with query params +const response = await base44.integrations.custom.call( + "my-crm", + "get:/contacts", + { queryParams: { limit: 10, status: "active" } } +); + +// POST request with body +const response = await base44.integrations.custom.call( + "my-crm", + "post:/contacts", + { payload: { name: "John Doe", email: "john@example.com" } } +); + +// Request with path parameters +const response = await base44.integrations.custom.call( + "github", + "post:/repos/{owner}/{repo}/issues", + { + pathParams: { owner: "myorg", repo: "myrepo" }, + payload: { title: "Bug report", body: "Something is broken" } + } +); +``` + +### Response Structure + +```javascript +{ + success: true, // Whether external API returned 2xx + status_code: 200, // HTTP status code + data: { ... } // Response from external API +} +``` + +## Requirements + +- **Core integrations**: Available on all plans +- **Catalog/Custom integrations**: Require Builder plan or higher + +## Type Definitions + +### Core Integration Parameters + +```typescript +/** Parameters for the InvokeLLM function. */ +interface InvokeLLMParams { + /** The prompt text to send to the model. */ + prompt: string; + /** If true, uses Google Search/Maps/News for real-time context. */ + add_context_from_internet?: boolean; + /** JSON schema for structured output. If provided, returns object instead of string. */ + response_json_schema?: object; + /** File URLs (from UploadFile) to provide as context. */ + file_urls?: string[]; +} + +/** Parameters for the GenerateImage function. */ +interface GenerateImageParams { + /** Description of the image to generate. */ + prompt: string; +} + +/** Result from GenerateImage. */ +interface GenerateImageResult { + /** URL of the generated image. */ + url: string; +} + +/** Parameters for the UploadFile function. */ +interface UploadFileParams { + /** The file object to upload. */ + file: File; +} + +/** Result from UploadFile. */ +interface UploadFileResult { + /** URL of the uploaded file. */ + file_url: string; +} + +/** Parameters for the SendEmail function. */ +interface SendEmailParams { + /** Recipient email address. */ + to: string; + /** Email subject line. */ + subject: string; + /** Plain text or HTML email body. */ + body: string; + /** Sender name (defaults to app name). */ + from_name?: string; +} + +/** Parameters for ExtractDataFromUploadedFile. */ +interface ExtractDataFromUploadedFileParams { + /** URL of the uploaded file. */ + file_url: string; + /** JSON schema defining fields to extract. */ + json_schema: object; +} + +/** Parameters for UploadPrivateFile. */ +interface UploadPrivateFileParams { + /** The file object to upload. */ + file: File; +} + +/** Result from UploadPrivateFile. */ +interface UploadPrivateFileResult { + /** URI of the private file (used for signed URLs). */ + file_uri: string; +} + +/** Parameters for CreateFileSignedUrl. */ +interface CreateFileSignedUrlParams { + /** URI from UploadPrivateFile. */ + file_uri: string; + /** Expiration time in seconds (default: 300). */ + expires_in?: number; +} + +/** Result from CreateFileSignedUrl. */ +interface CreateFileSignedUrlResult { + /** Temporary signed URL to access the file. */ + signed_url: string; +} +``` + +### CoreIntegrations + +```typescript +/** Core package containing built-in Base44 integration functions. */ +interface CoreIntegrations { + InvokeLLM(params: InvokeLLMParams): Promise; + GenerateImage(params: GenerateImageParams): Promise; + UploadFile(params: UploadFileParams): Promise; + SendEmail(params: SendEmailParams): Promise; + ExtractDataFromUploadedFile(params: ExtractDataFromUploadedFileParams): Promise; + UploadPrivateFile(params: UploadPrivateFileParams): Promise; + CreateFileSignedUrl(params: CreateFileSignedUrlParams): Promise; +} +``` + +### Custom Integrations + +```typescript +/** Parameters for calling a custom integration endpoint. */ +interface CustomIntegrationCallParams { + /** Request body payload. */ + payload?: Record; + /** Path parameters to substitute in the URL. */ + pathParams?: Record; + /** Query string parameters. */ + queryParams?: Record; + /** Additional headers for this request. */ + headers?: Record; +} + +/** Response from a custom integration call. */ +interface CustomIntegrationCallResponse { + /** Whether the external API returned a 2xx status code. */ + success: boolean; + /** The HTTP status code from the external API. */ + status_code: number; + /** The response data from the external API. */ + data: any; +} + +/** Module for calling custom workspace-level API integrations. */ +interface CustomIntegrationsModule { + /** + * Call a custom integration endpoint. + * @param slug - The integration's unique identifier. + * @param operationId - The operation ID from the OpenAPI spec. + * @param params - Optional payload, pathParams, queryParams, headers. + */ + call(slug: string, operationId: string, params?: CustomIntegrationCallParams): Promise; +} +``` + +### IntegrationsModule + +```typescript +/** Integrations module for calling integration endpoints. */ +type IntegrationsModule = { + /** Core package with built-in integrations. */ + Core: CoreIntegrations; + /** Custom integrations module. */ + custom: CustomIntegrationsModule; + /** Additional integration packages (dynamic). */ + [packageName: string]: any; +}; +``` diff --git a/evals/fixtures/react-task-manager/skills/base44-sdk/references/users.md b/evals/fixtures/react-task-manager/skills/base44-sdk/references/users.md new file mode 100644 index 0000000..4205b06 --- /dev/null +++ b/evals/fixtures/react-task-manager/skills/base44-sdk/references/users.md @@ -0,0 +1,82 @@ +# Users Module + +Invite users to the app via `base44.users`. + +## Contents +- [Methods](#methods) +- [Examples](#examples) (Invite User) +- [Roles](#roles) +- [Notes](#notes) + +## Methods + +| Method | Signature | Description | +|--------|-----------|-------------| +| `inviteUser(user_email, role)` | `Promise` | Invite a user to the app | + +## Examples + +### Invite User + +```javascript +// Invite a user with "user" role +await base44.users.inviteUser("newuser@example.com", "user"); + +// Invite an admin +await base44.users.inviteUser("admin@example.com", "admin"); +``` + +### Invite Multiple Users + +```javascript +const usersToInvite = [ + { email: "user1@example.com", role: "user" }, + { email: "user2@example.com", role: "user" }, + { email: "manager@example.com", role: "admin" } +]; + +for (const user of usersToInvite) { + await base44.users.inviteUser(user.email, user.role); + console.log(`Invited ${user.email} as ${user.role}`); +} +``` + +## Roles + +The `role` parameter must be one of: + +| Role | Description | +|------|-------------| +| `"user"` | Standard user with default permissions | +| `"admin"` | Administrator with elevated permissions | + +**Note:** Only `"user"` and `"admin"` are valid role values. An error will be thrown if you pass any other value. + +## Notes + +- **Email invitation**: The invited user receives an email with a link to join the app +- **Duplicate handling**: Inviting an existing user will re-send the invitation +- **Also available in auth**: `base44.auth.inviteUser()` provides the same functionality +- **Role validation**: Only `"user"` or `"admin"` are accepted + +```javascript +// These are equivalent: +await base44.users.inviteUser("newuser@example.com", "user"); +await base44.auth.inviteUser("newuser@example.com", "user"); +``` + +## Type Definitions + +```typescript +/** Users module for inviting users to the app. */ +interface UsersModule { + /** + * Invite a user to the application. + * @param user_email - User's email address. + * @param role - User's role ('user' or 'admin'). + * @returns Promise resolving when the invitation is sent. + * @throws Error if role is not 'user' or 'admin'. + */ + inviteUser(user_email: string, role: "user" | "admin"): Promise; +} +``` diff --git a/evals/fixtures/sdk-auth/eval.json b/evals/fixtures/sdk-auth/eval.json new file mode 100644 index 0000000..6f419c2 --- /dev/null +++ b/evals/fixtures/sdk-auth/eval.json @@ -0,0 +1,137 @@ +{ + "$schema": "../../eval.schema.json", + "name": "sdk-auth", + "description": "Test SDK authentication patterns: login, register, profile, route guard", + "prompts": [ + { + "name": "sdk-auth-login", + "description": "Implement login page with email/password", + "prompt": "Create a LoginPage component in src/pages/LoginPage.jsx that lets users log in with email and password using the Base44 SDK auth module. Include form inputs for email and password, a submit handler that calls the correct auth method, error handling for failed login, and redirect to /dashboard on success.", + "expectedSkills": ["base44-sdk"], + "checks": [ + { + "type": "file-exists", + "description": "LoginPage component file exists", + "filePath": "src/pages/LoginPage.jsx" + }, + { + "type": "file-content", + "description": "Uses loginViaEmailPassword method", + "filePath": "src/pages/LoginPage.jsx", + "pattern": "loginViaEmailPassword" + }, + { + "type": "file-content", + "description": "Imports from Base44 SDK", + "filePath": "src/pages/LoginPage.jsx", + "pattern": "import.*base44|from ['\"].*base44" + }, + { + "type": "file-content-excluded", + "description": "Does not use Firebase auth methods", + "filePath": "src/pages/LoginPage.jsx", + "pattern": "signInWithEmailAndPassword|signIn\\(" + }, + { + "type": "file-content-excluded", + "description": "Does not use OAuth provider methods", + "filePath": "src/pages/LoginPage.jsx", + "pattern": "signInWith(Google|Provider)" + } + ] + }, + { + "name": "sdk-auth-register", + "description": "Implement registration page", + "prompt": "Create a RegisterPage component in src/pages/RegisterPage.jsx that lets new users register with the Base44 SDK. Include form fields for email and password, call the correct registration method, handle errors, and redirect to /login on success.", + "expectedSkills": ["base44-sdk"], + "checks": [ + { + "type": "file-exists", + "description": "RegisterPage component file exists", + "filePath": "src/pages/RegisterPage.jsx" + }, + { + "type": "file-content", + "description": "Uses register method", + "filePath": "src/pages/RegisterPage.jsx", + "pattern": "register\\s*\\(" + }, + { + "type": "file-content", + "description": "Imports from Base44 SDK", + "filePath": "src/pages/RegisterPage.jsx", + "pattern": "import.*base44|from ['\"].*base44" + }, + { + "type": "file-content-excluded", + "description": "Does not use Firebase registration methods", + "filePath": "src/pages/RegisterPage.jsx", + "pattern": "createUser|signUp\\(" + } + ] + }, + { + "name": "sdk-auth-profile", + "description": "Implement user profile page", + "prompt": "Create a ProfilePage component in src/pages/ProfilePage.jsx that displays the current user's information using the Base44 SDK. Fetch the current user on mount, display their email and name, and add a button to update the user's name using the correct SDK method.", + "expectedSkills": ["base44-sdk"], + "checks": [ + { + "type": "file-exists", + "description": "ProfilePage component file exists", + "filePath": "src/pages/ProfilePage.jsx" + }, + { + "type": "file-content", + "description": "Uses auth.me() to fetch current user", + "filePath": "src/pages/ProfilePage.jsx", + "pattern": "auth\\.me\\(\\)" + }, + { + "type": "file-content", + "description": "Uses updateMe or auth.update method", + "filePath": "src/pages/ProfilePage.jsx", + "pattern": "updateMe|auth\\.update" + }, + { + "type": "file-content-excluded", + "description": "Does not use Firebase auth state methods", + "filePath": "src/pages/ProfilePage.jsx", + "pattern": "currentUser|onAuthStateChanged" + } + ] + }, + { + "name": "sdk-auth-guard", + "description": "Implement route guard component", + "prompt": "Create an AuthGuard component in src/components/AuthGuard.jsx that checks if the user is logged in using the Base44 SDK. If logged in, render the children. If not, redirect to /login. Use auth.me() to check authentication status.", + "expectedSkills": ["base44-sdk"], + "checks": [ + { + "type": "file-exists", + "description": "AuthGuard component file exists", + "filePath": "src/components/AuthGuard.jsx" + }, + { + "type": "file-content", + "description": "Uses auth.me() to check authentication", + "filePath": "src/components/AuthGuard.jsx", + "pattern": "auth\\.me\\(\\)" + }, + { + "type": "file-content", + "description": "Renders children when authenticated", + "filePath": "src/components/AuthGuard.jsx", + "pattern": "children" + }, + { + "type": "file-content-excluded", + "description": "Does not use Firebase auth state methods", + "filePath": "src/components/AuthGuard.jsx", + "pattern": "onAuthStateChanged|currentUser" + } + ] + } + ] +} diff --git a/evals/fixtures/sdk-auth/project/.claude/settings.json b/evals/fixtures/sdk-auth/project/.claude/settings.json new file mode 100644 index 0000000..ab60505 --- /dev/null +++ b/evals/fixtures/sdk-auth/project/.claude/settings.json @@ -0,0 +1,6 @@ +{ + "permissions": { + "allow": ["Read", "Write", "Edit", "Bash", "Glob", "Grep"] + }, + "skills": ["./skills"] +} diff --git a/evals/fixtures/sdk-auth/project/AGENTS.md b/evals/fixtures/sdk-auth/project/AGENTS.md new file mode 100644 index 0000000..6c0f2be --- /dev/null +++ b/evals/fixtures/sdk-auth/project/AGENTS.md @@ -0,0 +1,25 @@ +IMPORTANT: Prefer retrieval-led reasoning over pre-training-led reasoning. Base44 is NOT Firebase, Supabase, or any other SDK you know. Every API name below is the ONLY correct form. Always verify against this index and the referenced docs. + +[Base44 SDK Docs Index]|root: ./skills/base44-sdk/references +|entities.md: CRUD - create(data), list(), filter(query), get(id), update(id,data), delete(id), subscribe(cb) +|auth.md: Auth - loginViaEmailPassword(), loginWithProvider('google'), me(), register(), logout() +|base44-agents.md: Agents - createConversation({agent_name}), addMessage(conv,{role,content}), subscribeToConversation(id,cb) +|functions.md: Functions - invoke(name, data) +|integrations.md: Core - InvokeLLM({prompt}), SendEmail({to,subject,body}), UploadFile({file}) +|client.md: Client - createClient({appId}), import from "@base44/sdk" +|QUICK_REFERENCE.md: All method signatures + +[Base44 CLI Docs Index]|root: ./skills/base44-cli/references +|entities-create.md: Entity schema - {name:"PascalCase", type:"object", properties:{}, required:[]} +|functions-create.md: Function - function.jsonc {name,entry} + index.ts with Deno.serve + createClientFromRequest +|create.md: Project creation - npx base44 create name -p . +|deploy.md: Deploy - npx base44 deploy -y +|agents-push.md: Agent config - {name:"snake_case", description, instructions, tool_configs:[{entity_name,allowed_operations}|{function_name,description}]} + +[Common Mistakes - NEVER use these hallucinated names] +|SDK: Do NOT use find() → use filter(), findOne() → get(), insert() → create(), remove() → delete(), onChange() → subscribe() +|SDK: Do NOT use signInWithGoogle() → use loginWithProvider('google'), functions.call() → functions.invoke() +|SDK: Do NOT use ai.generate() → use integrations.Core.InvokeLLM({prompt}) +|CLI: Entity names must be PascalCase alphanumeric. File names must be kebab-case.jsonc +|CLI: Agent names must be lowercase with underscores only. Function entry field is "entry" not "entrypoint" +|CLI: Backend functions import from "npm:@base44/sdk" (Deno requires npm: prefix) diff --git a/evals/fixtures/sdk-auth/project/CLAUDE.md b/evals/fixtures/sdk-auth/project/CLAUDE.md new file mode 100644 index 0000000..724ce4d --- /dev/null +++ b/evals/fixtures/sdk-auth/project/CLAUDE.md @@ -0,0 +1,7 @@ +# Base44 Development Guidelines + +When working with Base44 projects, first explore the existing project structure to understand what already exists. Look at existing code, config files, and patterns in use. + +Then consult the AGENTS.md index in the project root for correct API patterns. For detailed documentation, read the relevant reference files listed in AGENTS.md. + +IMPORTANT: Prefer retrieval-led reasoning over pre-training-led reasoning. Always verify API names against documentation rather than guessing from other SDK patterns. diff --git a/evals/fixtures/sdk-auth/project/base44/config.jsonc b/evals/fixtures/sdk-auth/project/base44/config.jsonc new file mode 100644 index 0000000..ffb0b7e --- /dev/null +++ b/evals/fixtures/sdk-auth/project/base44/config.jsonc @@ -0,0 +1,10 @@ +{ + "name": "Auth Test App", + "description": "A test application for SDK authentication", + "entitiesDir": "./entities", + "functionsDir": "./functions", + "site": { + "buildCommand": "npm run build", + "outputDirectory": "dist" + } +} diff --git a/evals/fixtures/sdk-auth/project/package.json b/evals/fixtures/sdk-auth/project/package.json new file mode 100644 index 0000000..b20ac94 --- /dev/null +++ b/evals/fixtures/sdk-auth/project/package.json @@ -0,0 +1,20 @@ +{ + "name": "auth-test-app", + "version": "0.1.0", + "private": true, + "type": "module", + "scripts": { + "dev": "vite", + "build": "vite build" + }, + "dependencies": { + "@base44/sdk": "^1.0.0", + "react": "^18.2.0", + "react-dom": "^18.2.0" + }, + "devDependencies": { + "@vitejs/plugin-react": "^4.2.1", + "base44": "latest", + "vite": "^5.2.0" + } +} diff --git a/evals/fixtures/sdk-auth/project/src/App.jsx b/evals/fixtures/sdk-auth/project/src/App.jsx new file mode 100644 index 0000000..5553fc1 --- /dev/null +++ b/evals/fixtures/sdk-auth/project/src/App.jsx @@ -0,0 +1,11 @@ +import React from 'react'; + +function App() { + return ( +
+

Auth Test App

+
+ ); +} + +export default App; diff --git a/evals/fixtures/sdk-auth/project/src/api/base44Client.js b/evals/fixtures/sdk-auth/project/src/api/base44Client.js new file mode 100644 index 0000000..7887147 --- /dev/null +++ b/evals/fixtures/sdk-auth/project/src/api/base44Client.js @@ -0,0 +1,5 @@ +import { createClient } from "@base44/sdk"; + +const base44 = createClient({ appId: "test-app" }); + +export default base44; diff --git a/evals/fixtures/sdk-auth/skills/base44-cli/SKILL.md b/evals/fixtures/sdk-auth/skills/base44-cli/SKILL.md new file mode 100644 index 0000000..3c33f05 --- /dev/null +++ b/evals/fixtures/sdk-auth/skills/base44-cli/SKILL.md @@ -0,0 +1,384 @@ +--- +name: base44-cli +description: "The base44 CLI is used for EVERYTHING related to base44 projects: resource configuration (entities, backend functions, ai agents), initialization and actions (resource creation, deployment). This skill is the place for learning about how to configure resources. When you plan or implement a feature, you must learn this skill" +--- + +# Base44 CLI + +Create and manage Base44 apps (projects) using the Base44 CLI tool. + +## ⚡ IMMEDIATE ACTION REQUIRED - Read This First + +This skill activates on ANY mention of "base44" or when a `base44/` folder exists. **DO NOT read documentation files or search the web before acting.** + +**Your first action MUST be:** +1. Check if `base44/config.jsonc` exists in the current directory +2. If **NO** (new project scenario): + - This skill (base44-cli) handles the request + - Guide user through project initialization + - Do NOT activate base44-sdk yet +3. If **YES** (existing project scenario): + - Transfer to base44-sdk skill for implementation + - This skill only handles CLI commands (login, deploy, entities push) + +## Critical: Local Installation Only + +NEVER call `base44` directly. The CLI is installed locally as a dev dependency and must be accessed via a package manager: + +- `npx base44 ` (npm - recommended) +- `yarn base44 ` (yarn) +- `pnpm base44 ` (pnpm) + +WRONG: `base44 login` +RIGHT: `npx base44 login` + +## MANDATORY: Authentication Check at Session Start + +**CRITICAL**: At the very start of every AI session when this skill is activated, you MUST: + +1. **Check authentication status** by running: + ```bash + npx base44 whoami + ``` + +2. **If the user is logged in** (command succeeds and shows an email): + - Continue with the requested task + +3. **If the user is NOT logged in** (command fails or shows an error): + - **STOP immediately** + - **DO NOT proceed** with any CLI operations + - **Ask the user to login manually** by running: + ```bash + npx base44 login + ``` + - Wait for the user to confirm they have logged in before continuing + +**This check is mandatory and must happen before executing any other Base44 CLI commands.** + +## Overview + +The Base44 CLI provides command-line tools for authentication, creating projects, managing entities, and deploying Base44 applications. It is framework-agnostic and works with popular frontend frameworks like Vite, Next.js, and Create React App, Svelte, Vue, and more. + +## When to Use This Skill vs base44-sdk + +**Use base44-cli when:** +- Creating a **NEW** Base44 project from scratch +- Initializing a project in an empty directory +- Directory is missing `base44/config.jsonc` +- User mentions: "create a new project", "initialize project", "setup a project", "start a new Base44 app" +- Deploying, pushing entities, or authenticating via CLI +- Working with CLI commands (`npx base44 ...`) + +**Use base44-sdk when:** +- Building features in an **EXISTING** Base44 project +- `base44/config.jsonc` already exists +- Writing JavaScript/TypeScript code using Base44 SDK +- Implementing functionality, components, or features +- User mentions: "implement", "build a feature", "add functionality", "write code" + +**Skill Dependencies:** +- `base44-cli` is a **prerequisite** for `base44-sdk` in new projects +- If user wants to "create an app" and no Base44 project exists, use `base44-cli` first +- `base44-sdk` assumes a Base44 project is already initialized + +**State Check Logic:** +Before selecting a skill, check: +- IF (user mentions "create/build app" OR "make a project"): + - IF (directory is empty OR no `base44/config.jsonc` exists): + → Use **base44-cli** (project initialization needed) + - ELSE: + → Use **base44-sdk** (project exists, build features) + +## Project Structure + +A Base44 project combines a standard frontend project with a `base44/` configuration folder: + +``` +my-app/ +├── base44/ # Base44 configuration (created by CLI) +│ ├── config.jsonc # Project settings, site config +│ ├── entities/ # Entity schema definitions +│ │ ├── task.jsonc +│ │ └── board.jsonc +│ ├── functions/ # Backend functions (optional) +│ │ └── my-function/ +│ │ ├── function.jsonc +│ │ └── index.ts +│ └── agents/ # Agent configurations (optional) +│ └── support_agent.jsonc +├── src/ # Frontend source code +│ ├── api/ +│ │ └── base44Client.js # Base44 SDK client +│ ├── pages/ +│ ├── components/ +│ └── main.jsx +├── index.html # SPA entry point +├── package.json +└── vite.config.js # Or your framework's config +``` + +**Key files:** +- `base44/config.jsonc` - Project name, description, site build settings +- `base44/entities/*.jsonc` - Data model schemas (see Entity Schema section) +- `base44/agents/*.jsonc` - Agent configurations (optional) +- `src/api/base44Client.js` - Pre-configured SDK client for frontend use + +**config.jsonc example:** +```jsonc +{ + "name": "My App", // Required: project name + "description": "App description", // Optional: project description + "entitiesDir": "./entities", // Optional: default "entities" + "functionsDir": "./functions", // Optional: default "functions" + "agentsDir": "./agents", // Optional: default "agents" + "site": { // Optional: site deployment config + "installCommand": "npm install", // Optional: install dependencies + "buildCommand": "npm run build", // Optional: build command + "serveCommand": "npm run dev", // Optional: local dev server + "outputDirectory": "./dist" // Optional: build output directory + } +} +``` + +**Config properties:** + +| Property | Description | Default | +|----------|-------------|---------| +| `name` | Project name (required) | - | +| `description` | Project description | - | +| `entitiesDir` | Directory for entity schemas | `"entities"` | +| `functionsDir` | Directory for backend functions | `"functions"` | +| `agentsDir` | Directory for agent configs | `"agents"` | +| `site.installCommand` | Command to install dependencies | - | +| `site.buildCommand` | Command to build the project | - | +| `site.serveCommand` | Command to run dev server | - | +| `site.outputDirectory` | Build output directory for deployment | - | + +## Installation + +Install the Base44 CLI as a dev dependency in your project: + +```bash +npm install --save-dev base44 +``` + +**Important:** Never assume or hardcode the `base44` package version. Always install without a version specifier to get the latest version. + +Then run commands using `npx`: + +```bash +npx base44 +``` + +**Note:** All commands in this documentation use `npx base44`. You can also use `yarn base44`, or `pnpm base44` if preferred. + +## Available Commands + +### Authentication + +| Command | Description | Reference | +| --------------- | ----------------------------------------------- | ------------------------------------------- | +| `base44 login` | Authenticate with Base44 using device code flow | [auth-login.md](references/auth-login.md) | +| `base44 logout` | Logout from current device | [auth-logout.md](references/auth-logout.md) | +| `base44 whoami` | Display current authenticated user | [auth-whoami.md](references/auth-whoami.md) | + +### Project Management + +| Command | Description | Reference | +|---------|-------------|-----------| +| `base44 create` | Create a new Base44 project from a template | [create.md](references/create.md) ⚠️ **MUST READ** | +| `base44 link` | Link an existing local project to Base44 | [link.md](references/link.md) | +| `base44 dashboard` | Open the app dashboard in your browser | [dashboard.md](references/dashboard.md) | + +### Deployment + +| Command | Description | Reference | +|---------|-------------|-----------| +| `base44 deploy` | Deploy all resources (entities, functions, site) | [deploy.md](references/deploy.md) | + +### Entity Management + +| Action / Command | Description | Reference | +| ---------------------- | ------------------------------------------- | --------------------------------------------------- | +| Create Entities | Define entities in `base44/entities` folder | [entities-create.md](references/entities-create.md) | +| `base44 entities push` | Push local entities to Base44 | [entities-push.md](references/entities-push.md) | + +#### Entity Schema (Quick Reference) + +ALWAYS follow this exact structure when creating entity files: + +**File naming:** `base44/entities/{kebab-case-name}.jsonc` (e.g., `team-member.jsonc` for `TeamMember`) + +**Schema template:** +```jsonc +{ + "name": "EntityName", + "type": "object", + "properties": { + "field_name": { + "type": "string", + "description": "Field description" + } + }, + "required": ["field_name"] +} +``` + +**Field types:** `string`, `number`, `integer`, `boolean`, `array`, `object`, `binary` +**String formats:** `date`, `date-time`, `time`, `email`, `uri`, `hostname`, `ipv4`, `ipv6`, `uuid`, `file`, `regex`, `richtext` +**For enums:** Add `"enum": ["value1", "value2"]` and optionally `"default": "value1"` +**Entity names:** Must be alphanumeric only (pattern: `/^[a-zA-Z0-9]+$/`) + +For complete documentation, see [entities-create.md](references/entities-create.md). + +### Function Management + +| Action / Command | Description | Reference | +| ------------------------- | --------------------------------------------- | ------------------------------------------------------- | +| Create Functions | Define functions in `base44/functions` folder | [functions-create.md](references/functions-create.md) | +| `base44 functions deploy` | Deploy local functions to Base44 | [functions-deploy.md](references/functions-deploy.md) | + +### Agent Management + +Agents are conversational AI assistants that can interact with users, access your app's entities, and call backend functions. Use these commands to manage agent configurations. + +| Action / Command | Description | Reference | +| ----------------------- | --------------------------------------- | ----------------------------------------------- | +| Create Agents | Define agents in `base44/agents` folder | See Agent Schema below | +| `base44 agents pull` | Pull remote agents to local files | [agents-pull.md](references/agents-pull.md) | +| `base44 agents push` | Push local agents to Base44 | [agents-push.md](references/agents-push.md) | + +**Note:** Agent commands perform full synchronization - pushing replaces all remote agents with local ones, and pulling replaces all local agents with remote ones. + +#### Agent Schema (Quick Reference) + +**File naming:** `base44/agents/{agent_name}.jsonc` (e.g., `support_agent.jsonc`) + +**Schema template:** +```jsonc +{ + "name": "agent_name", + "description": "Brief description of what this agent does", + "instructions": "Detailed instructions for the agent's behavior", + "tool_configs": [ + // Entity tool - gives agent access to entity operations + { "entity_name": "tasks", "allowed_operations": ["read", "create", "update", "delete"] }, + // Backend function tool - gives agent access to a function + { "function_name": "send_email", "description": "Send an email notification" } + ], + "whatsapp_greeting": "Hello! How can I help you today?" +} +``` + +**Naming rules:** +- Agent names must match pattern: `/^[a-z0-9_]+$/` (lowercase alphanumeric with underscores, 1-100 chars) +- Valid: `support_agent`, `order_bot` +- Invalid: `Support-Agent`, `OrderBot` + +**Required fields:** `name`, `description`, `instructions` +**Optional fields:** `tool_configs` (defaults to `[]`), `whatsapp_greeting` + +**Tool config types:** +- **Entity tools**: `entity_name` + `allowed_operations` (array of: `read`, `create`, `update`, `delete`) +- **Backend function tools**: `function_name` + `description` + +### Site Deployment + +| Command | Description | Reference | +| -------------------- | ----------------------------------------- | ------------------------------------------- | +| `base44 site deploy` | Deploy built site files to Base44 hosting | [site-deploy.md](references/site-deploy.md) | + +**SPA only**: Base44 hosting supports Single Page Applications with a single `index.html` entry point. All routes are served from `index.html` (client-side routing). + +## Quick Start + +1. Install the CLI in your project: + ```bash + npm install --save-dev base44 + ``` + +2. Authenticate with Base44: + ```bash + npx base44 login + ``` + +3. Create a new project (ALWAYS provide name and `--path` flag): + ```bash + npx base44 create my-app -p . + ``` + +4. Build and deploy everything: + ```bash + npm run build + npx base44 deploy -y + ``` + +Or deploy individual resources: +- `npx base44 entities push` - Push entities only +- `npx base44 functions deploy` - Deploy functions only +- `npx base44 agents push` - Push agents only +- `npx base44 site deploy -y` - Deploy site only + +## Common Workflows + +### Creating a New Project + +**⚠️ MANDATORY: Before running `base44 create`, you MUST read [create.md](references/create.md) for:** +- **Template selection** - Choose the correct template (`backend-and-client` vs `backend-only`) +- **Correct workflow** - Different templates require different setup steps +- **Common pitfalls** - Avoid folder creation errors that cause failures + +Failure to follow the create.md instructions will result in broken project scaffolding. + +### Linking an Existing Project +```bash +# If you have base44/config.jsonc but no .app.jsonc +npx base44 link --create --name my-app +``` + +### Deploying All Changes +```bash +# Build your project first +npm run build + +# Deploy everything (entities, functions, and site) +npx base44 deploy -y +``` + +### Deploying Individual Resources +```bash +# Push only entities +npx base44 entities push + +# Deploy only functions +npx base44 functions deploy + +# Push only agents +npx base44 agents push + +# Deploy only site +npx base44 site deploy -y +``` + +### Opening the Dashboard +```bash +# Open app dashboard in browser +npx base44 dashboard +``` + +## Authentication + +Most commands require authentication. If you're not logged in, the CLI will automatically prompt you to login. Your session is stored locally and persists across CLI sessions. + +## Troubleshooting + +| Error | Solution | +| --------------------------- | ----------------------------------------------------------------------------------- | +| Not authenticated | Run `npx base44 login` first | +| No entities found | Ensure entities exist in `base44/entities/` directory | +| Entity not recognized | Ensure file uses kebab-case naming (e.g., `team-member.jsonc` not `TeamMember.jsonc`) | +| No functions found | Ensure functions exist in `base44/functions/` with valid `function.jsonc` configs | +| No agents found | Ensure agents exist in `base44/agents/` directory with valid `.jsonc` configs | +| Invalid agent name | Agent names must be lowercase alphanumeric with underscores only | +| No site configuration found | Check that `site.outputDirectory` is configured in project config | +| Site deployment fails | Ensure you ran `npm run build` first and the build succeeded | diff --git a/evals/fixtures/sdk-auth/skills/base44-cli/references/agents-pull.md b/evals/fixtures/sdk-auth/skills/base44-cli/references/agents-pull.md new file mode 100644 index 0000000..6a700f0 --- /dev/null +++ b/evals/fixtures/sdk-auth/skills/base44-cli/references/agents-pull.md @@ -0,0 +1,73 @@ +# base44 agents pull + +Pull AI agent configurations from Base44 to local files. Agents are conversational AI assistants that can interact with users, access your app's entities, and call backend functions. + +## Syntax + +```bash +npx base44 agents pull +``` + +## Authentication + +**Required**: Yes. If not authenticated, you'll be prompted to login first. + +## What It Does + +1. Fetches all agents from Base44 +2. Writes agent files to the `base44/agents/` directory +3. Deletes local agent files that don't exist remotely +4. Reports written and deleted agents + +## Prerequisites + +- Must be run from a Base44 project directory +- Project must be linked to a Base44 app + +## Output + +```bash +$ npx base44 agents pull + +Fetching agents from Base44... +✓ Agents fetched successfully + +Writing agent files... +✓ Agent files written successfully + +Written: support_agent, order_bot +Deleted: old_agent + +Pulled 2 agents to base44/agents +``` + +## Agent Synchronization + +The pull operation synchronizes remote agents to your local files: + +- **Written**: Agent files created or updated from remote +- **Deleted**: Local agent files removed (didn't exist remotely) + +**Warning**: This operation replaces all local agent configurations with remote versions. Any local changes not pushed to Base44 will be overwritten. + +## Error Handling + +If no agents exist on Base44: +```bash +$ npx base44 agents pull +No agents found on Base44 +``` + +## Use Cases + +- Sync agent configurations to a new development machine +- Get the latest agent configurations from your team +- Restore local agent files after accidental deletion +- Start working on an existing project with agents + +## Notes + +- This command syncs agent configurations, not conversation data +- Agent files are stored as `.jsonc` in the `base44/agents/` directory +- The directory location is configurable via `agentsDir` in `config.jsonc` +- Use `base44 agents push` to upload local changes to Base44 diff --git a/evals/fixtures/sdk-auth/skills/base44-cli/references/agents-push.md b/evals/fixtures/sdk-auth/skills/base44-cli/references/agents-push.md new file mode 100644 index 0000000..fa2795b --- /dev/null +++ b/evals/fixtures/sdk-auth/skills/base44-cli/references/agents-push.md @@ -0,0 +1,156 @@ +# base44 agents push + +Push local AI agent configurations to Base44. Agents are conversational AI assistants that can interact with users, access your app's entities, and call backend functions. + +## Syntax + +```bash +npx base44 agents push +``` + +## Authentication + +**Required**: Yes. If not authenticated, you'll be prompted to login first. + +## What It Does + +1. Reads all agent files from the `base44/agents/` directory +2. Validates agent configurations +3. Displays the count of agents to be pushed +4. Uploads agents to the Base44 backend +5. Reports the results: created, updated, and deleted agents + +## Prerequisites + +- Must be run from a Base44 project directory +- Project must have agent definitions in the `base44/agents/` folder + +## Output + +```bash +$ npx base44 agents push + +Found 2 agents to push +Pushing agents to Base44... + +Created: support_agent +Updated: order_bot +Deleted: old_agent + +✓ Agents pushed to Base44 +``` + +## Agent Synchronization + +The push operation synchronizes your local agents with Base44: + +- **Created**: New agents that didn't exist in Base44 +- **Updated**: Existing agents with modified configuration +- **Deleted**: Agents that were removed from your local configuration + +**Warning**: This is a full sync operation. Agents removed locally will be deleted from Base44. + +## Error Handling + +If no agents are found in your project: +```bash +$ npx base44 agents push +No local agents found - this will delete all remote agents +``` + +If an agent has an invalid name: +```bash +$ npx base44 agents push +Error: Agent name must be lowercase alphanumeric with underscores +``` + +## Agent Configuration Schema + +Each agent file should be a `.jsonc` file in `base44/agents/` with this structure: + +```jsonc +{ + "name": "agent_name", // Required: lowercase alphanumeric with underscores, 1-100 chars + "description": "Brief description of what this agent does", // Required: min 1 char + "instructions": "Detailed instructions for the agent's behavior", // Required: min 1 char + "tool_configs": [ // Optional: defaults to [] + // Entity tool - gives agent access to entity operations + { "entity_name": "Task", "allowed_operations": ["read", "create", "update", "delete"] }, + // Backend function tool - gives agent access to a function + { "function_name": "send_email", "description": "Send an email notification" } + ], + "whatsapp_greeting": "Hello! How can I help you today?" // Optional +} +``` + +**Naming rules:** +- **Agent names** must match pattern: `/^[a-z0-9_]+$/` (lowercase alphanumeric with underscores only, 1-100 characters) + - Valid: `support_agent`, `order_bot`, `task_helper` + - Invalid: `Support-Agent`, `OrderBot`, `task helper` +- **Agent file names** must use underscores (matching the agent name) + - Valid: `support_agent.jsonc`, `order_bot.jsonc` + - Invalid: `support-agent.jsonc` (hyphens not allowed) +- **Entity names in `tool_configs`** must use PascalCase (matching the entity's `name` field) + - Valid: `"entity_name": "Task"`, `"entity_name": "TeamMember"` + - Invalid: `"entity_name": "task"`, `"entity_name": "team_member"` + +**Required fields:** +- `name`: Required, must follow naming rules above +- `description`: Required, minimum 1 character +- `instructions`: Required, minimum 1 character +- `tool_configs`: Optional, defaults to empty array +- `whatsapp_greeting`: Optional + +### Common Mistake: Wrong tool_configs Format + +**WRONG** - Do NOT use `tools` with `type` and `entity`: +```jsonc +{ + "name": "my_agent", + "tools": [ // ❌ WRONG + { "type": "entity_query", "entity": "Task" } + ] +} +``` + +**CORRECT** - Use `tool_configs` with `entity_name` and `allowed_operations`: +```jsonc +{ + "name": "my_agent", + "tool_configs": [ // ✅ CORRECT + { "entity_name": "Task", "allowed_operations": ["read"] } + ] +} +``` + +### Best Practices for Agent Instructions + +When giving agents access to entities, be explicit in the instructions about using the tools: + +```jsonc +{ + "name": "support_agent", + "instructions": "You are a helpful support agent.\n\nIMPORTANT: You have access to customer data through entity tools. When users ask about their orders or account:\n1. ALWAYS use the Order entity tool to query their order history\n2. Use the Customer entity tool to look up account details\n3. Analyze the data and provide personalized responses\n\nAlways query the relevant entities first before answering questions about user data.", + "tool_configs": [ + { "entity_name": "Order", "allowed_operations": ["read"] }, + { "entity_name": "Customer", "allowed_operations": ["read"] } + ] +} +``` + +Without explicit instructions to use the entity tools, the agent may not proactively query user data when asked. + +## Use Cases + +- After defining new agents in your project +- When modifying existing agent configurations +- To sync agent changes before testing +- As part of your development workflow when agent behavior changes + +## Notes + +- This command syncs the agent configuration, not conversation data +- Changes are applied to your Base44 project immediately +- Make sure to test agent changes in a development environment first +- Agent definitions are located in the `base44/agents/` directory +- Use `base44 agents pull` to download agents from Base44 diff --git a/evals/fixtures/sdk-auth/skills/base44-cli/references/auth-login.md b/evals/fixtures/sdk-auth/skills/base44-cli/references/auth-login.md new file mode 100644 index 0000000..adebd27 --- /dev/null +++ b/evals/fixtures/sdk-auth/skills/base44-cli/references/auth-login.md @@ -0,0 +1,54 @@ +# base44 login + +Authenticate with Base44 using device code flow. + +## Syntax + +```bash +npx base44 login +``` + +## Authentication + +**Required**: No (this is the login command itself) + +## How It Works + +The login command uses OAuth 2.0 device code flow for authentication: + +1. Generates a device code for authentication +2. Displays a verification code and verification URI +3. Directs you to visit the URI and enter the code +4. Polls for authentication completion (up to device code expiration) +5. Retrieves access and refresh tokens upon successful authentication +6. Fetches and displays your user information +7. Saves authentication data locally with expiration timestamp + +## Interactive Flow + +```bash +$ npx base44 login + +Please visit: https://auth.base44.com/device +Enter code: ABCD-EFGH + +Waiting for authentication... +✓ Successfully authenticated! + +Logged in as: user@example.com +``` + +## Session Management + +- Authentication tokens are stored locally on your device +- Tokens include expiration timestamps +- The session persists across CLI sessions +- Other commands will automatically use your stored credentials +- Use `npx base44 logout` to clear your session +- Use `npx base44 whoami` to check your current authentication status + +## Notes + +- You only need to login once per device +- If your session expires, you'll be prompted to login again when running authenticated commands +- The CLI automatically prompts for login when you run commands that require authentication diff --git a/evals/fixtures/sdk-auth/skills/base44-cli/references/auth-logout.md b/evals/fixtures/sdk-auth/skills/base44-cli/references/auth-logout.md new file mode 100644 index 0000000..33c2ddf --- /dev/null +++ b/evals/fixtures/sdk-auth/skills/base44-cli/references/auth-logout.md @@ -0,0 +1,32 @@ +# base44 logout + +Logout from current device and clear stored authentication data. + +## Syntax + +```bash +npx base44 logout +``` + +## Authentication + +**Required**: No + +## What It Does + +- Deletes stored authentication data from your device +- Clears your local session +- Removes access and refresh tokens + +## Output + +```bash +$ npx base44 logout +Logged out successfully +``` + +## Notes + +- You can logout even if you're not currently logged in (no error) +- After logout, you'll need to run `npx base44 login` again to use authenticated commands +- This only affects the current device; your Base44 account remains active diff --git a/evals/fixtures/sdk-auth/skills/base44-cli/references/auth-whoami.md b/evals/fixtures/sdk-auth/skills/base44-cli/references/auth-whoami.md new file mode 100644 index 0000000..abe450c --- /dev/null +++ b/evals/fixtures/sdk-auth/skills/base44-cli/references/auth-whoami.md @@ -0,0 +1,37 @@ +# base44 whoami + +Display the currently authenticated user. + +## Syntax + +```bash +npx base44 whoami +``` + +## Authentication + +**Required**: Yes. If not authenticated, you'll be prompted to login first. + +## What It Does + +- Reads stored authentication data +- Displays the email of the currently logged-in user + +## Output + +```bash +$ npx base44 whoami +Logged in as: user@example.com +``` + +## Use Cases + +- Verify you're logged in before running other commands +- Check which account you're currently using +- Confirm authentication is working properly +- Useful in scripts or automation to verify credentials + +## Notes + +- If you're not logged in, the command will prompt you to authenticate first +- The email displayed matches your Base44 account email diff --git a/evals/fixtures/sdk-auth/skills/base44-cli/references/create.md b/evals/fixtures/sdk-auth/skills/base44-cli/references/create.md new file mode 100644 index 0000000..7580645 --- /dev/null +++ b/evals/fixtures/sdk-auth/skills/base44-cli/references/create.md @@ -0,0 +1,111 @@ +# base44 create + +Creates a new Base44 project from a template. This command is framework-agnostic and can either scaffold a complete project or add Base44 configuration to an existing project. + +## Critical: Non-Interactive Mode Required + +ALWAYS provide both the project name AND `--path` flag. Without both, the command opens an interactive TUI which agents cannot use properly. + +WRONG: `npx base44 create` +WRONG: `npx base44 create my-app` +RIGHT: `npx base44 create my-app -p ./my-app` + +## Syntax + +```bash +npx base44 create [name] --path [options] +``` + +## Arguments & Options + +| Argument/Option | Description | Required | +|--------|-------------|----------| +| `name` | Project name (positional argument) | Yes* | +| `-p, --path ` | Path where to create the project | Yes* | +| `-t, --template ` | Template ID (see templates below) | No | +| `--deploy` | Build and deploy the site (includes pushing entities) | No | +| `--no-skills` | Skip AI agent skills installation (skills are added by default) | No | + +*Required for non-interactive mode. Both `name` and `--path` must be provided together. + +## Template Selection (CRITICAL - Choose Appropriately) + +**You MUST select the most appropriate template based on user requirements:** + +| Template ID | When to Use | Example Scenarios | +|-------------|-------------|-------------------| +| `backend-and-client` | Creating a NEW full-stack web app from scratch | "Create a task app", "Build me a dashboard", "Make a SaaS app" | +| `backend-only` | Adding Base44 to an EXISTING project OR using a different framework (Next.js, Vue, Svelte, etc.) | "Add Base44 to my project", "I want to use Next.js", "I already have a frontend" | + +**Default Choice:** When the user asks to "create an app" or "build a project" without specifying a particular framework, use `backend-and-client` to provide a complete, production-ready application with Vite + React + Tailwind. + +## The `--path` Flag + +- **For `backend-and-client` template (new projects):** Use a new subfolder path + ```bash + npx base44 create my-app -p ./my-app -t backend-and-client + ``` +- **For `backend-only` template (existing projects):** Use `-p .` in the current directory + ```bash + npx base44 create my-app -p . + ``` + +## Workflow: Using `backend-only` with External Frameworks + +**CRITICAL: The project folder MUST exist BEFORE running `base44 create` with `backend-only`** + +The `backend-only` template only adds Base44 configuration files - it does NOT create a frontend. If you need a frontend with a specific framework: + +```bash +# Step 1: Initialize the frontend project FIRST +npm create vite@latest my-app -- --template react # or vue, svelte, etc. +# OR: npx create-next-app@latest my-app +# OR: any other framework's init command + +# Step 2: Navigate into the created folder +cd my-app + +# Step 3: Install Base44 CLI +npm install --save-dev base44 + +# Step 4: Add Base44 configuration +npx base44 create my-app -p . +``` + +**WARNING:** Do NOT: +- Create an empty folder manually, then try to run `npx create vite` inside it (will fail - folder exists) +- Run `base44 create` with `backend-only` expecting it to create a frontend (it won't) + +**DO:** +- Run the external framework's init command FIRST (it creates its own folder) +- Then run `base44 create` inside that folder with `-p .` + +## Examples + +```bash +# RECOMMENDED: Create full-stack project (for new apps) +npx base44 create my-app -p ./my-app -t backend-and-client + +# Create full-stack and deploy in one step +npx base44 create my-app -p ./my-app -t backend-and-client --deploy + +# Add Base44 to EXISTING project (must be inside the project folder) +npx base44 create my-app -p . + +# Add Base44 to existing project and deploy +npx base44 create my-app -p . --deploy + +# Create without adding AI agent skills +npx base44 create my-app -p . --no-skills +``` + +## What It Does + +1. Applies the selected template to the target path +2. Creates a `base44/` folder with configuration files +3. Registers the project with Base44 backend +4. Creates `base44/.app.jsonc` with the app ID +5. If `--deploy` is used: + - Pushes any entities defined in `base44/entities/` + - Runs install and build commands (for templates with frontend) + - Deploys the site to Base44 hosting diff --git a/evals/fixtures/sdk-auth/skills/base44-cli/references/dashboard.md b/evals/fixtures/sdk-auth/skills/base44-cli/references/dashboard.md new file mode 100644 index 0000000..95a534a --- /dev/null +++ b/evals/fixtures/sdk-auth/skills/base44-cli/references/dashboard.md @@ -0,0 +1,35 @@ +# base44 dashboard + +Opens the Base44 app dashboard in your default web browser. + +## Syntax + +```bash +npx base44 dashboard +``` + +## Options + +This command has no options. + +## What It Does + +1. Reads the project's app ID from `base44/.app.jsonc` +2. Opens the dashboard URL in your default browser + +## Example + +```bash +# Open dashboard for current project +npx base44 dashboard +``` + +## Requirements + +- Must be run from a linked Base44 project directory (contains `base44/.app.jsonc`) +- Must be authenticated (run `npx base44 login` first) + +## Notes + +- The dashboard provides a web interface to manage your app's entities, functions, users, and settings +- If you're not in a project directory, the command will fail with an error diff --git a/evals/fixtures/sdk-auth/skills/base44-cli/references/deploy.md b/evals/fixtures/sdk-auth/skills/base44-cli/references/deploy.md new file mode 100644 index 0000000..500526f --- /dev/null +++ b/evals/fixtures/sdk-auth/skills/base44-cli/references/deploy.md @@ -0,0 +1,86 @@ +# base44 deploy + +Deploys all project resources (entities, functions, and site) to Base44 in a single command. + +## Syntax + +```bash +npx base44 deploy [options] +``` + +## Options + +| Option | Description | +|--------|-------------| +| `-y, --yes` | Skip confirmation prompt | + +## What It Deploys + +The command automatically detects and deploys: + +1. **Entities** - All `.jsonc` files in `base44/entities/` +2. **Functions** - All functions in `base44/functions/` +3. **Agents** - All agent configurations in `base44/agents/` +4. **Site** - Built files from `site.outputDirectory` (if configured) + +## Examples + +```bash +# Interactive mode - shows what will be deployed and asks for confirmation +npx base44 deploy + +# Non-interactive - skip confirmation (for CI/CD or agent use) +npx base44 deploy -y +``` + +## Typical Workflow + +```bash +# 1. Make your changes (entities, functions, frontend code) + +# 2. Build the frontend (if you have one) +npm run build + +# 3. Deploy everything +npx base44 deploy -y +``` + +## What It Does + +1. Reads project configuration from `base44/config.jsonc` +2. Detects available resources (entities, functions, site) +3. Shows a summary of what will be deployed +4. Asks for confirmation (unless `-y` flag is used) +5. Deploys all resources in sequence: + - Pushes entity schemas + - Deploys functions + - Pushes agent configurations + - Uploads site files +6. Displays the dashboard URL and app URL (if site was deployed) + +## Requirements + +- Must be run from a linked Base44 project directory +- Must be authenticated (run `npx base44 login` first) +- For site deployment, must run `npm run build` first + +## Output + +After successful deployment: +- **Dashboard**: Link to your app's management dashboard +- **App URL**: Your deployed site's public URL (if site was included) + +## Notes + +- If no resources are found, the command exits with a message +- Use individual commands (`entities push`, `functions deploy`, `site deploy`) if you only want to deploy specific resources +- The site must be built before deployment - this command does not run `npm run build` for you + +## Related Commands + +| Command | Description | +|---------|-------------| +| `base44 entities push` | Push only entities | +| `base44 functions deploy` | Deploy only functions | +| `base44 agents push` | Push only agents | +| `base44 site deploy` | Deploy only the site | diff --git a/evals/fixtures/sdk-auth/skills/base44-cli/references/entities-create.md b/evals/fixtures/sdk-auth/skills/base44-cli/references/entities-create.md new file mode 100644 index 0000000..29b40aa --- /dev/null +++ b/evals/fixtures/sdk-auth/skills/base44-cli/references/entities-create.md @@ -0,0 +1,552 @@ +# Creating Entities + +Base44 entities are defined locally in your project and then pushed to the Base44 backend. + +## Critical: File Naming + +Entity files MUST use kebab-case naming: `{kebab-case-name}.jsonc` + +| Entity Name | File Name | +|-------------|-----------| +| `Task` | `task.jsonc` | +| `TeamMember` | `team-member.jsonc` | +| `ActivityLog` | `activity-log.jsonc` | + +WRONG: `TeamMember.jsonc`, `teamMember.jsonc` +RIGHT: `team-member.jsonc` + +## Table of Contents + +- [Creating Entities](#creating-entities) + - [Entity Directory](#entity-directory) + - [How to Create an Entity](#how-to-create-an-entity) + - [Entity Schema Structure](#entity-schema-structure) + - [Supported Field Types](#supported-field-types) + - [Field Properties](#field-properties) + - [Complete Example](#complete-example) + - [Naming Conventions](#naming-conventions) + - [Relationships Between Entities](#relationships-between-entities) + - [Row Level Security (RLS)](#row-level-security-rls) + - [Field Level Security (FLS)](#field-level-security-fls) + - [Pushing Entities](#pushing-entities) + +## Entity Directory + +All entity definitions must be placed in the `base44/entities/` folder in your project root. Each entity is defined in its own `.jsonc` file. + +Example structure: +``` +my-app/ + base44/ + entities/ + user.jsonc + product.jsonc + order.jsonc +``` + +## How to Create an Entity + +1. Create a new `.jsonc` file in the `base44/entities/` directory +2. Define your entity schema following the structure below +3. Push the changes to Base44 using the CLI + +## Entity Schema Structure + +Each entity file follows a JSON Schema-like structure: + +```jsonc +{ + "name": "EntityName", // PascalCase entity name + "type": "object", // Always "object" + "properties": { + // Define your fields here + }, + "required": ["field1"] // Array of required field names +} +``` + +### Common Mistake: Nested Schema Property + +**WRONG** - Do NOT wrap properties in a `schema` object: +```jsonc +{ + "name": "Task", + "description": "A task entity", + "schema": { // ❌ WRONG - don't use nested "schema" + "type": "object", + "properties": { ... } + } +} +``` + +**CORRECT** - Put `type` and `properties` at the top level: +```jsonc +{ + "name": "Task", + "description": "A task entity", + "type": "object", // ✅ CORRECT - top level + "properties": { ... } // ✅ CORRECT - top level +} +``` + +This is a common mistake that will cause "Invalid schema: Schema must have a 'type' field" errors when pushing entities. + +## Supported Field Types + +### String + +Basic text field: +```jsonc +{ + "title": { + "type": "string", + "description": "Task title" + } +} +``` + +With format: +```jsonc +{ + "due_date": { + "type": "string", + "format": "date", + "description": "Due date" + } +} +``` + +Available formats: `date`, `date-time`, `time`, `email`, `uri`, `hostname`, `ipv4`, `ipv6`, `uuid`, `file`, `regex`, `richtext` + +### String with Enum + +Constrained to specific values: +```jsonc +{ + "status": { + "type": "string", + "enum": ["todo", "in_progress", "done"], + "default": "todo", + "description": "Current status" + } +} +``` + +### Number + +```jsonc +{ + "position": { + "type": "number", + "description": "Position for ordering" + } +} +``` + +### Integer + +For whole numbers only: +```jsonc +{ + "quantity": { + "type": "integer", + "description": "Item quantity", + "minimum": 0, + "maximum": 1000 + } +} +``` + +### Binary + +For file/blob data: +```jsonc +{ + "attachment": { + "type": "binary", + "description": "File attachment" + } +} +``` + +### Boolean + +```jsonc +{ + "notify_on_change": { + "type": "boolean", + "default": true, + "description": "Enable notifications" + } +} +``` + +### Array of Strings + +```jsonc +{ + "labels": { + "type": "array", + "items": { "type": "string" }, + "description": "Task labels/tags" + } +} +``` + +### Array of Objects + +```jsonc +{ + "attachments": { + "type": "array", + "description": "File attachments", + "items": { + "type": "object", + "properties": { + "name": { "type": "string" }, + "url": { "type": "string" }, + "type": { "type": "string" } + } + } + } +} +``` + +## Field Properties + +| Property | Description | +| ------------- | ---------------------------------------------------------------------------------------- | +| `type` | Data type: `string`, `number`, `integer`, `boolean`, `array`, `object`, `binary` | +| `description` | Human-readable description of the field | +| `enum` | Array of allowed values (for strings) | +| `enumNames` | Human-readable labels for enum values (same order as `enum`) | +| `default` | Default value when not provided | +| `format` | Format hint: `date`, `date-time`, `time`, `email`, `uri`, `hostname`, `ipv4`, `ipv6`, `uuid`, `file`, `regex`, `richtext` | +| `items` | Schema for array items | +| `properties` | Nested properties for object types | +| `$ref` | Reference to another schema definition | +| `minLength` | Minimum string length | +| `maxLength` | Maximum string length | +| `pattern` | Regex pattern for string validation | +| `minimum` | Minimum value for numbers | +| `maximum` | Maximum value for numbers | +| `rls` | Field-level security rules (see Field Level Security section) | + +## Complete Example + +Here's a complete entity definition for a Task: + +```jsonc +{ + "name": "Task", + "type": "object", + "properties": { + "title": { + "type": "string", + "description": "Task title" + }, + "description": { + "type": "string", + "description": "Task description" + }, + "status": { + "type": "string", + "enum": ["todo", "in_progress", "done"], + "default": "todo", + "description": "Current status of the task" + }, + "board_id": { + "type": "string", + "description": "Board this task belongs to" + }, + "assignee_email": { + "type": "string", + "description": "Email of assigned user" + }, + "priority": { + "type": "string", + "enum": ["low", "medium", "high"], + "default": "medium", + "description": "Task priority" + }, + "due_date": { + "type": "string", + "format": "date", + "description": "Due date" + }, + "labels": { + "type": "array", + "items": { "type": "string" }, + "description": "Task labels/tags" + } + }, + "required": ["title"] +} +``` + +## Naming Conventions + +- **Entity name**: Use PascalCase with alphanumeric characters only (e.g., `Task`, `TeamMember`, `ActivityLog`) + - Must match pattern: `/^[a-zA-Z0-9]+$/` + - Valid: `Task`, `TeamMember`, `Order123` + - Invalid: `Team_Member`, `Team-Member`, `Team Member` +- **File name**: Use kebab-case matching the entity (e.g., `task.jsonc`, `team-member.jsonc`, `activity-log.jsonc`) +- **Field names**: Use snake_case (e.g., `board_id`, `user_email`, `due_date`) + +## Relationships Between Entities + +To create relationships between entities, use ID reference fields: + +```jsonc +{ + "board_id": { + "type": "string", + "description": "Board this task belongs to" + }, + "team_id": { + "type": "string", + "description": "Associated team ID" + } +} +``` + +## Row Level Security (RLS) + +Row Level Security (RLS) controls which records users can access based on their identity and attributes. RLS rules are defined per entity inside the `rls` field of the schema. + +**Important:** If no RLS is defined, all records are accessible to all users. + +### RLS Operations + +RLS supports five operations: + +| Operation | Description | +|-----------|-------------| +| `create` | Control who can add new records | +| `read` | Control who can view records | +| `update` | Control who can modify records | +| `delete` | Control who can remove records | +| `write` | Shorthand for `create`, `update`, and `delete` combined | + +### Permission Values + +Each operation accepts one of the following values: + +1. **`true`** - Allow all users (including anonymous/unauthenticated) +2. **`false`** - Block all users +3. **Condition object** - Allow users matching the condition + +### Template Variables + +Use template variables to reference the current user's attributes: + +| Template | Description | +|----------|-------------| +| `{{user.id}}` | The user's ID | +| `{{user.email}}` | The user's email | +| `{{user.role}}` | The user's role | +| `{{user.data.field_name}}` | Custom field from the user's `data` object | + +### Built-in Entity Attributes + +Every entity record has these built-in attributes available for RLS rules: + +| Attribute | Description | +|-----------|-------------| +| `id` | Unique record identifier | +| `created_date` | Timestamp when record was created | +| `updated_date` | Timestamp when record was last updated | +| `created_by` | Email of the user who created the record | + +### Rule Types + +There are two condition types you can use: + +**1. Entity-to-user comparison** - Compare record fields to the current user's values: +```jsonc +{ + "created_by": "{{user.email}}" +} +``` + +**2. User condition check** - Check user properties directly using `user_condition`: +```jsonc +{ + "user_condition": { "role": "admin" } +} +``` + +**Important limitations:** +- `user_condition` only supports **simple equality** (e.g., `{ "role": "admin" }`) +- For `data.*` field comparisons, you can use operators: `$in`, `$nin`, `$ne`, `$all` +- Logical operators `$or`, `$and`, `$nor` are available for combining conditions +- You cannot filter by entity field values directly (e.g., `{"status": "published"}`) +- Only user-related conditions are allowed + +### RLS Examples + +**Owner-only access:** +```jsonc +{ + "created_by": "{{user.email}}" +} +``` + +**Department-based access:** +```jsonc +{ + "data.department": "{{user.data.department}}" +} +``` + +**Admin-only access:** +```jsonc +{ + "user_condition": { "role": "admin" } +} +``` + +**Complete RLS configuration:** +```jsonc +{ + "name": "Task", + "type": "object", + "properties": { + "title": { + "type": "string", + "description": "Task title" + }, + "status": { + "type": "string", + "enum": ["todo", "in_progress", "done"], + "default": "todo" + } + }, + "required": ["title"], + "rls": { + "create": true, + "read": { "created_by": "{{user.email}}" }, + "update": { "created_by": "{{user.email}}" }, + "delete": { "created_by": "{{user.email}}" } + } +} +``` + +### Common RLS Patterns + +**Public create, admin-only management (e.g., contact forms, waitlists):** +```jsonc +{ + "rls": { + "create": true, + "read": { "user_condition": { "role": "admin" } }, + "update": { "user_condition": { "role": "admin" } }, + "delete": { "user_condition": { "role": "admin" } } + } +} +``` + +**Owner-only access:** +```jsonc +{ + "rls": { + "create": true, + "read": { "created_by": "{{user.email}}" }, + "update": { "created_by": "{{user.email}}" }, + "delete": { "created_by": "{{user.email}}" } + } +} +``` + +**Logged-in users only:** +```jsonc +{ + "rls": { + "create": { "user_condition": { "id": "{{user.id}}" } }, + "read": true, + "update": { "created_by": "{{user.email}}" }, + "delete": { "created_by": "{{user.email}}" } + } +} +``` + +### Limitations + +- **user_condition is equality only:** `user_condition` only supports exact match (e.g., `{ "role": "admin" }`) - no operators +- **No comparison operators on user_condition:** `$gt`, `$lt`, `$regex`, `$expr`, `$where` are NOT supported for user conditions +- **No entity field filtering:** Cannot filter by entity field values (e.g., `{"status": "published"}`) +- **No deeply nested templates:** Templates like `{{user.data.profile.department}}` may not work + +**Supported operators:** +- **Logical operators:** `$or`, `$and`, `$nor` for combining multiple conditions +- **Field operators (for `data.*` fields only):** `$in`, `$nin`, `$ne`, `$all` + +### Complex Access Patterns + +For complex access patterns that require multiple conditions (e.g., "owner OR admin"), you have two options: + +1. **Use the Base44 Dashboard UI** - The dashboard allows adding multiple rules per operation with OR logic +2. **Use separate entities** - Split data into multiple entities with different access rules +3. **Use backend functions** - Implement custom access logic in backend functions + +## Field Level Security (FLS) + +Field Level Security allows you to control access to individual fields within an entity. FLS rules are defined within each field's schema using the `rls` property. + +### FLS Operations + +FLS supports the same operations as entity-level RLS: + +| Operation | Description | +|-----------|-------------| +| `create` | Control who can set this field when creating records | +| `read` | Control who can view this field | +| `update` | Control who can modify this field | +| `delete` | Control who can clear this field | +| `write` | Shorthand for `create`, `update`, and `delete` combined | + +### FLS Example + +```jsonc +{ + "name": "Employee", + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "Employee name" + }, + "salary": { + "type": "number", + "description": "Employee salary", + "rls": { + "read": { "user_condition": { "role": "hr" } }, + "update": { "user_condition": { "role": "hr" } } + } + }, + "department": { + "type": "string", + "description": "Department name" + } + }, + "required": ["name"] +} +``` + +In this example, only users with the `hr` role can read or update the `salary` field. All users with access to the entity can read/update other fields. + +### FLS Notes + +- If no field-level RLS is defined, the field inherits the entity-level RLS rules +- FLS rules follow the same condition format as entity-level RLS +- Use FLS for sensitive fields like salary, SSN, or internal notes + +## Pushing Entities + +The `entities push` command will push all entities that exist in the `base44/entities` folder. + +```bash +npx base44 entities push +``` + +For more details on the push command, see [entities-push.md](entities-push.md). diff --git a/evals/fixtures/sdk-auth/skills/base44-cli/references/entities-push.md b/evals/fixtures/sdk-auth/skills/base44-cli/references/entities-push.md new file mode 100644 index 0000000..63b92d7 --- /dev/null +++ b/evals/fixtures/sdk-auth/skills/base44-cli/references/entities-push.md @@ -0,0 +1,71 @@ +# base44 entities push + +Push local entity definitions to Base44. + +## Syntax + +```bash +npx base44 entities push +``` + +## Authentication + +**Required**: Yes. If not authenticated, you'll be prompted to login first. + +## What It Does + +1. Pushes all entities that exist in the `base44/entities` folder +2. Validates that entities exist in the folder +3. Displays the count of entities to be pushed +4. Uploads entities to the Base44 backend +5. Reports the results: created, updated, and deleted entities + +## Prerequisites + +- Must be run from a Base44 project directory +- Project must have entity definitions in the `base44/entities` folder + +## Output + +```bash +$ npx base44 entities push + +Found 3 entities to push +Pushing entities to Base44... + +Created: User, Post +Updated: Comment +Deleted: OldEntity + +✓ Entities pushed successfully +``` + +## Entity Synchronization + +The push operation synchronizes your local entity schema with Base44: + +- **Created**: New entities that didn't exist in Base44 +- **Updated**: Existing entities with modified schema or configuration +- **Deleted**: Entities that were removed from your local configuration + +## Error Handling + +If no entities are found in your project: +```bash +$ npx base44 entities push +No entities found in project +``` + +## Use Cases + +- After defining new entities in your project +- When modifying existing entity schemas +- To sync entity changes before deploying +- As part of your development workflow when data models change + +## Notes + +- This command syncs the entity schema/structure, not the actual data +- Changes are applied to your Base44 project immediately +- Make sure to test entity changes in a development environment first +- Entity definitions are located in the `base44/entities/` directory diff --git a/evals/fixtures/sdk-auth/skills/base44-cli/references/functions-create.md b/evals/fixtures/sdk-auth/skills/base44-cli/references/functions-create.md new file mode 100644 index 0000000..c523cdc --- /dev/null +++ b/evals/fixtures/sdk-auth/skills/base44-cli/references/functions-create.md @@ -0,0 +1,229 @@ +# Creating Functions + +Base44 functions are serverless backend functions that run on Deno. They are defined locally in your project and deployed to the Base44 backend. + +## Function Directory + +All function definitions must be placed in the `base44/functions/` folder in your project. Each function lives in its own subdirectory with a configuration file and entry point. + +Example structure: +``` +my-app/ + base44/ + functions/ + process-order/ + function.jsonc + index.ts + send-notification/ + function.jsonc + index.ts +``` + +## How to Create a Function + +1. Create a new directory in `base44/functions/` with your function name (use kebab-case) +2. Create a `function.jsonc` configuration file in the directory +3. Create the entry point file (e.g., `index.ts`) +4. Deploy the function using the CLI + +## Function Configuration + +Each function requires a `function.jsonc` configuration file: + +```jsonc +{ + "name": "my-function", + "entry": "index.ts" +} +``` + +### Configuration Properties + +| Property | Description | Required | +|----------|-------------|----------| +| `name` | Function name (must match `/^[^.]+$/` - no dots allowed) | Yes | +| `entry` | Entry point file path relative to the function directory (min 1 char) | Yes | + +## Entry Point File + +Functions run on Deno and must export using `Deno.serve()`. Use `npm:` prefix for npm packages. + +```typescript +import { createClientFromRequest } from "npm:@base44/sdk"; + +Deno.serve(async (req) => { + // Get authenticated client from request + const base44 = createClientFromRequest(req); + + // Parse input + const { orderId, action } = await req.json(); + + // Your logic here + const order = await base44.entities.Orders.get(orderId); + + // Return response + return Response.json({ + success: true, + order: order + }); +}); +``` + +### Request Object + +The function receives a standard Deno `Request` object: +- `req.json()` - Parse JSON body +- `req.text()` - Get raw text body +- `req.headers` - Access request headers +- `req.method` - HTTP method + +### Response Object + +Return using `Response.json()` for JSON responses: + +```typescript +// Success response +return Response.json({ data: result }); + +// Error response with status code +return Response.json({ error: "Something went wrong" }, { status: 400 }); + +// Not found +return Response.json({ error: "Order not found" }, { status: 404 }); +``` + +## Complete Example + +### Directory Structure +``` +base44/ + functions/ + process-order/ + function.jsonc + index.ts +``` + +### function.jsonc +```jsonc +{ + "name": "process-order", + "entry": "index.ts" +} +``` + +### index.ts +```typescript +import { createClientFromRequest } from "npm:@base44/sdk"; + +Deno.serve(async (req) => { + try { + const base44 = createClientFromRequest(req); + const { orderId } = await req.json(); + + // Validate input + if (!orderId) { + return Response.json( + { error: "Order ID is required" }, + { status: 400 } + ); + } + + // Fetch and process the order + const order = await base44.entities.Orders.get(orderId); + if (!order) { + return Response.json( + { error: "Order not found" }, + { status: 404 } + ); + } + + return Response.json({ + success: true, + orderId: order.id, + processedAt: new Date().toISOString() + }); + + } catch (error) { + return Response.json( + { error: error.message }, + { status: 500 } + ); + } +}); +``` + +## Using Service Role Access + +For admin-level operations, use `asServiceRole`: + +```typescript +import { createClientFromRequest } from "npm:@base44/sdk"; + +Deno.serve(async (req) => { + const base44 = createClientFromRequest(req); + + // Check user is authenticated + const user = await base44.auth.me(); + if (!user) { + return Response.json({ error: "Unauthorized" }, { status: 401 }); + } + + // Use service role for admin operations + const allOrders = await base44.asServiceRole.entities.Orders.list(); + + return Response.json({ orders: allOrders }); +}); +``` + +## Using Secrets + +Access environment variables configured in the app dashboard: + +```typescript +Deno.serve(async (req) => { + // Access environment variables (configured in app settings) + const apiKey = Deno.env.get("STRIPE_API_KEY"); + + const response = await fetch("https://api.stripe.com/v1/charges", { + headers: { + "Authorization": `Bearer ${apiKey}` + } + }); + + return Response.json(await response.json()); +}); +``` + +## Naming Conventions + +- **Directory name**: Use kebab-case (e.g., `process-order`, `send-notification`) +- **Function name**: Match the directory name, must match pattern `/^[^.]+$/` (no dots allowed) + - Valid: `process-order`, `send_notification`, `myFunction` + - Invalid: `process.order`, `send.notification.v2` +- **Entry file**: Typically `index.ts` or `index.js` + +## Deploying Functions + +After creating your function, deploy it to Base44: + +```bash +npx base44 functions deploy +``` + +For more details on deploying, see [functions-deploy.md](functions-deploy.md). + +## Notes + +- Functions run on Deno runtime, not Node.js +- Use `npm:` prefix for npm packages (e.g., `npm:@base44/sdk`) +- Use `createClientFromRequest(req)` to get a client that inherits the caller's auth context +- Configure secrets via app dashboard for API keys +- Make sure to handle errors gracefully and return appropriate HTTP status codes + +## Common Mistakes + +| Wrong | Correct | Why | +|-------|---------|-----| +| `functions/myFunction.js` (single file) | `functions/my-function/index.ts` + `function.jsonc` | Functions require subdirectory with config | +| `import { ... } from "@base44/sdk"` | `import { ... } from "npm:@base44/sdk"` | Deno requires `npm:` prefix for npm packages | +| `MyFunction` or `myFunction` directory | `my-function` directory | Use kebab-case for directory names | diff --git a/evals/fixtures/sdk-auth/skills/base44-cli/references/functions-deploy.md b/evals/fixtures/sdk-auth/skills/base44-cli/references/functions-deploy.md new file mode 100644 index 0000000..f89d796 --- /dev/null +++ b/evals/fixtures/sdk-auth/skills/base44-cli/references/functions-deploy.md @@ -0,0 +1,78 @@ +# base44 functions deploy + +Deploy local function definitions to Base44. + +## Syntax + +```bash +npx base44 functions deploy +``` + +## Authentication + +**Required**: Yes. If not authenticated, you'll be prompted to login first. + +## What It Does + +1. Scans the `base44/functions/` directory for function definitions +2. Validates that functions exist and have valid configurations +3. Displays the count of functions to be deployed +4. Uploads function code and configuration to Base44 +5. Reports the results: deployed and deleted functions + +## Prerequisites + +- Must be run from a Base44 project directory +- Project must have function definitions in the `base44/functions/` folder +- Each function must have a valid `function.jsonc` config file + +## Output + +```bash +$ npx base44 functions deploy + +Found 2 functions to deploy +Deploying functions to Base44... + +Deployed: process-order, send-notification +Deleted: old-function + +✓ Functions deployed successfully +``` + +## Function Synchronization + +The deploy operation synchronizes your local functions with Base44: + +- **Deployed**: Functions that were created or updated +- **Deleted**: Functions that were removed from your local configuration + +## Error Handling + +If no functions are found in your project: +```bash +$ npx base44 functions deploy +No functions found. Create functions in the 'functions' directory. +``` + +If a function has configuration errors: +```bash +$ npx base44 functions deploy +Function deployment errors: +'my-function' function: Entry point cannot be empty +``` + +## Use Cases + +- After creating new functions in your project +- When modifying existing function code or configuration +- To sync function changes before testing +- As part of your development workflow when backend logic changes + +## Notes + +- This command deploys the function code and configuration +- Changes are applied to your Base44 project immediately +- Make sure to test functions in a development environment first +- Function definitions are located in the `base44/functions/` directory +- For how to create functions, see [functions-create.md](functions-create.md) diff --git a/evals/fixtures/sdk-auth/skills/base44-cli/references/link.md b/evals/fixtures/sdk-auth/skills/base44-cli/references/link.md new file mode 100644 index 0000000..0b009ce --- /dev/null +++ b/evals/fixtures/sdk-auth/skills/base44-cli/references/link.md @@ -0,0 +1,81 @@ +# base44 link + +Links an existing local Base44 project to a Base44 app in the cloud. Use this when you have a `base44/config.jsonc` but haven't connected it to a Base44 app yet. + +## Critical: When to Use Link vs Create + +| Scenario | Command | +|----------|---------| +| Starting fresh, no `base44/` folder | `npx base44 create` | +| Have `base44/config.jsonc` but no `.app.jsonc` | `npx base44 link` | +| Project already linked (has `.app.jsonc`) | Already done, use `deploy` | + +## Syntax + +```bash +npx base44 link [options] +``` + +## Options + +| Option | Description | Required | +|--------|-------------|----------| +| `-c, --create` | Create a new project (skip selection prompt) | No | +| `-n, --name ` | Project name (required when `--create` is used) | With `--create` | +| `-d, --description ` | Project description | No | +| `-p, --projectId ` | Project ID to link to an existing project (skip selection prompt) | No | + +## Non-Interactive Mode + +For CI/CD or agent use: + +**Create a new project:** +```bash +npx base44 link --create --name my-app +``` + +**Link to an existing project:** +```bash +npx base44 link --projectId +``` + +WRONG: `npx base44 link --create` (missing --name) +WRONG: `npx base44 link --create --projectId ` (cannot use both) +RIGHT: `npx base44 link --create --name my-app` +RIGHT: `npx base44 link --projectId ` + +## Examples + +```bash +# Interactive mode - prompts for project details +npx base44 link + +# Non-interactive - create and link in one step +npx base44 link --create --name my-app + +# With description +npx base44 link --create --name my-app --description "My awesome app" + +# Link to a specific existing project by ID +npx base44 link --projectId abc123 +``` + +## What It Does + +1. Finds the `base44/config.jsonc` in the current directory (or parent directories) +2. Verifies no `.app.jsonc` exists (project not already linked) +3. Either: + - Creates a new Base44 app in the cloud (with `--create`), OR + - Links to an existing project (with `--projectId` or interactive selection) +4. Writes the app ID to `base44/.app.jsonc` + +## Requirements + +- Must have `base44/config.jsonc` in the project +- Must NOT have `base44/.app.jsonc` (use `deploy` if already linked) +- Must be authenticated (run `npx base44 login` first) + +## Notes + +- After linking, you can deploy resources with `npx base44 deploy` +- The `.app.jsonc` file should be git-ignored (contains your app ID) diff --git a/evals/fixtures/sdk-auth/skills/base44-cli/references/rls-examples.md b/evals/fixtures/sdk-auth/skills/base44-cli/references/rls-examples.md new file mode 100644 index 0000000..7d3ef42 --- /dev/null +++ b/evals/fixtures/sdk-auth/skills/base44-cli/references/rls-examples.md @@ -0,0 +1,463 @@ +# RLS Examples + +Practical Row-Level Security patterns for common application types. + +**Important:** Base44 RLS supports: +- **Logical operators:** `$or`, `$and`, `$nor` for combining conditions +- **Field operators (for `data.*` fields):** `$in`, `$nin`, `$ne`, `$all` +- **user_condition:** Equality only (no operators) + +## Contents +- [Simple Patterns (JSON Schema)](#simple-patterns-json-schema) +- [Using Operators](#using-operators) +- [Field-Level Security Examples](#field-level-security-examples) +- [Complex Patterns (Dashboard UI or Backend)](#complex-patterns-dashboard-ui-or-backend) +- [Best Practices](#best-practices) + +--- + +## Simple Patterns (JSON Schema) + +These patterns work with the JSON schema RLS format. + +### Todo App - Owner-only access + +Users see and manage only their own tasks. + +```jsonc +{ + "name": "Task", + "type": "object", + "properties": { + "title": { "type": "string" }, + "description": { "type": "string" }, + "completed": { "type": "boolean" }, + "priority": { "type": "string", "enum": ["low", "medium", "high"] }, + "due_date": { "type": "string", "format": "date" } + }, + "rls": { + "create": true, + "read": { "created_by": "{{user.email}}" }, + "update": { "created_by": "{{user.email}}" }, + "delete": { "created_by": "{{user.email}}" } + } +} +``` + +### Contact Form - Public create, admin-only read + +Anyone can submit, only admins can view submissions. + +```jsonc +{ + "name": "ContactSubmission", + "type": "object", + "properties": { + "name": { "type": "string" }, + "email": { "type": "string", "format": "email" }, + "message": { "type": "string" } + }, + "rls": { + "create": true, + "read": { "user_condition": { "role": "admin" } }, + "update": { "user_condition": { "role": "admin" } }, + "delete": { "user_condition": { "role": "admin" } } + } +} +``` + +### User Profile - Self-management + +Users can only access their own profile. + +```jsonc +{ + "name": "UserProfile", + "type": "object", + "properties": { + "name": { "type": "string" }, + "avatar_url": { "type": "string" }, + "bio": { "type": "string" }, + "preferences": { "type": "object" } + }, + "rls": { + "create": true, + "read": { "created_by": "{{user.email}}" }, + "update": { "created_by": "{{user.email}}" }, + "delete": { "created_by": "{{user.email}}" } + } +} +``` + +### Department Data - Same department access + +Users can only see records from their department. + +```jsonc +{ + "name": "DepartmentAnnouncement", + "type": "object", + "properties": { + "title": { "type": "string" }, + "content": { "type": "string" }, + "department": { "type": "string" } + }, + "rls": { + "create": { "user_condition": { "role": "manager" } }, + "read": { "data.department": "{{user.data.department}}" }, + "update": { "user_condition": { "role": "manager" } }, + "delete": { "user_condition": { "role": "admin" } } + } +} +``` + +### Subscription - Admin-managed, user-readable via email field + +```jsonc +{ + "name": "Subscription", + "type": "object", + "properties": { + "user_email": { "type": "string" }, + "tier": { "type": "string", "enum": ["free", "basic", "pro", "enterprise"] }, + "credits": { "type": "number" }, + "renewal_date": { "type": "string", "format": "date" } + }, + "rls": { + "create": { "user_condition": { "role": "admin" } }, + "read": { "data.user_email": "{{user.email}}" }, + "update": { "user_condition": { "role": "admin" } }, + "delete": { "user_condition": { "role": "admin" } } + } +} +``` + +**Note:** This pattern only allows users to read their own subscription. Admins need to use the Dashboard UI to configure additional read access for themselves. + +### Private Data - Owner-only + +```jsonc +{ + "name": "PrivateNotes", + "type": "object", + "properties": { + "title": { "type": "string" }, + "content": { "type": "string" }, + "tags": { "type": "array", "items": { "type": "string" } } + }, + "rls": { + "create": true, + "read": { "created_by": "{{user.email}}" }, + "update": { "created_by": "{{user.email}}" }, + "delete": { "created_by": "{{user.email}}" } + } +} +``` + +### Public Read, Authenticated Write + +Anyone can read, only logged-in users can create/edit their own records. + +```jsonc +{ + "name": "BlogPost", + "type": "object", + "properties": { + "title": { "type": "string" }, + "content": { "type": "string" }, + "author_email": { "type": "string" } + }, + "rls": { + "create": true, + "read": true, + "update": { "created_by": "{{user.email}}" }, + "delete": { "created_by": "{{user.email}}" } + } +} +``` + +--- + +## Using Operators + +### Logical Operators + +Combine multiple conditions using `$or`, `$and`, or `$nor`: + +**Owner OR Admin access:** +```jsonc +{ + "name": "Document", + "type": "object", + "properties": { + "title": { "type": "string" }, + "content": { "type": "string" } + }, + "rls": { + "create": true, + "read": { + "$or": [ + { "created_by": "{{user.email}}" }, + { "user_condition": { "role": "admin" } } + ] + }, + "update": { + "$or": [ + { "created_by": "{{user.email}}" }, + { "user_condition": { "role": "admin" } } + ] + }, + "delete": { "user_condition": { "role": "admin" } } + } +} +``` + +**Multiple roles with $or:** +```jsonc +{ + "rls": { + "read": { + "$or": [ + { "user_condition": { "role": "admin" } }, + { "user_condition": { "role": "manager" } }, + { "user_condition": { "role": "hr" } } + ] + } + } +} +``` + +### Field Operators for data.* Fields + +Use `$in`, `$nin`, `$ne`, `$all` for comparing entity data fields: + +**Access based on tags ($in):** +```jsonc +{ + "rls": { + "read": { + "data.category": { "$in": ["public", "shared"] } + } + } +} +``` + +**Exclude specific statuses ($nin):** +```jsonc +{ + "rls": { + "read": { + "data.status": { "$nin": ["archived", "deleted"] } + } + } +} +``` + +**Not equal ($ne):** +```jsonc +{ + "rls": { + "read": { + "data.visibility": { "$ne": "private" } + } + } +} +``` + +**All tags must match ($all):** +```jsonc +{ + "rls": { + "read": { + "data.required_tags": { "$all": ["approved", "reviewed"] } + } + } +} +``` + +### Combining Logical and Field Operators + +```jsonc +{ + "rls": { + "read": { + "$and": [ + { "data.status": { "$ne": "draft" } }, + { + "$or": [ + { "created_by": "{{user.email}}" }, + { "data.visibility": "public" } + ] + } + ] + } + } +} +``` + +--- + +## Field-Level Security Examples + +Control access to specific fields within an entity. + +### Sensitive Salary Field + +```jsonc +{ + "name": "Employee", + "type": "object", + "properties": { + "name": { "type": "string" }, + "email": { "type": "string", "format": "email" }, + "salary": { + "type": "number", + "description": "Annual salary", + "rls": { + "read": { "user_condition": { "role": "hr" } }, + "write": { "user_condition": { "role": "hr" } } + } + }, + "performance_notes": { + "type": "string", + "description": "Manager notes", + "rls": { + "read": { + "$or": [ + { "user_condition": { "role": "manager" } }, + { "user_condition": { "role": "hr" } } + ] + }, + "write": { "user_condition": { "role": "manager" } } + } + } + } +} +``` + +### Admin-Only Internal Fields + +```jsonc +{ + "name": "Order", + "type": "object", + "properties": { + "order_number": { "type": "string" }, + "total": { "type": "number" }, + "internal_notes": { + "type": "string", + "description": "Internal processing notes", + "rls": { + "read": { "user_condition": { "role": "admin" } }, + "write": { "user_condition": { "role": "admin" } } + } + }, + "profit_margin": { + "type": "number", + "description": "Profit margin percentage", + "rls": { + "read": { "user_condition": { "role": "admin" } }, + "write": false + } + } + } +} +``` + +--- + +## Complex Patterns (Dashboard UI or Backend) + +Some patterns may still require the Dashboard UI or backend functions. + +### Bidirectional Relationships (e.g., Friendships, Matches) + +**Requirement:** Either party in a relationship should have access. + +**Now possible with $or:** +```jsonc +{ + "rls": { + "read": { + "$or": [ + { "data.user_a_email": "{{user.email}}" }, + { "data.user_b_email": "{{user.email}}" } + ] + } + } +} +``` + +**Alternative solutions:** +1. **Entity redesign:** Store two records per relationship (one for each party) +2. **Backend function:** Query with custom logic + +### Complex Business Logic + +**Requirement:** Access depends on multiple entity fields with complex conditions. + +**JSON Schema limitation:** While operators help, very complex business logic may still be hard to express. + +**Solution options:** +1. **Backend function:** Implement custom access logic +2. **Combine simpler rules:** Break complex rules into simpler entity-level and field-level rules + +--- + +## Best Practices + +### Security Strategy + +Use a combination of entity-level RLS and field-level security: + +| Data Type | Approach | Example | +|-----------|----------|---------| +| User-editable | Entity RLS: Owner-only | UserProfile with `created_by` check | +| Sensitive fields | Field-level RLS | Salary field with HR role check | +| Multi-role access | `$or` with user_condition | Admin OR Manager access | +| Conditional access | Field operators | `$in`, `$ne` on data fields | +| Public content | Entity RLS: `read: true` | PublicPost | +| Private content | Entity RLS: Owner-only | PrivateNote | + +### When to Use Each Approach + +| Requirement | Approach | +|-------------|----------| +| Single condition (owner, admin, department) | JSON Schema RLS | +| Multiple OR/AND conditions | JSON Schema RLS with `$or`/`$and` | +| Field value checks with `$in`/`$ne`/etc. | JSON Schema RLS for `data.*` fields | +| Field-level access control | JSON Schema FLS (field-level `rls`) | +| Complex comparison operators (`$gt`, `$lt`) | Backend functions | +| Very complex business logic | Backend functions | + +### Common Role Patterns + +| Role | Typical Access | +|------|----------------| +| `admin` | Full access to all records | +| `moderator` | Read/update access, limited delete | +| `manager` | Department-scoped access | +| `user` | Own records only | + +### Supported Operators Summary + +| Operator | Supported | Notes | +|----------|-----------|-------| +| `$or` | Yes | Combine multiple conditions | +| `$and` | Yes | All conditions must match | +| `$nor` | Yes | None of the conditions match | +| `$in` | Yes | For `data.*` fields only | +| `$nin` | Yes | For `data.*` fields only | +| `$ne` | Yes | For `data.*` fields only | +| `$all` | Yes | For `data.*` fields only | +| `$gt`, `$lt`, `$gte`, `$lte` | No | Use backend functions | +| `$regex` | No | Use backend functions | + +### Limitations Summary + +| Not Supported | Alternative | +|---------------|-------------| +| Operators on `user_condition` | Use equality only for user checks | +| Comparison operators (`$gt`, `$lt`) | Backend functions | +| Regex matching (`$regex`) | Backend functions | +| Cross-entity relationships | Backend functions | diff --git a/evals/fixtures/sdk-auth/skills/base44-cli/references/site-deploy.md b/evals/fixtures/sdk-auth/skills/base44-cli/references/site-deploy.md new file mode 100644 index 0000000..929d302 --- /dev/null +++ b/evals/fixtures/sdk-auth/skills/base44-cli/references/site-deploy.md @@ -0,0 +1,118 @@ +# base44 site deploy + +Deploy built site files to Base44 hosting. + +## Table of Contents + +- [Syntax](#syntax) +- [Authentication](#authentication) +- [Prerequisites](#prerequisites) +- [How It Works](#how-it-works) +- [Interactive Flow](#interactive-flow) +- [Typical Workflow](#typical-workflow) +- [Configuration](#configuration) +- [Error Handling](#error-handling) +- [Use Cases](#use-cases) +- [Notes](#notes) + +## Syntax + +```bash +npx base44 site deploy [options] +``` + +## Options + +| Option | Description | +| ------------ | ------------------------- | +| `-y, --yes` | Skip confirmation prompt | + +Use `-y` flag for non-interactive/automated deployments: + +```bash +npx base44 site deploy -y +``` + +## Authentication + +**Required**: Yes. If not authenticated, you'll be prompted to login first. + +## Prerequisites + +- Must be run from a Base44 project directory +- Project must have `site.outputDirectory` configured in project config +- Site must be built before deploying (run your build command first) +- **SPA only**: Base44 hosting supports Single Page Applications with a single `index.html` entry point. All routes are served from `index.html` (client-side routing). + +## How It Works + +1. Reads project configuration +2. Validates that site configuration exists +3. Prompts for deployment confirmation showing the output directory +4. Creates an archive of site files from the output directory +5. Deploys to Base44 hosting +6. Returns the app URL + +## Interactive Flow + +```bash +$ npx base44 site deploy + +Deploy site from ./dist? (yes/no) yes + +Creating archive... +Uploading to Base44... +Deploying... + +✓ Deployment successful! + +Visit your site at: https://my-app.base44.app +``` + +## Typical Workflow + +```bash +# 1. Build your site using your framework's build command +npm run build + +# 2. Deploy to Base44 +npx base44 site deploy +``` + +## Configuration + +The `site.outputDirectory` in your project configuration should point to where your framework outputs built files: + +- Vite: typically `./dist` +- Next.js: typically `./.next` or `./out` +- Create React App: typically `./build` +- Custom: whatever your build tool outputs to + +## Error Handling + +If site configuration is missing: +```bash +$ npx base44 site deploy +Error: No site configuration found in project +``` + +If you cancel the deployment: +```bash +Deploy site from ./dist? (yes/no) no +Deployment cancelled +``` + +## Use Cases + +- Deploy your site after making changes +- Push new versions of your application +- Deploy after updating content or functionality +- Part of your CI/CD pipeline + +## Notes + +- Always build your site before deploying +- The command deploys whatever is in your output directory +- Make sure your build completed successfully before deploying +- Previous deployments are preserved (versioned) in Base44 +- Deployment is immediate and updates your live site diff --git a/evals/fixtures/sdk-auth/skills/base44-sdk/SKILL.md b/evals/fixtures/sdk-auth/skills/base44-sdk/SKILL.md new file mode 100644 index 0000000..01ddd7f --- /dev/null +++ b/evals/fixtures/sdk-auth/skills/base44-sdk/SKILL.md @@ -0,0 +1,296 @@ +--- +name: base44-sdk +description: "The base44 SDK is the library to communicate with base44 services. In projects, you use it to communicate with remote resources (entities, backend functions, ai agents) and to write backend functions. This skill is the place for learning about available modules and types. When you plan or implement a feature, you must learn this skill" +--- + +# Base44 Coder + +Build apps on the Base44 platform using the Base44 JavaScript SDK. + +## ⚡ IMMEDIATE ACTION REQUIRED - Read This First + +This skill activates on ANY mention of "base44" or when a `base44/` folder exists. **DO NOT read documentation files or search the web before acting.** + +**Your first action MUST be:** +1. Check if `base44/config.jsonc` exists in the current directory +2. If **YES** (existing project scenario): + - This skill (base44-sdk) handles the request + - Implement features using Base44 SDK + - Do NOT use base44-cli unless user explicitly requests CLI commands +3. If **NO** (new project scenario): + - Transfer to base44-cli skill for project initialization + - This skill cannot help until project is initialized + +## When to Use This Skill vs base44-cli + +**Use base44-sdk when:** +- Building features in an **EXISTING** Base44 project +- `base44/config.jsonc` already exists in the project +- Base44 SDK imports are present (`@base44/sdk`) +- Writing JavaScript/TypeScript code using Base44 SDK modules +- Implementing functionality, components, or features +- User mentions: "implement", "build a feature", "add functionality", "write code for" +- User says "create a [type] app" **and** a Base44 project already exists + +**DO NOT USE base44-sdk for:** +- ❌ Initializing new Base44 projects (use `base44-cli` instead) +- ❌ Empty directories without Base44 configuration +- ❌ When user says "create a new Base44 project/app/site" and no project exists +- ❌ CLI commands like `npx base44 create`, `npx base44 deploy`, `npx base44 login` (use `base44-cli`) + +**Skill Dependencies:** +- `base44-sdk` assumes a Base44 project is **already initialized** +- `base44-cli` is a **prerequisite** for `base44-sdk` in new projects +- If user wants to "create an app" and no Base44 project exists, use `base44-cli` first + +**State Check Logic:** +Before selecting this skill, verify: +- IF (user mentions "create/build app" OR "make a project"): + - IF (directory is empty OR no `base44/config.jsonc` exists): + → Use **base44-cli** (project initialization needed) + - ELSE: + → Use **base44-sdk** (project exists, build features) + +## Quick Start + +```javascript +// In Base44-generated apps, base44 client is pre-configured and available + +// CRUD operations +const task = await base44.entities.Task.create({ title: "New task", status: "pending" }); +const tasks = await base44.entities.Task.list(); +await base44.entities.Task.update(task.id, { status: "done" }); + +// Get current user +const user = await base44.auth.me(); +``` + +```javascript +// External apps +import { createClient } from "@base44/sdk"; + +// IMPORTANT: Use 'appId' (NOT 'clientId' or 'id') +const base44 = createClient({ appId: "your-app-id" }); +await base44.auth.loginViaEmailPassword("user@example.com", "password"); +``` + +## ⚠️ CRITICAL: Do Not Hallucinate APIs + +**Before writing ANY Base44 code, verify method names against this table or [QUICK_REFERENCE.md](references/QUICK_REFERENCE.md).** + +Base44 SDK has unique method names. Do NOT assume patterns from Firebase, Supabase, or other SDKs. + +### Authentication - WRONG vs CORRECT + +| ❌ WRONG (hallucinated) | ✅ CORRECT | +|------------------------|-----------| +| `signInWithGoogle()` | `loginWithProvider('google')` | +| `signInWithProvider('google')` | `loginWithProvider('google')` | +| `auth.google()` | `loginWithProvider('google')` | +| `signInWithEmailAndPassword(email, pw)` | `loginViaEmailPassword(email, pw)` | +| `signIn(email, pw)` | `loginViaEmailPassword(email, pw)` | +| `createUser()` / `signUp()` | `register({email, password})` | +| `onAuthStateChanged()` | `me()` (no listener, call when needed) | +| `currentUser` | `await auth.me()` | + +### Functions - WRONG vs CORRECT + +| ❌ WRONG (hallucinated) | ✅ CORRECT | +|------------------------|-----------| +| `functions.call('name', data)` | `functions.invoke('name', data)` | +| `functions.run('name', data)` | `functions.invoke('name', data)` | +| `callFunction('name', data)` | `functions.invoke('name', data)` | +| `httpsCallable('name')(data)` | `functions.invoke('name', data)` | + +### Integrations - WRONG vs CORRECT + +| ❌ WRONG (hallucinated) | ✅ CORRECT | +|------------------------|-----------| +| `ai.generate(prompt)` | `integrations.Core.InvokeLLM({prompt})` | +| `openai.chat(prompt)` | `integrations.Core.InvokeLLM({prompt})` | +| `llm(prompt)` | `integrations.Core.InvokeLLM({prompt})` | +| `sendEmail(to, subject, body)` | `integrations.Core.SendEmail({to, subject, body})` | +| `email.send()` | `integrations.Core.SendEmail({to, subject, body})` | +| `uploadFile(file)` | `integrations.Core.UploadFile({file})` | +| `storage.upload(file)` | `integrations.Core.UploadFile({file})` | + +### Entities - WRONG vs CORRECT + +| ❌ WRONG (hallucinated) | ✅ CORRECT | +|------------------------|-----------| +| `entities.Task.find({...})` | `entities.Task.filter({...})` | +| `entities.Task.findOne(id)` | `entities.Task.get(id)` | +| `entities.Task.insert(data)` | `entities.Task.create(data)` | +| `entities.Task.remove(id)` | `entities.Task.delete(id)` | +| `entities.Task.onChange(cb)` | `entities.Task.subscribe(cb)` | + +## SDK Modules + +| Module | Purpose | Reference | +|--------|---------|-----------| +| `entities` | CRUD operations on data models | [entities.md](references/entities.md) | +| `auth` | Login, register, user management | [auth.md](references/auth.md) | +| `agents` | AI conversations and messages | [base44-agents.md](references/base44-agents.md) | +| `functions` | Backend function invocation | [functions.md](references/functions.md) | +| `integrations` | AI, email, file uploads, custom APIs | [integrations.md](references/integrations.md) | +| `connectors` | OAuth tokens (service role only) | [connectors.md](references/connectors.md) | +| `analytics` | Track custom events and user activity | [analytics.md](references/analytics.md) | +| `appLogs` | Log user activity in app | [app-logs.md](references/app-logs.md) | +| `users` | Invite users to the app | [users.md](references/users.md) | + +For client setup and authentication modes, see [client.md](references/client.md). + +**TypeScript Support:** Each reference file includes a "Type Definitions" section with TypeScript interfaces and types for the module's methods, parameters, and return values. + +## Installation + +Install the Base44 SDK: + +```bash +npm install @base44/sdk +``` + +**Important:** Never assume or hardcode the `@base44/sdk` package version. Always install without a version specifier to get the latest version. + +## Creating a Client (External Apps) + +When creating a client in external apps, **ALWAYS use `appId` as the parameter name**: + +```javascript +import { createClient } from "@base44/sdk"; + +// ✅ CORRECT +const base44 = createClient({ appId: "your-app-id" }); + +// ❌ WRONG - Do NOT use these: +// const base44 = createClient({ clientId: "your-app-id" }); // WRONG +// const base44 = createClient({ id: "your-app-id" }); // WRONG +``` + +**Required parameter:** `appId` (string) - Your Base44 application ID + +**Optional parameters:** +- `token` (string) - Pre-authenticated user token +- `options` (object) - Configuration options + - `options.onError` (function) - Global error handler + +**Example with error handler:** +```javascript +const base44 = createClient({ + appId: "your-app-id", + options: { + onError: (error) => { + console.error("Base44 error:", error); + } + } +}); +``` + +## Module Selection + +**Working with app data?** +- Create/read/update/delete records → `entities` +- Import data from file → `entities.importEntities()` +- Realtime updates → `entities.EntityName.subscribe()` + +**User management?** +- Login/register/logout → `auth` +- Get current user → `auth.me()` +- Update user profile → `auth.updateMe()` +- Invite users → `users.inviteUser()` + +**AI features?** +- Chat with AI agents → `agents` (requires logged-in user) +- Create new conversation → `agents.createConversation()` +- Manage conversations → `agents.getConversations()` +- Generate text/JSON with AI → `integrations.Core.InvokeLLM()` +- Generate images → `integrations.Core.GenerateImage()` + +**Custom backend logic?** +- Run server-side code → `functions.invoke()` +- Need admin access → `base44.asServiceRole.functions.invoke()` + +**External services?** +- Send emails → `integrations.Core.SendEmail()` +- Upload files → `integrations.Core.UploadFile()` +- Custom APIs → `integrations.custom.call()` +- OAuth tokens (Google, Slack) → `connectors` (backend only) + +**Tracking and analytics?** +- Track custom events → `analytics.track()` +- Log page views/activity → `appLogs.logUserInApp()` + +## Common Patterns + +### Filter and Sort Data + +```javascript +const pendingTasks = await base44.entities.Task.filter( + { status: "pending", assignedTo: userId }, // query + "-created_date", // sort (descending) + 10, // limit + 0 // skip +); +``` + +### Protected Routes (check auth) + +```javascript +const user = await base44.auth.me(); +if (!user) { + // Navigate to your custom login page + navigate('/login', { state: { returnTo: window.location.pathname } }); + return; +} +``` + +### Backend Function Call + +```javascript +// Frontend +const result = await base44.functions.invoke("processOrder", { + orderId: "123", + action: "ship" +}); + +// Backend function (Deno) +import { createClientFromRequest } from "npm:@base44/sdk"; + +Deno.serve(async (req) => { + const base44 = createClientFromRequest(req); + const { orderId, action } = await req.json(); + // Process with service role for admin access + const order = await base44.asServiceRole.entities.Orders.get(orderId); + return Response.json({ success: true }); +}); +``` + +### Service Role Access + +Use `asServiceRole` in backend functions for admin-level operations: + +```javascript +// User mode - respects permissions +const myTasks = await base44.entities.Task.list(); + +// Service role - full access (backend only) +const allTasks = await base44.asServiceRole.entities.Task.list(); +const token = await base44.asServiceRole.connectors.getAccessToken("slack"); +``` + +## Frontend vs Backend + +| Capability | Frontend | Backend | +|------------|----------|---------| +| `entities` (user's data) | Yes | Yes | +| `auth` | Yes | Yes | +| `agents` | Yes | Yes | +| `functions.invoke()` | Yes | Yes | +| `integrations` | Yes | Yes | +| `analytics` | Yes | Yes | +| `appLogs` | Yes | Yes | +| `users` | Yes | Yes | +| `asServiceRole.*` | No | Yes | +| `connectors` | No | Yes | + +Backend functions use `Deno.serve()` and `createClientFromRequest(req)` to get a properly authenticated client. diff --git a/evals/fixtures/sdk-auth/skills/base44-sdk/references/QUICK_REFERENCE.md b/evals/fixtures/sdk-auth/skills/base44-sdk/references/QUICK_REFERENCE.md new file mode 100644 index 0000000..d357384 --- /dev/null +++ b/evals/fixtures/sdk-auth/skills/base44-sdk/references/QUICK_REFERENCE.md @@ -0,0 +1,155 @@ +# Base44 SDK Quick Reference + +Compact method signatures for all SDK modules. **Verify against this before writing code.** + +--- + +## Auth (`base44.auth.*`) + +``` +loginViaEmailPassword(email, password, turnstileToken?) → Promise<{access_token, user}> +loginWithProvider('google' | 'microsoft' | 'facebook', fromUrl?) → void +me() → Promise +updateMe(data) → Promise +isAuthenticated() → Promise +logout(redirectUrl?) → void +redirectToLogin(nextUrl) → void # ⚠️ Avoid - prefer custom login UI +register({email, password, turnstile_token?, referral_code?}) → Promise +verifyOtp({email, otpCode}) → Promise +resendOtp(email) → Promise +inviteUser(userEmail, role) → Promise +resetPasswordRequest(email) → Promise +resetPassword({resetToken, newPassword}) → Promise +changePassword({userId, currentPassword, newPassword}) → Promise +setToken(token, saveToStorage?) → void +``` + +--- + +## Entities (`base44.entities.EntityName.*`) + +``` +create(data) → Promise +bulkCreate(dataArray) → Promise +list(sort?, limit?, skip?, fields?) → Promise +filter(query, sort?, limit?, skip?, fields?) → Promise +get(id) → Promise +update(id, data) → Promise +delete(id) → Promise +deleteMany(query) → Promise +importEntities(file) → Promise // frontend only +subscribe(callback) → () => void // returns unsubscribe fn +``` + +**Sort:** Use `-fieldName` for descending (e.g., `-created_date`) + +--- + +## Functions (`base44.functions.*`) + +``` +invoke(functionName, data) → Promise +``` + +**Backend:** Use `base44.asServiceRole.functions.invoke()` for admin access. + +--- + +## Integrations (`base44.integrations.Core.*`) + +``` +InvokeLLM({prompt, add_context_from_internet?, response_json_schema?, file_urls?}) → Promise +GenerateImage({prompt}) → Promise<{url}> +SendEmail({to, subject, body, from_name?}) → Promise +UploadFile({file}) → Promise<{file_url}> +UploadPrivateFile({file}) → Promise<{file_uri}> +CreateFileSignedUrl({file_uri, expires_in?}) → Promise<{signed_url}> +ExtractDataFromUploadedFile({file_url, json_schema}) → Promise +``` + +### Custom Integrations (`base44.integrations.custom.*`) + +``` +call(slug, operationId, {payload?, pathParams?, queryParams?, headers?}?) → Promise<{success, status_code, data}> +``` + +**operationId format:** `"method:/path"` (e.g., `"get:/contacts"`, `"post:/users/{id}"`) + +--- + +## Analytics (`base44.analytics.*`) + +``` +track({eventName, properties?}) → void +``` + +--- + +## App Logs (`base44.appLogs.*`) + +``` +logUserInApp(pageName) → Promise +``` + +--- + +## Users (`base44.users.*`) + +``` +inviteUser(userEmail, role) → Promise // role: 'user' | 'admin' +``` + +--- + +## Connectors (`base44.asServiceRole.connectors.*`) + +**Backend only, service role required.** + +``` +getAccessToken(integrationType) → Promise +``` + +**Types:** `'googlecalendar'`, `'googledrive'`, `'slack'`, `'notion'`, `'salesforce'`, `'hubspot'`, `'linkedin'`, `'tiktok'`, `'github'` + +--- + +## Service Role Access + +**Backend functions only.** Prefix any module with `asServiceRole` for admin access: + +```javascript +base44.asServiceRole.entities.Task.list() +base44.asServiceRole.functions.invoke('name', data) +base44.asServiceRole.connectors.getAccessToken('slack') +``` + +--- + +## Backend Function Template + +```javascript +import { createClientFromRequest } from "@base44/sdk"; + +Deno.serve(async (req) => { + const base44 = createClientFromRequest(req); + const data = await req.json(); + + // User context + const user = await base44.auth.me(); + + // Service role for admin operations + const allRecords = await base44.asServiceRole.entities.Task.list(); + + return Response.json({ success: true }); +}); +``` + +--- + +## Client Initialization (External Apps) + +```javascript +import { createClient } from "@base44/sdk"; + +const base44 = createClient({ appId: "your-app-id" }); // MUST use 'appId' +``` diff --git a/evals/fixtures/sdk-auth/skills/base44-sdk/references/analytics.md b/evals/fixtures/sdk-auth/skills/base44-sdk/references/analytics.md new file mode 100644 index 0000000..d1b2c69 --- /dev/null +++ b/evals/fixtures/sdk-auth/skills/base44-sdk/references/analytics.md @@ -0,0 +1,113 @@ +# Analytics Module + +Track custom events and user activity via `base44.analytics`. + +## Contents +- [Methods](#methods) +- [Examples](#examples) +- [Automatic Tracking](#automatic-tracking) +- [Best Practices](#best-practices) + +## Methods + +| Method | Signature | Description | +|--------|-----------|-------------| +| `track(params)` | `void` | Track a custom event | + +## Examples + +### Track Custom Event + +```javascript +// Track a simple event +base44.analytics.track({ + eventName: "button_clicked" +}); + +// Track event with properties +base44.analytics.track({ + eventName: "purchase_completed", + properties: { + product_id: "prod-123", + amount: 99.99, + currency: "USD" + } +}); +``` + +### Track User Actions + +```javascript +// Track page view +base44.analytics.track({ + eventName: "page_view", + properties: { + page: "/dashboard", + referrer: document.referrer + } +}); + +// Track feature usage +base44.analytics.track({ + eventName: "feature_used", + properties: { + feature: "export_data", + format: "csv" + } +}); +``` + +## Automatic Tracking + +The analytics module automatically tracks: +- **Initialization events**: When the app loads +- **Heartbeat events**: Periodic activity signals +- **Session duration**: Time spent in the app + +These internal events help measure user engagement without manual instrumentation. + +## Best Practices + +1. **Use descriptive event names**: `order_completed` instead of `click` +2. **Include relevant properties**: Add context that helps analyze the event +3. **Be consistent**: Use the same event names and property keys across your app +4. **Don't track sensitive data**: Avoid PII in event properties + +```javascript +// Good: Descriptive with relevant properties +base44.analytics.track({ + eventName: "subscription_started", + properties: { + plan: "pro", + billing_cycle: "annual" + } +}); + +// Avoid: Vague event name, no context +base44.analytics.track({ + eventName: "click" +}); +``` + +## Type Definitions + +```typescript +/** Properties that can be attached to a tracked event. */ +type TrackEventProperties = { + [key: string]: string | number | boolean | null | undefined; +}; + +/** Parameters for the track() method. */ +interface TrackEventParams { + /** The name of the event to track. */ + eventName: string; + /** Optional properties to attach to the event. */ + properties?: TrackEventProperties; +} + +/** The analytics module interface. */ +interface AnalyticsModule { + /** Track a custom event with optional properties. */ + track(params: TrackEventParams): void; +} +``` diff --git a/evals/fixtures/sdk-auth/skills/base44-sdk/references/app-logs.md b/evals/fixtures/sdk-auth/skills/base44-sdk/references/app-logs.md new file mode 100644 index 0000000..0439805 --- /dev/null +++ b/evals/fixtures/sdk-auth/skills/base44-sdk/references/app-logs.md @@ -0,0 +1,78 @@ +# App Logs Module + +Log user activity in your app via `base44.appLogs`. + +## Contents +- [Methods](#methods) +- [Examples](#examples) +- [Use Cases](#use-cases) + +## Methods + +| Method | Signature | Description | +|--------|-----------|-------------| +| `logUserInApp(pageName)` | `Promise` | Log user activity on a page | + +## Examples + +### Log User Activity + +```javascript +// Log when user visits a page +await base44.appLogs.logUserInApp("dashboard"); + +// Log specific page visits +await base44.appLogs.logUserInApp("settings"); +await base44.appLogs.logUserInApp("profile"); + +// Log feature usage +await base44.appLogs.logUserInApp("export-button-click"); +``` + +The page name doesn't have to be an actual page - it can be any string you want to track. + +## Use Cases + +### Track Page Views in React + +```javascript +// Log page views on route change +useEffect(() => { + base44.appLogs.logUserInApp(window.location.pathname); +}, [location.pathname]); +``` + +### Track Feature Usage + +```javascript +// Log when user uses specific features +function handleExport() { + base44.appLogs.logUserInApp("export-data"); + // ... export logic +} + +function handleSettingsChange() { + base44.appLogs.logUserInApp("settings-updated"); + // ... save settings +} +``` + +## Notes + +- Logs appear in the Analytics page of your app dashboard +- App logs track page-level and feature-level activity +- Use `analytics.track()` for custom events with properties, `appLogs.logUserInApp()` for simple page/feature tracking + +## Type Definitions + +```typescript +/** App Logs module for tracking and analyzing app usage. */ +interface AppLogsModule { + /** + * Log user activity in the app. + * @param pageName - Name of the page or section being visited. + * @returns Promise that resolves when the log is recorded. + */ + logUserInApp(pageName: string): Promise; +} +``` diff --git a/evals/fixtures/sdk-auth/skills/base44-sdk/references/auth.md b/evals/fixtures/sdk-auth/skills/base44-sdk/references/auth.md new file mode 100644 index 0000000..5195b3a --- /dev/null +++ b/evals/fixtures/sdk-auth/skills/base44-sdk/references/auth.md @@ -0,0 +1,761 @@ +# Auth Module + +User authentication, registration, and session management via `base44.auth`. + +## Contents +- [TypeScript Types](#typescript-types) +- [Methods](#methods) +- [Examples](#examples) +- [Error Handling](#error-handling) +- [Auth Providers](#auth-providers) +- [Environment Availability](#environment-availability) +- [App Visibility](#app-visibility) +- [Limitations](#limitations) + +--- + +## TypeScript Types + +### User Interface +```typescript +interface User { + id: string; + created_date: string; + updated_date: string; + email: string; + full_name: string | null; + disabled: boolean | null; + is_verified: boolean; + app_id: string; + is_service: boolean; + role: string; + [key: string]: any; // Custom schema fields +} +``` + +### LoginResponse Interface +```typescript +interface LoginResponse { + access_token: string; // JWT token + user: User; // Complete user object +} +``` + +### Parameter Interfaces + +#### RegisterParams +```typescript +interface RegisterParams { + email: string; // Required + password: string; // Required + turnstile_token?: string | null; // Optional: Cloudflare Turnstile for bot protection + referral_code?: string | null; // Optional: Referral code +} +``` + +#### VerifyOtpParams +```typescript +interface VerifyOtpParams { + email: string; // User's email + otpCode: string; // OTP code from email +} +``` + +#### ResetPasswordParams +```typescript +interface ResetPasswordParams { + resetToken: string; // Token from password reset email + newPassword: string; // New password to set +} +``` + +#### ChangePasswordParams +```typescript +interface ChangePasswordParams { + userId: string; // User ID + currentPassword: string; // Current password for verification + newPassword: string; // New password to set +} +``` + +### Provider Type +```typescript +type Provider = 'google' | 'microsoft' | 'facebook'; +``` + +--- + +## Methods + +### Module Interface +```typescript +interface AuthModule { + // User Info + me(): Promise; + updateMe(data: Partial>): Promise; + isAuthenticated(): Promise; + + // Login/Logout + loginViaEmailPassword(email: string, password: string, turnstileToken?: string): Promise; + loginWithProvider(provider: Provider, fromUrl?: string): void; + logout(redirectUrl?: string): void; + redirectToLogin(nextUrl: string): void; + + // Token Management + setToken(token: string, saveToStorage?: boolean): void; + + // Registration + register(params: RegisterParams): Promise; + verifyOtp(params: VerifyOtpParams): Promise; + resendOtp(email: string): Promise; + + // User Management + inviteUser(userEmail: string, role: string): Promise; + + // Password Management + resetPasswordRequest(email: string): Promise; + resetPassword(params: ResetPasswordParams): Promise; + changePassword(params: ChangePasswordParams): Promise; +} +``` + +### Method Reference Table + +| Method | Parameters | Return Type | Description | +|--------|-----------|-------------|-------------| +| `register()` | `params: RegisterParams` | `Promise` | Create new user account | +| `loginViaEmailPassword()` | `email: string, password: string, turnstileToken?: string` | `Promise` | Authenticate with email/password | +| `loginWithProvider()` | `provider: Provider, fromUrl?: string` | `void` | Initiate OAuth login flow | +| `me()` | None | `Promise` | Get current authenticated user | +| `updateMe()` | `data: Partial` | `Promise` | Update current user's profile | +| `logout()` | `redirectUrl?: string` | `void` | Clear session, optionally redirect | +| `redirectToLogin()` | `nextUrl: string` | `void` | ⚠️ **Avoid** - Prefer custom login UI with `loginViaEmailPassword()` or `loginWithProvider()` | +| `isAuthenticated()` | None | `Promise` | Check if user is logged in | +| `setToken()` | `token: string, saveToStorage?: boolean` | `void` | Manually set auth token | +| `inviteUser()` | `userEmail: string, role: string` | `Promise` | Send invitation email | +| `verifyOtp()` | `params: VerifyOtpParams` | `Promise` | Verify OTP code | +| `resendOtp()` | `email: string` | `Promise` | Resend OTP code | +| `resetPasswordRequest()` | `email: string` | `Promise` | Request password reset | +| `resetPassword()` | `params: ResetPasswordParams` | `Promise` | Reset password with token | +| `changePassword()` | `params: ChangePasswordParams` | `Promise` | Change user password | + +--- + +## Examples + +### Register New User (Complete Flow) + +Registration requires email verification before login. Complete flow: + +1. **Register** - Create the user account +2. **Verification email sent** - User receives an OTP code +3. **Verify OTP** - User enters code to verify email +4. **Login** - User can now log in + +```javascript +try { + // Step 1: Register the user + await base44.auth.register({ + email: "user@example.com", + password: "securePassword123", + referral_code: "OPTIONAL_CODE", // optional + turnstile_token: "CAPTCHA_TOKEN" // optional, for bot protection + }); + console.log('Registration successful. Check email for OTP code.'); + + // Step 2: User receives email with OTP code (e.g., "123456") + + // Step 3: Verify the OTP code + await base44.auth.verifyOtp({ + email: "user@example.com", + otpCode: "123456" // code from verification email + }); + console.log('Email verified successfully.'); + + // Step 4: Now the user can log in + const loginResponse = await base44.auth.loginViaEmailPassword( + "user@example.com", + "securePassword123" + ); + console.log('Login successful:', loginResponse.user); + +} catch (error) { + console.error('Registration flow failed:', error.message); + // Handle specific errors (see Error Handling section) +} +``` + +> **Important**: Users cannot log in until they complete OTP verification. Attempting to call `loginViaEmailPassword` before verification will fail. + +### Login with Email/Password + +```javascript +try { + const response = await base44.auth.loginViaEmailPassword( + "user@example.com", + "password123", + turnstileToken // optional: for bot protection + ); + + console.log('Login successful'); + console.log('User:', response.user); + console.log('Token:', response.access_token); + + // JWT is automatically stored for subsequent requests + +} catch (error) { + console.error('Login failed:', error.message); + if (error.status === 401) { + console.error('Invalid credentials'); + } else if (error.status === 403) { + console.error('Email not verified. Please check your email for OTP.'); + } +} +``` + +### Login with OAuth Provider + +```javascript +// Redirect to Google OAuth +base44.auth.loginWithProvider('google'); + +// Redirect to Google OAuth and return to current page after +base44.auth.loginWithProvider('google', window.location.href); + +// Supported providers: 'google', 'microsoft', 'facebook' +base44.auth.loginWithProvider('microsoft'); +``` + +### Get Current User + +```javascript +try { + const user = await base44.auth.me(); + + if (user) { + console.log('User ID:', user.id); + console.log('Email:', user.email); + console.log('Name:', user.full_name); + console.log('Role:', user.role); + console.log('Verified:', user.is_verified); + } else { + console.log('User not authenticated'); + } + +} catch (error) { + console.error('Failed to fetch user:', error.message); + if (error.status === 401) { + // Token expired or invalid - navigate to your custom login page + navigate('/login'); + } +} +``` + +### Update User Profile + +```javascript +try { + const updatedUser = await base44.auth.updateMe({ + full_name: "John Doe", + // Custom schema fields can be updated here + phone: "+1234567890", + preferences: { theme: "dark" } + }); + + console.log('Profile updated:', updatedUser); + +} catch (error) { + console.error('Profile update failed:', error.message); + if (error.status === 400) { + console.error('Invalid data provided'); + } else if (error.status === 401) { + console.error('Authentication required'); + navigate('/login'); + } +} +``` + +### Check Authentication Status + +```javascript +try { + const isLoggedIn = await base44.auth.isAuthenticated(); + + if (isLoggedIn) { + // Show authenticated UI + console.log('User is authenticated'); + } else { + // Show login button + console.log('User is not authenticated'); + } + +} catch (error) { + console.error('Failed to check authentication:', error.message); + // On error, treat as not authenticated +} +``` + +### Logout + +```javascript +// Simple logout +base44.auth.logout(); + +// Logout and redirect to goodbye page +base44.auth.logout("/goodbye"); + +// Logout and redirect to homepage +base44.auth.logout("/"); +``` + +### Protected Route Pattern + +```javascript +// Using a navigation function (e.g., React Router's useNavigate, Next.js router) +async function requireAuth(navigate) { + try { + const user = await base44.auth.me(); + + if (!user) { + // Navigate to your custom login page + navigate('/login', { state: { returnTo: window.location.pathname } }); + return null; + } + + return user; + + } catch (error) { + console.error('Authentication check failed:', error.message); + navigate('/login', { state: { returnTo: window.location.pathname } }); + return null; + } +} + +// Usage in your app +async function loadProtectedPage(navigate) { + const user = await requireAuth(navigate); + if (!user) { + // Will navigate to login + return; + } + + // Continue with authenticated logic + console.log('Welcome,', user.full_name); +} +``` + +### Set Authentication Token + +```javascript +// SECURITY WARNING: Never hardcode tokens or expose them in client code +// Tokens should only be received from secure authentication flows + +// Set token and save to localStorage (default) +base44.auth.setToken(receivedToken); + +// Set token without saving to localStorage (temporary session) +base44.auth.setToken(receivedToken, false); + +// Verify token was set +try { + const isAuthenticated = await base44.auth.isAuthenticated(); + if (!isAuthenticated) { + console.error('Token validation failed'); + } +} catch (error) { + console.error('Failed to set token:', error.message); +} +``` + +### Invite User to Application + +```javascript +try { + // Note: Typically requires admin privileges + const response = await base44.auth.inviteUser( + "newuser@example.com", + "user" // or "admin" + ); + + console.log('Invitation sent successfully'); + +} catch (error) { + console.error('Failed to invite user:', error.message); + if (error.status === 403) { + console.error('Insufficient permissions to invite users'); + } else if (error.status === 400) { + console.error('Invalid email or role'); + } +} +``` + +### OTP Verification + +```javascript +try { + // Verify OTP code sent to user's email + await base44.auth.verifyOtp({ + email: "user@example.com", + otpCode: "123456" + }); + + console.log('OTP verified successfully'); + +} catch (error) { + console.error('OTP verification failed:', error.message); + if (error.status === 400) { + console.error('Invalid or expired OTP code'); + } else if (error.status === 429) { + console.error('Too many attempts. Please try again later.'); + } +} + +// Resend OTP if needed +try { + await base44.auth.resendOtp("user@example.com"); + console.log('OTP resent successfully'); + +} catch (error) { + console.error('Failed to resend OTP:', error.message); + if (error.status === 429) { + console.error('Too many requests. Please wait before trying again.'); + } +} +``` + +### Password Reset Flow + +```javascript +// Step 1: Request password reset +try { + await base44.auth.resetPasswordRequest("user@example.com"); + console.log('Password reset email sent. Check your inbox.'); + +} catch (error) { + console.error('Password reset request failed:', error.message); + if (error.status === 429) { + console.error('Too many requests. Please try again later.'); + } + // Note: For security, don't reveal if email exists +} + +// Step 2: Reset password with token from email +try { + await base44.auth.resetPassword({ + resetToken: "token-from-email", + newPassword: "newSecurePassword123" + }); + + console.log('Password reset successfully. You can now log in.'); + +} catch (error) { + console.error('Password reset failed:', error.message); + if (error.status === 400) { + console.error('Invalid or expired reset token'); + } else if (error.status === 422) { + console.error('Password does not meet requirements'); + } +} +``` + +### Change Password + +```javascript +try { + const currentUser = await base44.auth.me(); + + if (!currentUser) { + throw new Error('User must be authenticated to change password'); + } + + await base44.auth.changePassword({ + userId: currentUser.id, + currentPassword: "oldPassword123", + newPassword: "newSecurePassword456" + }); + + console.log('Password changed successfully'); + +} catch (error) { + console.error('Password change failed:', error.message); + if (error.status === 401) { + console.error('Current password is incorrect'); + } else if (error.status === 422) { + console.error('New password does not meet requirements'); + } else if (error.status === 403) { + console.error('Not authorized to change this password'); + } +} +``` + +--- + +## Error Handling + +### Common Error Scenarios + +The auth module can throw various errors. Here are common scenarios and how to handle them: + +#### Authentication Errors (401/403) +```javascript +try { + const user = await base44.auth.me(); +} catch (error) { + if (error.status === 401) { + // Token expired or invalid - navigate to your custom login page + navigate('/login'); + } else if (error.status === 403) { + // Email not verified or insufficient permissions + console.error('Access denied:', error.message); + } +} +``` + +#### Validation Errors (400/422) +```javascript +try { + await base44.auth.register({ + email: "invalid-email", + password: "weak" + }); +} catch (error) { + if (error.status === 400) { + console.error('Invalid input:', error.message); + // Handle validation errors + } else if (error.status === 422) { + console.error('Data validation failed:', error.details); + } +} +``` + +#### Rate Limiting (429) +```javascript +try { + await base44.auth.resendOtp("user@example.com"); +} catch (error) { + if (error.status === 429) { + console.error('Too many requests. Please wait before trying again.'); + // Show countdown or disable button temporarily + } +} +``` + +#### Generic Error Handler +```javascript +function handleAuthError(error) { + switch (error.status) { + case 400: + return 'Invalid input. Please check your data.'; + case 401: + return 'Authentication required. Redirecting to login...'; + case 403: + return 'Access denied. You may need to verify your email.'; + case 404: + return 'Resource not found.'; + case 422: + return 'Validation failed. Please check your input.'; + case 429: + return 'Too many requests. Please try again later.'; + case 500: + return 'Server error. Please try again.'; + default: + return 'An unexpected error occurred.'; + } +} + +// Usage +try { + await base44.auth.loginViaEmailPassword(email, password); +} catch (error) { + console.error(handleAuthError(error)); +} +``` + +--- + +## Auth Providers + +Configure authentication providers in your app dashboard: + +### Available Providers + +**Built-in (All Plans):** +- **Email/Password** - Default, always enabled +- **Google** - OAuth authentication +- **Microsoft** - OAuth authentication +- **Facebook** - OAuth authentication + +**SSO Providers (Elite Plan):** +- **Okta** +- **Azure AD** +- **GitHub** + +### Using OAuth Providers + +```javascript +// Initiate OAuth login flow +base44.auth.loginWithProvider('google'); + +// Return to specific page after authentication +base44.auth.loginWithProvider('microsoft', '/dashboard'); + +// Supported values: 'google', 'microsoft', 'facebook' +``` + +--- + +## Environment Availability + +| Environment | Availability | Notes | +|------------|--------------|-------| +| **Frontend** | ✅ Yes | All methods available | +| **Backend Functions** | ✅ Yes | Use `createClientFromRequest(req)` for authenticated client | +| **Service Role** | ❌ No | Auth methods not available in service role context | + +### Frontend Usage +```javascript +// Standard browser usage +const user = await base44.auth.me(); +``` + +### Backend Functions Usage +```javascript +import { createClientFromRequest } from "@base44/sdk"; + +Deno.serve(async (req) => { + const base44 = createClientFromRequest(req); + + // Get user from request context + const user = await base44.auth.me(); + + // User operations here... + return Response.json({ user }); +}); +``` + +--- + +## App Visibility + +Control who can access your app in the app settings: + +### Public Apps +- No login required for basic access +- Users can view public content without authentication +- Authenticated users get additional features/data + +### Private Apps +- Login required to access any content +- Unauthenticated users are automatically redirected to login +- All content is protected by default + +--- + +## Limitations + +### Authentication UI Options +- **Recommended:** Build custom login/signup UI using `loginViaEmailPassword()` and `loginWithProvider()` for full control over user experience and branding +- **Alternative:** `redirectToLogin()` uses Base44's hosted authentication pages with limited customization + +### Hosted Login (via redirectToLogin) +- `redirectToLogin()` shows both login and signup options on the same page +- No separate `redirectToSignup()` method +- Users can switch between login/signup on the hosted page +- ⚠️ **Note:** Prefer building custom login UI for better user experience + +### Password Requirements +- Minimum length and complexity requirements enforced +- Requirements not exposed via API +- Validation errors returned when requirements not met + +### Rate Limiting +- OTP requests are rate-limited to prevent abuse +- Password reset requests are rate-limited +- Login attempts may be rate-limited with Turnstile protection + +### Token Management +- JWTs are automatically stored in localStorage by default +- Token expiration and refresh not exposed in API +- Call `me()` or `isAuthenticated()` to verify token validity + +--- + +## Best Practices + +### 1. Always Handle Errors +```javascript +try { + await base44.auth.loginViaEmailPassword(email, password); +} catch (error) { + // Always handle authentication errors + console.error('Login failed:', error.message); +} +``` + +### 2. Verify Authentication Before Protected Actions +```javascript +const user = await requireAuth(); +if (!user) return; // Will redirect to login +// Proceed with authenticated action +``` + +### 3. Use Type Safety with TypeScript +```typescript +import type { User, LoginResponse } from '@base44/sdk'; + +const user: User = await base44.auth.me(); +const response: LoginResponse = await base44.auth.loginViaEmailPassword(email, password); +``` + +### 4. Don't Hardcode Credentials +```javascript +// ❌ BAD +base44.auth.setToken("hardcoded-token-here"); + +// ✅ GOOD +const token = await secureAuthFlow(); +base44.auth.setToken(token); +``` + +### 5. Provide User Feedback +```javascript +try { + await base44.auth.register(params); + showSuccessMessage('Registration successful! Check your email for verification code.'); +} catch (error) { + showErrorMessage('Registration failed: ' + error.message); +} +``` + +### 6. Handle Token Expiration Gracefully +```javascript +try { + const user = await base44.auth.me(); +} catch (error) { + if (error.status === 401) { + // Clear local state and navigate to login + localStorage.clear(); + navigate('/login'); + } +} +``` + +### 7. Build Custom Login UI (Recommended) +```javascript +// ✅ RECOMMENDED - Custom login form with direct methods +const handleLogin = async (email, password) => { + try { + const { user } = await base44.auth.loginViaEmailPassword(email, password); + navigate('/dashboard'); + } catch (error) { + setError(error.message); + } +}; + +const handleGoogleLogin = () => { + base44.auth.loginWithProvider('google', '/dashboard'); +}; + +// ❌ AVOID - Redirecting to hosted login page +// base44.auth.redirectToLogin(window.location.href); +``` diff --git a/evals/fixtures/sdk-auth/skills/base44-sdk/references/base44-agents.md b/evals/fixtures/sdk-auth/skills/base44-sdk/references/base44-agents.md new file mode 100644 index 0000000..ee55dee --- /dev/null +++ b/evals/fixtures/sdk-auth/skills/base44-sdk/references/base44-agents.md @@ -0,0 +1,364 @@ +# Agents Module + +AI agent conversations and messages via `base44.agents`. + +> **Note:** This module requires a logged-in user. All agent methods work in the context of the authenticated user. + +## Contents +- [Concepts](#concepts) +- [Methods](#methods) +- [Examples](#examples) (Create, Get Conversations, List, Subscribe, Send Message, WhatsApp) +- [Message Structure](#message-structure) +- [Conversation Structure](#conversation-structure) +- [Common Patterns](#common-patterns) + +## Concepts + +- **Conversation**: A dialogue between user and an AI agent. Has unique ID, agent name, user reference, and metadata. +- **Message**: Single message in a conversation. Has role (`user`, `assistant`, `system`), content, timestamps, and optional metadata. + +## Methods + +| Method | Signature | Description | +|--------|-----------|-------------| +| `createConversation(params)` | `Promise` | Create a new conversation with an agent | +| `getConversations()` | `Promise` | Get all user's conversations | +| `getConversation(id)` | `Promise` | Get conversation with messages | +| `listConversations(filterParams)` | `Promise` | Filter/sort/paginate conversations | +| `subscribeToConversation(id, onUpdate?)` | `() => void` | Real-time updates via WebSocket (returns unsubscribe function) | +| `addMessage(conversation, message)` | `Promise` | Send a message | +| `getWhatsAppConnectURL(agentName)` | `string` | Get WhatsApp connection URL for agent | + +## Examples + +### Create Conversation + +```javascript +const conversation = await base44.agents.createConversation({ + agent_name: "support-agent", + metadata: { + order_id: "ORD-123", + category: "billing" + } +}); + +console.log(conversation.id); +``` + +### Get All Conversations + +```javascript +const conversations = await base44.agents.getConversations(); + +conversations.forEach(conv => { + console.log(conv.id, conv.agent_name, conv.created_date); +}); +``` + +### Get Single Conversation (with messages) + +```javascript +const conversation = await base44.agents.getConversation("conv-id-123"); + +console.log(conversation.messages); +``` + +### List with Filters + +```javascript +// Using filterParams object with q, sort, limit, skip, fields +const recent = await base44.agents.listConversations({ + q: { agent_name: "support-agent" }, + sort: "-created_date", + limit: 10, + skip: 0 +}); + +// Filter by metadata +const highPriority = await base44.agents.listConversations({ + q: { + agent_name: "support-agent", + "metadata.priority": "high" + }, + sort: "-updated_date", + limit: 20 +}); +``` + +### Subscribe to Updates (Real-time) + +```javascript +const unsubscribe = base44.agents.subscribeToConversation( + "conv-id-123", + (updatedConversation) => { + // Called when new messages arrive + console.log("New messages:", updatedConversation.messages); + } +); + +// Later: unsubscribe +unsubscribe(); +``` + +### Send a Message + +```javascript +const conversation = await base44.agents.getConversation("conv-id-123"); + +await base44.agents.addMessage(conversation, { + role: "user", + content: "What's the weather like today?" +}); +``` + +### Get WhatsApp Connection URL + +```javascript +const whatsappUrl = base44.agents.getWhatsAppConnectURL("support-agent"); +// Returns URL for users to connect with agent via WhatsApp +console.log(whatsappUrl); +``` + +## Message Structure + +```javascript +{ + role: "user" | "assistant" | "system", + content: "Message text or structured object", + created_date: "2024-01-15T10:30:00Z", + updated_date: "2024-01-15T10:30:00Z", + + // Optional fields + reasoning: { + content: "Agent's reasoning process", + timing: 1500 + }, + tool_calls: [{ + name: "search", + arguments: { query: "weather" }, + result: { ... }, + status: "success" + }], + file_urls: ["https://..."], + usage: { + prompt_tokens: 150, + completion_tokens: 50 + }, + metadata: { ... }, + custom_context: { ... } +} +``` + +## Conversation Structure + +```javascript +{ + id: "conv-id-123", + app_id: "app-id", + agent_name: "support-agent", + created_by_id: "user-id", + created_date: "2024-01-15T10:00:00Z", + updated_date: "2024-01-15T10:30:00Z", + messages: [ ... ], + metadata: { ... } +} +``` + +## Common Patterns + +### Chat Interface + +```javascript +// Load conversation +const conv = await base44.agents.getConversation(conversationId); +setMessages(conv.messages); + +// Subscribe to updates +const unsubscribe = base44.agents.subscribeToConversation(conversationId, (updated) => { + setMessages(updated.messages); +}); + +// Send message +async function sendMessage(text) { + await base44.agents.addMessage(conv, { role: "user", content: text }); +} + +// Cleanup on unmount +return () => unsubscribe(); +``` + +## Type Definitions + +### AgentConversation + +```typescript +/** An agent conversation containing messages exchanged with an AI agent. */ +interface AgentConversation { + /** Unique identifier for the conversation. */ + id: string; + /** Application ID. */ + app_id: string; + /** Name of the agent in this conversation. */ + agent_name: string; + /** ID of the user who created the conversation. */ + created_by_id: string; + /** When the conversation was created. */ + created_date: string; + /** When the conversation was last updated. */ + updated_date: string; + /** Array of messages in the conversation. */ + messages: AgentMessage[]; + /** Optional metadata associated with the conversation. */ + metadata?: Record; +} +``` + +### AgentMessage + +```typescript +/** A message in an agent conversation. */ +interface AgentMessage { + /** Unique identifier for the message. */ + id: string; + /** Role of the message sender. */ + role: "user" | "assistant" | "system"; + /** When the message was created. */ + created_date: string; + /** When the message was last updated. */ + updated_date: string; + /** Message content. */ + content?: string | Record; + /** Optional reasoning information for the message. */ + reasoning?: AgentMessageReasoning | null; + /** URLs to files attached to the message. */ + file_urls?: string[]; + /** Tool calls made by the agent. */ + tool_calls?: AgentMessageToolCall[]; + /** Token usage statistics. */ + usage?: AgentMessageUsage; + /** Whether the message is hidden from the user. */ + hidden?: boolean; + /** Custom context provided with the message. */ + custom_context?: AgentMessageCustomContext[]; + /** Model used to generate the message. */ + model?: string; + /** Checkpoint ID for the message. */ + checkpoint_id?: string; + /** Metadata about when and by whom the message was created. */ + metadata?: AgentMessageMetadata; +} +``` + +### Supporting Types + +```typescript +/** Reasoning information for an agent message. */ +interface AgentMessageReasoning { + /** When reasoning started. */ + start_date: string; + /** When reasoning ended. */ + end_date?: string; + /** Reasoning content. */ + content: string; +} + +/** A tool call made by the agent. */ +interface AgentMessageToolCall { + /** Tool call ID. */ + id: string; + /** Name of the tool called. */ + name: string; + /** Arguments passed to the tool as JSON string. */ + arguments_string: string; + /** Status of the tool call. */ + status: "running" | "success" | "error" | "stopped"; + /** Results from the tool call. */ + results?: string; +} + +/** Token usage statistics for an agent message. */ +interface AgentMessageUsage { + /** Number of tokens in the prompt. */ + prompt_tokens?: number; + /** Number of tokens in the completion. */ + completion_tokens?: number; +} + +/** Custom context provided with an agent message. */ +interface AgentMessageCustomContext { + /** Context message. */ + message: string; + /** Associated data for the context. */ + data: Record; + /** Type of context. */ + type: string; +} + +/** Metadata about when and by whom a message was created. */ +interface AgentMessageMetadata { + /** When the message was created. */ + created_date: string; + /** Email of the user who created the message. */ + created_by_email: string; + /** Full name of the user who created the message. */ + created_by_full_name: string; +} +``` + +### CreateConversationParams + +```typescript +/** Parameters for creating a new conversation. */ +interface CreateConversationParams { + /** The name of the agent to create a conversation with. */ + agent_name: string; + /** Optional metadata to attach to the conversation. */ + metadata?: Record; +} +``` + +### ModelFilterParams + +```typescript +/** Parameters for filtering, sorting, and paginating conversations. */ +interface ModelFilterParams { + /** Query object with field-value pairs for filtering. */ + q?: Record; + /** Sort parameter (e.g., "-created_date" for descending). */ + sort?: string | null; + /** Maximum number of results to return. */ + limit?: number | null; + /** Number of results to skip for pagination. */ + skip?: number | null; + /** Array of field names to include in the response. */ + fields?: string[] | null; +} +``` + +### AgentsModule + +```typescript +/** Agents module for managing AI agent conversations. */ +interface AgentsModule { + /** Gets all conversations from all agents in the app. */ + getConversations(): Promise; + + /** Gets a specific conversation by ID. */ + getConversation(conversationId: string): Promise; + + /** Lists conversations with filtering, sorting, and pagination. */ + listConversations(filterParams: ModelFilterParams): Promise; + + /** Creates a new conversation with an agent. */ + createConversation(conversation: CreateConversationParams): Promise; + + /** Adds a message to a conversation. */ + addMessage(conversation: AgentConversation, message: Partial): Promise; + + /** Subscribes to real-time updates for a conversation. Returns unsubscribe function. */ + subscribeToConversation(conversationId: string, onUpdate?: (conversation: AgentConversation) => void): () => void; + + /** Gets WhatsApp connection URL for an agent. */ + getWhatsAppConnectURL(agentName: string): string; +} +``` diff --git a/evals/fixtures/sdk-auth/skills/base44-sdk/references/client.md b/evals/fixtures/sdk-auth/skills/base44-sdk/references/client.md new file mode 100644 index 0000000..554bf2b --- /dev/null +++ b/evals/fixtures/sdk-auth/skills/base44-sdk/references/client.md @@ -0,0 +1,257 @@ +# Client Setup + +How to create and configure the Base44 client. + +## Contents +- [In Base44-Generated Apps](#in-base44-generated-apps) +- [In External Apps](#in-external-apps) +- [In Backend Functions](#in-backend-functions) +- [Authentication Modes](#authentication-modes) (Anonymous, User, Service Role) +- [Available Modules](#available-modules) +- [Client Methods](#client-methods) +- [Client Configuration Options](#client-configuration-options) + +## In Base44-Generated Apps + +The client is pre-configured and available as `base44`. Just use it: + +```javascript +const tasks = await base44.entities.Task.list(); +``` + +## In External Apps + +Install the SDK and create a client: + +```bash +npm install @base44/sdk +``` + +```javascript +import { createClient } from "@base44/sdk"; + +// IMPORTANT: The parameter name is 'appId' (NOT 'clientId', NOT 'id') +// IMPORTANT: onError must be nested inside 'options' object +const base44 = createClient({ + appId: "your-app-id", // Required: Use 'appId' parameter + token: "optional-user-token", // Optional: for pre-authenticated requests + options: { // Optional: configuration options + onError: (error) => { // Optional: error handler (must be in options) + console.error("Base44 error:", error); + } + } +}); +``` + +**Common Mistakes:** +- ❌ `createClient({ clientId: "..." })` - WRONG parameter name +- ❌ `createClient({ id: "..." })` - WRONG parameter name +- ❌ `createClient({ appId: "...", onError: ... })` - WRONG: onError must be in options +- ✅ `createClient({ appId: "..." })` - CORRECT parameter name +- ✅ `createClient({ appId: "...", options: { onError: ... } })` - CORRECT: onError in options + +## In Backend Functions + +Use `createClientFromRequest` to get a client with the caller's auth context: + +```javascript +import { createClientFromRequest } from "@base44/sdk"; + +Deno.serve(async (req) => { + const base44 = createClientFromRequest(req); + + // Client inherits authentication from the request + const user = await base44.auth.me(); + + return Response.json({ user }); +}); +``` + +## Authentication Modes + +| Mode | How to Get | Permissions | +|------|-----------|-------------| +| **Anonymous** | `createClient({ appId })` without token | Public data only | +| **User** | After `loginViaEmailPassword()` or via `createClientFromRequest` | User's own data | +| **Service Role** | `base44.asServiceRole.*` in backend | Full admin access | + +## Anonymous Mode + +No authentication. Can only access public resources. + +```javascript +const base44 = createClient({ appId: "your-app-id" }); + +// Only works if Task entity allows anonymous read +const publicTasks = await base44.entities.Task.list(); +``` + +## User Mode + +After user logs in, the client automatically includes their token. + +```javascript +const base44 = createClient({ appId: "your-app-id" }); + +// Login sets the token +await base44.auth.loginViaEmailPassword("user@example.com", "password"); + +// Subsequent requests are authenticated +const user = await base44.auth.me(); +const myTasks = await base44.entities.Task.list(); // filtered by permissions +``` + +## Service Role Mode + +Admin-level access. **Backend only.** + +```javascript +// Inside a backend function +Deno.serve(async (req) => { + const base44 = createClientFromRequest(req); + + // User mode - respects permissions + const myTasks = await base44.entities.Task.list(); + + // Service role - bypasses permissions + const allTasks = await base44.asServiceRole.entities.Task.list(); + const allUsers = await base44.asServiceRole.entities.User.list(); + const oauthToken = await base44.asServiceRole.connectors.getAccessToken("slack"); + + return Response.json({ myTasks, allTasks }); +}); +``` + +## Available Modules + +The client exposes these modules: + +```javascript +base44.entities // CRUD operations +base44.auth // Authentication +base44.agents // AI conversations +base44.functions // Backend function invocation +base44.integrations // Third-party services +base44.analytics // Event tracking +base44.appLogs // App usage logging +base44.users // User invitations + +// Service role only (backend) +base44.asServiceRole.entities +base44.asServiceRole.agents +base44.asServiceRole.functions +base44.asServiceRole.integrations +base44.asServiceRole.appLogs +base44.asServiceRole.connectors +``` + +## Client Methods + +The client provides these methods: + +```javascript +// Set authentication token for all subsequent requests +base44.setToken(newToken); + +// Cleanup WebSocket connections (call when done with client) +base44.cleanup(); +``` + +### setToken + +Updates the authentication token for all subsequent API requests and WebSocket connections. + +```javascript +// After receiving a token (e.g., from external auth) +base44.setToken("new-jwt-token"); +``` + +### cleanup + +Disconnects WebSocket connections. Call when you're done with the client or when the component unmounts. + +```javascript +// Cleanup on component unmount (React example) +useEffect(() => { + return () => base44.cleanup(); +}, []); +``` + +## Client Configuration Options + +```javascript +createClient({ + appId: "your-app-id", // Required: MUST use 'appId' (not 'clientId' or 'id') + token: "jwt-token", // Optional: pre-set auth token + options: { // Optional: configuration options + onError: (error) => {} // Optional: global error handler (must be in options) + } +}); +``` + +**⚠️ Critical:** +- The parameter name is `appId`, not `clientId` or `id`. Using the wrong parameter name will cause errors. +- The `onError` handler must be nested inside the `options` object, not at the top level. + +## Type Definitions + +### CreateClientConfig + +```typescript +/** Configuration for creating a Base44 client. */ +interface CreateClientConfig { + /** The Base44 app ID (required). */ + appId: string; + /** User authentication token. Used to authenticate as a specific user. */ + token?: string; + /** Service role authentication token (backend only). */ + serviceToken?: string; + /** Additional client options. */ + options?: CreateClientOptions; +} + +/** Options for creating a Base44 client. */ +interface CreateClientOptions { + /** Optional error handler called whenever an API error occurs. */ + onError?: (error: Error) => void; +} +``` + +### Base44Client + +```typescript +/** The Base44 client instance. */ +interface Base44Client { + /** Entities module for CRUD operations on your data models. */ + entities: EntitiesModule; + /** Integrations module for calling pre-built integration endpoints. */ + integrations: IntegrationsModule; + /** Auth module for user authentication and management. */ + auth: AuthModule; + /** Functions module for invoking custom backend functions. */ + functions: FunctionsModule; + /** Agents module for managing AI agent conversations. */ + agents: AgentsModule; + /** App logs module for tracking app usage. */ + appLogs: AppLogsModule; + /** Analytics module for tracking custom events. */ + analytics: AnalyticsModule; + + /** Cleanup function to disconnect WebSocket connections. */ + cleanup(): void; + + /** Sets a new authentication token for all subsequent requests. */ + setToken(newToken: string): void; + + /** Provides access to modules with elevated service role permissions (backend only). */ + readonly asServiceRole: { + entities: EntitiesModule; + integrations: IntegrationsModule; + connectors: ConnectorsModule; + functions: FunctionsModule; + agents: AgentsModule; + appLogs: AppLogsModule; + cleanup(): void; + }; +} +``` diff --git a/evals/fixtures/sdk-auth/skills/base44-sdk/references/connectors.md b/evals/fixtures/sdk-auth/skills/base44-sdk/references/connectors.md new file mode 100644 index 0000000..f3fbcb4 --- /dev/null +++ b/evals/fixtures/sdk-auth/skills/base44-sdk/references/connectors.md @@ -0,0 +1,113 @@ +# Connectors Module + +OAuth token management for external services. **Service role only** (backend functions). + +## Method + +```javascript +base44.asServiceRole.connectors.getAccessToken(integrationType): Promise +``` + +Returns a raw OAuth access token for the specified service. + +## Supported Services + +| Integration Type | Service | +|-----------------|---------| +| `"googlecalendar"` | Google Calendar | +| `"googledrive"` | Google Drive | +| `"slack"` | Slack | +| `"notion"` | Notion | +| `"salesforce"` | Salesforce | +| `"hubspot"` | HubSpot | +| `"linkedin"` | LinkedIn | +| `"tiktok"` | TikTok | +| `"github"` | GitHub | + +## Example Usage + +```javascript +// Backend function only +Deno.serve(async (req) => { + const base44 = createClientFromRequest(req); + + // Get OAuth token for Slack + const slackToken = await base44.asServiceRole.connectors.getAccessToken("slack"); + + // Use token directly with Slack API + const response = await fetch("https://slack.com/api/chat.postMessage", { + method: "POST", + headers: { + "Authorization": `Bearer ${slackToken}`, + "Content-Type": "application/json" + }, + body: JSON.stringify({ + channel: "#general", + text: "Hello from Base44!" + }) + }); + + return Response.json(await response.json()); +}); +``` + +## Google Calendar Example + +```javascript +Deno.serve(async (req) => { + const base44 = createClientFromRequest(req); + + const token = await base44.asServiceRole.connectors.getAccessToken("googlecalendar"); + + // List upcoming events + const response = await fetch( + "https://www.googleapis.com/calendar/v3/calendars/primary/events?" + + new URLSearchParams({ + maxResults: "10", + orderBy: "startTime", + singleEvents: "true", + timeMin: new Date().toISOString() + }), + { + headers: { "Authorization": `Bearer ${token}` } + } + ); + + const events = await response.json(); + return Response.json(events); +}); +``` + +## Setup Requirements + +1. **Builder plan** or higher +2. **Backend functions** enabled +3. **Connector configured** in Base44 dashboard (OAuth flow completed) + +## Important Notes + +- **One account per connector per app**: All users share the same connected account +- **Backend only**: `connectors` module not available in frontend code +- **Service role required**: Must use `base44.asServiceRole.connectors` +- **You handle the API calls**: Base44 only provides the token; you make the actual API requests +- **Token refresh**: Base44 handles token refresh automatically + +## Type Definitions + +```typescript +/** + * The type of external integration/connector. + * Examples: 'googlecalendar', 'slack', 'github', 'notion', etc. + */ +type ConnectorIntegrationType = string; + +/** Connectors module for managing OAuth tokens for external services. */ +interface ConnectorsModule { + /** + * Retrieves an OAuth access token for a specific external integration type. + * @param integrationType - The type of integration (e.g., 'googlecalendar', 'slack'). + * @returns Promise resolving to the access token string. + */ + getAccessToken(integrationType: ConnectorIntegrationType): Promise; +} +``` diff --git a/evals/fixtures/sdk-auth/skills/base44-sdk/references/entities.md b/evals/fixtures/sdk-auth/skills/base44-sdk/references/entities.md new file mode 100644 index 0000000..a7bdf35 --- /dev/null +++ b/evals/fixtures/sdk-auth/skills/base44-sdk/references/entities.md @@ -0,0 +1,253 @@ +# Entities Module + +CRUD operations on data models. Access via `base44.entities.EntityName.method()`. + +## Contents +- [Methods](#methods) +- [Examples](#examples) (Create, Bulk Create, List, Filter, Get, Update, Delete, Subscribe) +- [User Entity](#user-entity) +- [Service Role Access](#service-role-access) +- [Permissions](#permissions) + +## Methods + +| Method | Signature | Description | +|--------|-----------|-------------| +| `create(data)` | `Promise` | Create one record | +| `bulkCreate(dataArray)` | `Promise` | Create multiple records | +| `list(sort?, limit?, skip?, fields?)` | `Promise` | Get all records (paginated) | +| `filter(query, sort?, limit?, skip?, fields?)` | `Promise` | Get records matching conditions | +| `get(id)` | `Promise` | Get single record by ID | +| `update(id, data)` | `Promise` | Update record (partial update) | +| `delete(id)` | `Promise` | Delete record by ID | +| `deleteMany(query)` | `Promise` | Delete all matching records | +| `importEntities(file)` | `Promise` | Import from CSV (frontend only) | +| `subscribe(callback)` | `() => void` | Subscribe to realtime updates (returns unsubscribe function) | + +## Examples + +### Create + +```javascript +const task = await base44.entities.Task.create({ + title: "Complete documentation", + status: "pending", + dueDate: "2024-12-31" +}); +``` + +### Bulk Create + +```javascript +const tasks = await base44.entities.Task.bulkCreate([ + { title: "Task 1", status: "pending" }, + { title: "Task 2", status: "pending" } +]); +``` + +### List with Pagination + +```javascript +// Get first 10 records, sorted by created_date descending +const tasks = await base44.entities.Task.list( + "-created_date", // sort (string: prefix with - for descending) + 10, // limit + 0 // skip +); + +// Get next page +const page2 = await base44.entities.Task.list("-created_date", 10, 10); +``` + +### Filter + +```javascript +// Simple filter +const pending = await base44.entities.Task.filter({ status: "pending" }); + +// Multiple conditions +const myPending = await base44.entities.Task.filter({ + status: "pending", + assignedTo: userId +}); + +// With sort, limit, skip +const recent = await base44.entities.Task.filter( + { status: "pending" }, + "-created_date", // sort (string: prefix with - for descending) + 5, + 0 +); + +// Select specific fields +const titles = await base44.entities.Task.filter( + { status: "pending" }, + null, + null, + null, + ["id", "title"] +); +``` + +### Get by ID + +```javascript +const task = await base44.entities.Task.get("task-id-123"); +``` + +### Update + +```javascript +// Partial update - only specified fields change +await base44.entities.Task.update("task-id-123", { + status: "completed", + completedAt: new Date().toISOString() +}); +``` + +### Delete + +```javascript +// Single record +await base44.entities.Task.delete("task-id-123"); + +// Multiple records matching query +await base44.entities.Task.deleteMany({ status: "archived" }); +``` + +### Subscribe to Realtime Updates + +```javascript +// Subscribe to all changes on Task entity +const unsubscribe = base44.entities.Task.subscribe((event) => { + console.log(`Task ${event.id} was ${event.type}:`, event.data); + // event.type is "create", "update", or "delete" +}); + +// Later: unsubscribe to stop receiving updates +unsubscribe(); +``` + +**Event structure:** +```javascript +{ + type: "create" | "update" | "delete", + data: { ... }, // the entity data + id: "entity-id", // the affected entity's ID + timestamp: "2024-01-15T10:30:00Z" +} +``` + +## User Entity + +Every app has a built-in `User` entity with special rules: + +- Regular users can only read/update **their own** record +- Cannot create users via `entities.create()` - use `auth.register()` instead +- Service role has full access to all user records + +```javascript +// Get current user's record +const me = await base44.entities.User.get(currentUserId); + +// Service role: get any user +const anyUser = await base44.asServiceRole.entities.User.get(userId); +``` + +## Service Role Access + +For admin-level operations (bypass user permissions): + +```javascript +// Backend only +const allTasks = await base44.asServiceRole.entities.Task.list(); +const allUsers = await base44.asServiceRole.entities.User.list(); +``` + +## Permissions (RLS & FLS) + +Data access is controlled by **Row Level Security (RLS)** and **Field Level Security (FLS)** rules defined in entity schemas. + +1. **Authentication level**: anonymous, authenticated, or service role +2. **RLS rules**: Control which records (rows) users can create/read/update/delete +3. **FLS rules**: Control which fields users can read/write within accessible records + +Operations succeed or fail based on these rules - no partial results. + +RLS and FLS are configured in entity schema files (`base44/entities/*.jsonc`). See [entities-create.md](../../base44-cli/references/entities-create.md#row-level-security-rls) for configuration details. + +**Note:** `asServiceRole` sets the user's role to `"admin"` but does NOT bypass RLS. Your RLS rules must include admin access (e.g., `{ "user_condition": { "role": "admin" } }`) for service role operations to succeed. + +## Type Definitions + +### RealtimeEvent + +```typescript +/** Event types for realtime entity updates. */ +type RealtimeEventType = "create" | "update" | "delete"; + +/** Payload received when a realtime event occurs. */ +interface RealtimeEvent { + /** The type of change that occurred. */ + type: RealtimeEventType; + /** The entity data. */ + data: any; + /** The unique identifier of the affected entity. */ + id: string; + /** ISO 8601 timestamp of when the event occurred. */ + timestamp: string; +} + +/** Callback function invoked when a realtime event occurs. */ +type RealtimeCallback = (event: RealtimeEvent) => void; + +/** Function returned from subscribe, call it to unsubscribe. */ +type Subscription = () => void; +``` + +### EntityHandler + +```typescript +/** Entity handler providing CRUD operations for a specific entity type. */ +interface EntityHandler { + /** Lists records with optional pagination and sorting. */ + list(sort?: string, limit?: number, skip?: number, fields?: string[]): Promise; + + /** Filters records based on a query. */ + filter(query: Record, sort?: string, limit?: number, skip?: number, fields?: string[]): Promise; + + /** Gets a single record by ID. */ + get(id: string): Promise; + + /** Creates a new record. */ + create(data: Record): Promise; + + /** Updates an existing record. */ + update(id: string, data: Record): Promise; + + /** Deletes a single record by ID. */ + delete(id: string): Promise; + + /** Deletes multiple records matching a query. */ + deleteMany(query: Record): Promise; + + /** Creates multiple records in a single request. */ + bulkCreate(data: Record[]): Promise; + + /** Imports records from a file (frontend only). */ + importEntities(file: File): Promise; + + /** Subscribes to realtime updates. Returns unsubscribe function. */ + subscribe(callback: RealtimeCallback): Subscription; +} +``` + +### EntitiesModule + +```typescript +/** Entities module for managing app data. */ +interface EntitiesModule { + /** Access any entity by name dynamically. */ + [entityName: string]: EntityHandler; +} +``` diff --git a/evals/fixtures/sdk-auth/skills/base44-sdk/references/functions.md b/evals/fixtures/sdk-auth/skills/base44-sdk/references/functions.md new file mode 100644 index 0000000..8072465 --- /dev/null +++ b/evals/fixtures/sdk-auth/skills/base44-sdk/references/functions.md @@ -0,0 +1,216 @@ +# Functions Module + +Invoke custom backend functions via `base44.functions`. + +## Contents +- [Method](#method) +- [Invoking Functions](#invoking-functions) (Frontend, File Upload, Service Role, REST API) +- [Writing Backend Functions](#writing-backend-functions) (Basic, Service Role, Secrets, Errors) +- [Setup Requirements](#setup-requirements) +- [Authentication Modes](#authentication-modes) + +## Method + +```javascript +base44.functions.invoke(functionName, data): Promise +``` + +- `functionName`: Name of the backend function +- `data`: Object of parameters (sent as JSON, or multipart if contains File objects) +- Returns: Whatever the function returns + +## Invoking Functions + +### From Frontend + +```javascript +const result = await base44.functions.invoke("processOrder", { + orderId: "order-123", + action: "ship" +}); + +console.log(result); +``` + +### With File Upload + +```javascript +const fileInput = document.querySelector('input[type="file"]'); +const file = fileInput.files[0]; + +// Automatically uses multipart/form-data when File objects present +const result = await base44.functions.invoke("uploadDocument", { + file: file, + category: "invoices" +}); +``` + +### With Service Role (Backend) + +```javascript +// Inside another backend function +const result = await base44.asServiceRole.functions.invoke("adminTask", { + userId: "user-123" +}); +``` + +### Via REST API (curl) + +Functions can be called via HTTP POST to your app domain: + +```bash +curl -X POST "https:///functions/" \ + -H "Content-Type: application/json" \ + -d '{"key": "value"}' +``` + +## Writing Backend Functions + +Backend functions run on Deno. Must export using `Deno.serve()`. + +### Required Directory Structure + +Each function must be in its own subdirectory under `base44/functions/` with a configuration file: + +``` +base44/ + functions/ + process-order/ # kebab-case directory name + function.jsonc # required configuration + index.ts # entry point +``` + +**function.jsonc:** +```jsonc +{ + "name": "process-order", + "entry": "index.ts" +} +``` + +For complete setup and deployment instructions, see [functions-create.md](../../base44-cli/references/functions-create.md) in base44-cli. + +### Basic Structure + +```javascript +// base44/functions/process-order/index.ts +import { createClientFromRequest } from "npm:@base44/sdk"; + +Deno.serve(async (req) => { + // Get authenticated client from request + const base44 = createClientFromRequest(req); + + // Parse input + const { orderId, action } = await req.json(); + + // Your logic here + const order = await base44.entities.Orders.get(orderId); + + // Return response + return Response.json({ + success: true, + order: order + }); +}); +``` + +### With Service Role Access + +```javascript +import { createClientFromRequest } from "npm:@base44/sdk"; + +Deno.serve(async (req) => { + const base44 = createClientFromRequest(req); + + // Check user is authenticated + const user = await base44.auth.me(); + if (!user) { + return Response.json({ error: "Unauthorized" }, { status: 401 }); + } + + // Use service role for admin operations + const allOrders = await base44.asServiceRole.entities.Orders.list(); + + return Response.json({ orders: allOrders }); +}); +``` + +### Using Secrets + +```javascript +Deno.serve(async (req) => { + // Access environment variables (configured in app settings) + const apiKey = Deno.env.get("STRIPE_API_KEY"); + + const response = await fetch("https://api.stripe.com/v1/charges", { + headers: { + "Authorization": `Bearer ${apiKey}` + } + }); + + return Response.json(await response.json()); +}); +``` + +### Error Handling + +```javascript +import { createClientFromRequest } from "npm:@base44/sdk"; + +Deno.serve(async (req) => { + try { + const base44 = createClientFromRequest(req); + const { orderId } = await req.json(); + + const order = await base44.entities.Orders.get(orderId); + if (!order) { + return Response.json( + { error: "Order not found" }, + { status: 404 } + ); + } + + return Response.json({ order }); + + } catch (error) { + return Response.json( + { error: error.message }, + { status: 500 } + ); + } +}); +``` + +## Setup Requirements + +1. Enable Backend Functions in app settings (requires appropriate plan) +2. Create function files in `/functions` folder +3. Configure secrets via app dashboard for API keys + +## Authentication Modes + +| Mode | Context | Permissions | +|------|---------|-------------| +| User | `base44.functions.invoke()` | Runs under calling user's permissions | +| Service Role | `base44.asServiceRole.functions.invoke()` | Admin-level access | + +Inside the function, use `createClientFromRequest(req)` to get a client that inherits the caller's auth context. + +## Type Definitions + +```typescript +/** Functions module for invoking custom backend functions. */ +interface FunctionsModule { + /** + * Invokes a custom backend function by name. + * + * If any parameter is a File object, the request will automatically be + * sent as multipart/form-data. Otherwise, it will be sent as JSON. + * + * @param functionName - The name of the function to invoke. + * @param data - An object containing named parameters for the function. + * @returns Promise resolving to the function's response. + */ + invoke(functionName: string, data: Record): Promise; +} +``` diff --git a/evals/fixtures/sdk-auth/skills/base44-sdk/references/integrations.md b/evals/fixtures/sdk-auth/skills/base44-sdk/references/integrations.md new file mode 100644 index 0000000..f356a18 --- /dev/null +++ b/evals/fixtures/sdk-auth/skills/base44-sdk/references/integrations.md @@ -0,0 +1,377 @@ +# Integrations Module + +Access third-party services via `base44.integrations`. + +## Types of Integrations + +1. **Core/Built-in**: AI, email, file uploads (available by default) +2. **Catalog integrations**: Pre-built connectors from Base44 catalog +3. **Custom integrations**: Your own OpenAPI-based integrations + +## Accessing Integrations + +```javascript +// Core integrations +base44.integrations.Core.FunctionName(params) + +// Custom integrations +base44.integrations.custom.call(slug, operationId, params) +``` + +## Core Integrations + +### InvokeLLM (AI Text Generation) + +Generate text or structured JSON data using AI models. + +```javascript +// Basic prompt - returns string +const response = await base44.integrations.Core.InvokeLLM({ + prompt: "Summarize this text: ..." +}); + +// With internet context (uses Google Search, Maps, News) +const response = await base44.integrations.Core.InvokeLLM({ + prompt: "What's the current weather in NYC?", + add_context_from_internet: true +}); + +// Structured JSON response - returns object +const response = await base44.integrations.Core.InvokeLLM({ + prompt: "Analyze the sentiment of: 'Great product but slow shipping'", + response_json_schema: { + type: "object", + properties: { + sentiment: { type: "string", enum: ["positive", "negative", "mixed"] }, + score: { type: "number", description: "Score from 1-10" }, + key_points: { type: "array", items: { type: "string" } } + } + } +}); +// Returns: { sentiment: "mixed", score: 7, key_points: ["great product", "slow shipping"] } + +// With file attachments (uploaded via UploadFile) +const response = await base44.integrations.Core.InvokeLLM({ + prompt: "Describe what's in this image", + file_urls: ["https://...uploaded_image.png"] +}); +``` + +**Parameters:** +- `prompt` (string, required): The prompt text to send to the model +- `add_context_from_internet` (boolean, optional): If true, uses Google Search/Maps/News for real-time context +- `response_json_schema` (object, optional): JSON schema for structured output +- `file_urls` (string[], optional): URLs of uploaded files for context + +### GenerateImage + +Create AI-generated images from text prompts. + +```javascript +const { url } = await base44.integrations.Core.GenerateImage({ + prompt: "A serene mountain landscape with a lake" +}); +console.log(url); // https://...generated_image.png +``` + +### SendEmail + +Send emails to registered users. Every app gets this integration (no plan upgrade required). + +```javascript +await base44.integrations.Core.SendEmail({ + to: "user@example.com", + subject: "Welcome!", + body: "

Hello

Welcome to our app.

", + from_name: "My App" // optional, defaults to app name +}); +``` + +**Parameters:** +- `to` (string, required): Recipient email address +- `subject` (string, required): Email subject line +- `body` (string, required): Plain text or HTML email body +- `from_name` (string, optional): Sender name displayed to recipient + +**Limitations:** +- 1 credit per email (2 credits with custom domain) + +### UploadFile (Public) + +Upload files to public storage. + +```javascript +const fileInput = document.querySelector('input[type="file"]'); +const { file_url } = await base44.integrations.Core.UploadFile({ + file: fileInput.files[0] +}); +console.log(file_url); // https://...uploaded_file.pdf +``` + +### UploadPrivateFile + +Upload files to private storage that requires a signed URL to access. + +```javascript +const { file_uri } = await base44.integrations.Core.UploadPrivateFile({ + file: fileInput.files[0] +}); +console.log(file_uri); // "private/user123/document.pdf" + +// Create a signed URL to access the file +const { signed_url } = await base44.integrations.Core.CreateFileSignedUrl({ + file_uri, + expires_in: 3600 // URL expires in 1 hour (default: 300 seconds) +}); +console.log(signed_url); // Temporary URL to access the private file +``` + +### CreateFileSignedUrl + +Generate temporary access links for private files. + +```javascript +const { signed_url } = await base44.integrations.Core.CreateFileSignedUrl({ + file_uri: "private/user123/document.pdf", + expires_in: 7200 // 2 hours +}); +``` + +**Parameters:** +- `file_uri` (string, required): URI from UploadPrivateFile +- `expires_in` (number, optional): Expiration time in seconds (default: 300) + +### ExtractDataFromUploadedFile + +Extract structured data from uploaded files using AI. + +```javascript +// First upload the file +const { file_url } = await base44.integrations.Core.UploadFile({ file }); + +// Then extract structured data +const result = await base44.integrations.Core.ExtractDataFromUploadedFile({ + file_url, + json_schema: { + type: "object", + properties: { + invoice_number: { type: "string" }, + total_amount: { type: "number" }, + date: { type: "string" }, + vendor_name: { type: "string" } + } + } +}); +console.log(result); // { invoice_number: "INV-12345", total_amount: 1250.00, ... } +``` + +## Custom Integrations + +Custom integrations allow workspace administrators to connect any external API by importing an OpenAPI specification. Use `base44.integrations.custom.call()` to invoke them. + +### Syntax + +```javascript +const response = await base44.integrations.custom.call( + slug, // Integration identifier (set by admin) + operationId, // Endpoint in "method:path" format + params // Optional: payload, pathParams, queryParams +); +``` + +### Examples + +```javascript +// GET request with query params +const response = await base44.integrations.custom.call( + "my-crm", + "get:/contacts", + { queryParams: { limit: 10, status: "active" } } +); + +// POST request with body +const response = await base44.integrations.custom.call( + "my-crm", + "post:/contacts", + { payload: { name: "John Doe", email: "john@example.com" } } +); + +// Request with path parameters +const response = await base44.integrations.custom.call( + "github", + "post:/repos/{owner}/{repo}/issues", + { + pathParams: { owner: "myorg", repo: "myrepo" }, + payload: { title: "Bug report", body: "Something is broken" } + } +); +``` + +### Response Structure + +```javascript +{ + success: true, // Whether external API returned 2xx + status_code: 200, // HTTP status code + data: { ... } // Response from external API +} +``` + +## Requirements + +- **Core integrations**: Available on all plans +- **Catalog/Custom integrations**: Require Builder plan or higher + +## Type Definitions + +### Core Integration Parameters + +```typescript +/** Parameters for the InvokeLLM function. */ +interface InvokeLLMParams { + /** The prompt text to send to the model. */ + prompt: string; + /** If true, uses Google Search/Maps/News for real-time context. */ + add_context_from_internet?: boolean; + /** JSON schema for structured output. If provided, returns object instead of string. */ + response_json_schema?: object; + /** File URLs (from UploadFile) to provide as context. */ + file_urls?: string[]; +} + +/** Parameters for the GenerateImage function. */ +interface GenerateImageParams { + /** Description of the image to generate. */ + prompt: string; +} + +/** Result from GenerateImage. */ +interface GenerateImageResult { + /** URL of the generated image. */ + url: string; +} + +/** Parameters for the UploadFile function. */ +interface UploadFileParams { + /** The file object to upload. */ + file: File; +} + +/** Result from UploadFile. */ +interface UploadFileResult { + /** URL of the uploaded file. */ + file_url: string; +} + +/** Parameters for the SendEmail function. */ +interface SendEmailParams { + /** Recipient email address. */ + to: string; + /** Email subject line. */ + subject: string; + /** Plain text or HTML email body. */ + body: string; + /** Sender name (defaults to app name). */ + from_name?: string; +} + +/** Parameters for ExtractDataFromUploadedFile. */ +interface ExtractDataFromUploadedFileParams { + /** URL of the uploaded file. */ + file_url: string; + /** JSON schema defining fields to extract. */ + json_schema: object; +} + +/** Parameters for UploadPrivateFile. */ +interface UploadPrivateFileParams { + /** The file object to upload. */ + file: File; +} + +/** Result from UploadPrivateFile. */ +interface UploadPrivateFileResult { + /** URI of the private file (used for signed URLs). */ + file_uri: string; +} + +/** Parameters for CreateFileSignedUrl. */ +interface CreateFileSignedUrlParams { + /** URI from UploadPrivateFile. */ + file_uri: string; + /** Expiration time in seconds (default: 300). */ + expires_in?: number; +} + +/** Result from CreateFileSignedUrl. */ +interface CreateFileSignedUrlResult { + /** Temporary signed URL to access the file. */ + signed_url: string; +} +``` + +### CoreIntegrations + +```typescript +/** Core package containing built-in Base44 integration functions. */ +interface CoreIntegrations { + InvokeLLM(params: InvokeLLMParams): Promise; + GenerateImage(params: GenerateImageParams): Promise; + UploadFile(params: UploadFileParams): Promise; + SendEmail(params: SendEmailParams): Promise; + ExtractDataFromUploadedFile(params: ExtractDataFromUploadedFileParams): Promise; + UploadPrivateFile(params: UploadPrivateFileParams): Promise; + CreateFileSignedUrl(params: CreateFileSignedUrlParams): Promise; +} +``` + +### Custom Integrations + +```typescript +/** Parameters for calling a custom integration endpoint. */ +interface CustomIntegrationCallParams { + /** Request body payload. */ + payload?: Record; + /** Path parameters to substitute in the URL. */ + pathParams?: Record; + /** Query string parameters. */ + queryParams?: Record; + /** Additional headers for this request. */ + headers?: Record; +} + +/** Response from a custom integration call. */ +interface CustomIntegrationCallResponse { + /** Whether the external API returned a 2xx status code. */ + success: boolean; + /** The HTTP status code from the external API. */ + status_code: number; + /** The response data from the external API. */ + data: any; +} + +/** Module for calling custom workspace-level API integrations. */ +interface CustomIntegrationsModule { + /** + * Call a custom integration endpoint. + * @param slug - The integration's unique identifier. + * @param operationId - The operation ID from the OpenAPI spec. + * @param params - Optional payload, pathParams, queryParams, headers. + */ + call(slug: string, operationId: string, params?: CustomIntegrationCallParams): Promise; +} +``` + +### IntegrationsModule + +```typescript +/** Integrations module for calling integration endpoints. */ +type IntegrationsModule = { + /** Core package with built-in integrations. */ + Core: CoreIntegrations; + /** Custom integrations module. */ + custom: CustomIntegrationsModule; + /** Additional integration packages (dynamic). */ + [packageName: string]: any; +}; +``` diff --git a/evals/fixtures/sdk-auth/skills/base44-sdk/references/users.md b/evals/fixtures/sdk-auth/skills/base44-sdk/references/users.md new file mode 100644 index 0000000..4205b06 --- /dev/null +++ b/evals/fixtures/sdk-auth/skills/base44-sdk/references/users.md @@ -0,0 +1,82 @@ +# Users Module + +Invite users to the app via `base44.users`. + +## Contents +- [Methods](#methods) +- [Examples](#examples) (Invite User) +- [Roles](#roles) +- [Notes](#notes) + +## Methods + +| Method | Signature | Description | +|--------|-----------|-------------| +| `inviteUser(user_email, role)` | `Promise` | Invite a user to the app | + +## Examples + +### Invite User + +```javascript +// Invite a user with "user" role +await base44.users.inviteUser("newuser@example.com", "user"); + +// Invite an admin +await base44.users.inviteUser("admin@example.com", "admin"); +``` + +### Invite Multiple Users + +```javascript +const usersToInvite = [ + { email: "user1@example.com", role: "user" }, + { email: "user2@example.com", role: "user" }, + { email: "manager@example.com", role: "admin" } +]; + +for (const user of usersToInvite) { + await base44.users.inviteUser(user.email, user.role); + console.log(`Invited ${user.email} as ${user.role}`); +} +``` + +## Roles + +The `role` parameter must be one of: + +| Role | Description | +|------|-------------| +| `"user"` | Standard user with default permissions | +| `"admin"` | Administrator with elevated permissions | + +**Note:** Only `"user"` and `"admin"` are valid role values. An error will be thrown if you pass any other value. + +## Notes + +- **Email invitation**: The invited user receives an email with a link to join the app +- **Duplicate handling**: Inviting an existing user will re-send the invitation +- **Also available in auth**: `base44.auth.inviteUser()` provides the same functionality +- **Role validation**: Only `"user"` or `"admin"` are accepted + +```javascript +// These are equivalent: +await base44.users.inviteUser("newuser@example.com", "user"); +await base44.auth.inviteUser("newuser@example.com", "user"); +``` + +## Type Definitions + +```typescript +/** Users module for inviting users to the app. */ +interface UsersModule { + /** + * Invite a user to the application. + * @param user_email - User's email address. + * @param role - User's role ('user' or 'admin'). + * @returns Promise resolving when the invitation is sent. + * @throws Error if role is not 'user' or 'admin'. + */ + inviteUser(user_email: string, role: "user" | "admin"): Promise; +} +``` diff --git a/evals/fixtures/sdk-entity-crud/eval.json b/evals/fixtures/sdk-entity-crud/eval.json new file mode 100644 index 0000000..f7855fc --- /dev/null +++ b/evals/fixtures/sdk-entity-crud/eval.json @@ -0,0 +1,223 @@ +{ + "$schema": "../../eval.schema.json", + "name": "sdk-entity-crud", + "description": "Test SDK entity CRUD operations: create, filter, get, delete, subscribe, update", + "prompts": [ + { + "name": "sdk-entity-create", + "description": "Create new entity records", + "prompt": "Create a CreateProduct component in src/components/CreateProduct.jsx that lets users create new products using the Base44 SDK. Include form fields for name and price, and use entities.Product.create() to save.", + "expectedSkills": [ + "base44-sdk" + ], + "checks": [ + { + "type": "file-exists", + "filePath": "src/components/CreateProduct.jsx", + "description": "src/components/CreateProduct.jsx exists" + }, + { + "type": "file-content", + "filePath": "src/components/CreateProduct.jsx", + "pattern": "entities\\.Product\\.create\\(", + "description": "Uses entities.Product.create" + }, + { + "type": "file-content", + "filePath": "src/components/CreateProduct.jsx", + "pattern": "import.*base44|from ['\"].*base44", + "description": "Imports base44 client" + }, + { + "type": "file-content-excluded", + "filePath": "src/components/CreateProduct.jsx", + "pattern": "Product\\.insert\\(", + "description": "Does not use hallucinated Product.insert" + }, + { + "type": "file-content-excluded", + "filePath": "src/components/CreateProduct.jsx", + "pattern": "Product\\.add\\(", + "description": "Does not use hallucinated Product.add" + } + ] + }, + { + "name": "sdk-entity-filter", + "description": "Filter entity records", + "prompt": "Create a FilteredProducts component in src/components/FilteredProducts.jsx that displays products filtered by category using the Base44 SDK. Accept a category prop and use the correct SDK method to filter products by that category.", + "expectedSkills": [ + "base44-sdk" + ], + "checks": [ + { + "type": "file-exists", + "filePath": "src/components/FilteredProducts.jsx", + "description": "src/components/FilteredProducts.jsx exists" + }, + { + "type": "file-content", + "filePath": "src/components/FilteredProducts.jsx", + "pattern": "entities\\.Product\\.filter\\(", + "description": "Uses entities.Product.filter" + }, + { + "type": "file-content-excluded", + "filePath": "src/components/FilteredProducts.jsx", + "pattern": "Product\\.find\\(", + "description": "Does not use hallucinated Product.find" + }, + { + "type": "file-content-excluded", + "filePath": "src/components/FilteredProducts.jsx", + "pattern": "Product\\.where\\(", + "description": "Does not use hallucinated Product.where" + } + ] + }, + { + "name": "sdk-entity-get", + "description": "Get single entity record", + "prompt": "Create a ProductDetail component in src/components/ProductDetail.jsx that displays a single product by ID using the Base44 SDK. Accept a productId prop and use the correct SDK method to fetch a single product.", + "expectedSkills": [ + "base44-sdk" + ], + "checks": [ + { + "type": "file-exists", + "filePath": "src/components/ProductDetail.jsx", + "description": "src/components/ProductDetail.jsx exists" + }, + { + "type": "file-content", + "filePath": "src/components/ProductDetail.jsx", + "pattern": "entities\\.Product\\.get\\(", + "description": "Uses entities.Product.get" + }, + { + "type": "file-content-excluded", + "filePath": "src/components/ProductDetail.jsx", + "pattern": "Product\\.findOne\\(", + "description": "Does not use hallucinated Product.findOne" + }, + { + "type": "file-content-excluded", + "filePath": "src/components/ProductDetail.jsx", + "pattern": "Product\\.findById\\(", + "description": "Does not use hallucinated Product.findById" + } + ] + }, + { + "name": "sdk-entity-delete", + "description": "Delete entity records", + "prompt": "Create a DeleteProduct component in src/components/DeleteProduct.jsx that allows deleting a product by ID using the Base44 SDK. Include a confirmation dialog before deleting. Use the correct SDK method to delete.", + "expectedSkills": [ + "base44-sdk" + ], + "checks": [ + { + "type": "file-exists", + "filePath": "src/components/DeleteProduct.jsx", + "description": "src/components/DeleteProduct.jsx exists" + }, + { + "type": "file-content", + "filePath": "src/components/DeleteProduct.jsx", + "pattern": "entities\\.Product\\.delete\\(", + "description": "Uses entities.Product.delete" + }, + { + "type": "file-content-excluded", + "filePath": "src/components/DeleteProduct.jsx", + "pattern": "Product\\.remove\\(", + "description": "Does not use hallucinated Product.remove" + }, + { + "type": "file-content-excluded", + "filePath": "src/components/DeleteProduct.jsx", + "pattern": "Product\\.destroy\\(", + "description": "Does not use hallucinated Product.destroy" + } + ] + }, + { + "name": "sdk-entity-subscribe", + "description": "Subscribe to real-time entity updates", + "prompt": "Create a LiveProducts component in src/components/LiveProducts.jsx that displays products with real-time updates using the Base44 SDK. Use the correct subscription method to listen for changes and update the UI automatically. Clean up the subscription when the component unmounts.", + "expectedSkills": [ + "base44-sdk" + ], + "checks": [ + { + "type": "file-exists", + "filePath": "src/components/LiveProducts.jsx", + "description": "src/components/LiveProducts.jsx exists" + }, + { + "type": "file-content", + "filePath": "src/components/LiveProducts.jsx", + "pattern": "entities\\.Product\\.subscribe\\(", + "description": "Uses entities.Product.subscribe" + }, + { + "type": "file-content", + "filePath": "src/components/LiveProducts.jsx", + "pattern": "useEffect", + "description": "Uses React useEffect hook" + }, + { + "type": "file-content-excluded", + "filePath": "src/components/LiveProducts.jsx", + "pattern": "Product\\.onChange\\(", + "description": "Does not use hallucinated Product.onChange" + }, + { + "type": "file-content-excluded", + "filePath": "src/components/LiveProducts.jsx", + "pattern": "Product\\.onSnapshot\\(", + "description": "Does not use hallucinated Product.onSnapshot" + } + ] + }, + { + "name": "sdk-entity-update", + "description": "Update entity records", + "prompt": "Create an EditProduct component in src/components/EditProduct.jsx that lets users edit an existing product using the Base44 SDK. Accept a productId prop, fetch the current product data, show a form pre-filled with existing values, and use the correct SDK method to update with the (id, data) two-parameter signature.", + "expectedSkills": [ + "base44-sdk" + ], + "checks": [ + { + "type": "file-exists", + "filePath": "src/components/EditProduct.jsx", + "description": "src/components/EditProduct.jsx exists" + }, + { + "type": "file-content", + "filePath": "src/components/EditProduct.jsx", + "pattern": "entities\\.Product\\.update\\(", + "description": "Uses entities.Product.update" + }, + { + "type": "file-content", + "filePath": "src/components/EditProduct.jsx", + "pattern": "Product\\.update\\s*\\(\\s*\\w+[^,]*,\\s*\\{", + "description": "Matches expected pattern in EditProduct.jsx" + }, + { + "type": "file-content-excluded", + "filePath": "src/components/EditProduct.jsx", + "pattern": "Product\\.save\\(", + "description": "Does not use hallucinated Product.save" + }, + { + "type": "file-content-excluded", + "filePath": "src/components/EditProduct.jsx", + "pattern": "Product\\.set\\(", + "description": "Does not use hallucinated Product.set" + } + ] + } + ] +} diff --git a/evals/fixtures/sdk-entity-crud/project/.claude/settings.json b/evals/fixtures/sdk-entity-crud/project/.claude/settings.json new file mode 100644 index 0000000..ab60505 --- /dev/null +++ b/evals/fixtures/sdk-entity-crud/project/.claude/settings.json @@ -0,0 +1,6 @@ +{ + "permissions": { + "allow": ["Read", "Write", "Edit", "Bash", "Glob", "Grep"] + }, + "skills": ["./skills"] +} diff --git a/evals/fixtures/sdk-entity-crud/project/AGENTS.md b/evals/fixtures/sdk-entity-crud/project/AGENTS.md new file mode 100644 index 0000000..6c0f2be --- /dev/null +++ b/evals/fixtures/sdk-entity-crud/project/AGENTS.md @@ -0,0 +1,25 @@ +IMPORTANT: Prefer retrieval-led reasoning over pre-training-led reasoning. Base44 is NOT Firebase, Supabase, or any other SDK you know. Every API name below is the ONLY correct form. Always verify against this index and the referenced docs. + +[Base44 SDK Docs Index]|root: ./skills/base44-sdk/references +|entities.md: CRUD - create(data), list(), filter(query), get(id), update(id,data), delete(id), subscribe(cb) +|auth.md: Auth - loginViaEmailPassword(), loginWithProvider('google'), me(), register(), logout() +|base44-agents.md: Agents - createConversation({agent_name}), addMessage(conv,{role,content}), subscribeToConversation(id,cb) +|functions.md: Functions - invoke(name, data) +|integrations.md: Core - InvokeLLM({prompt}), SendEmail({to,subject,body}), UploadFile({file}) +|client.md: Client - createClient({appId}), import from "@base44/sdk" +|QUICK_REFERENCE.md: All method signatures + +[Base44 CLI Docs Index]|root: ./skills/base44-cli/references +|entities-create.md: Entity schema - {name:"PascalCase", type:"object", properties:{}, required:[]} +|functions-create.md: Function - function.jsonc {name,entry} + index.ts with Deno.serve + createClientFromRequest +|create.md: Project creation - npx base44 create name -p . +|deploy.md: Deploy - npx base44 deploy -y +|agents-push.md: Agent config - {name:"snake_case", description, instructions, tool_configs:[{entity_name,allowed_operations}|{function_name,description}]} + +[Common Mistakes - NEVER use these hallucinated names] +|SDK: Do NOT use find() → use filter(), findOne() → get(), insert() → create(), remove() → delete(), onChange() → subscribe() +|SDK: Do NOT use signInWithGoogle() → use loginWithProvider('google'), functions.call() → functions.invoke() +|SDK: Do NOT use ai.generate() → use integrations.Core.InvokeLLM({prompt}) +|CLI: Entity names must be PascalCase alphanumeric. File names must be kebab-case.jsonc +|CLI: Agent names must be lowercase with underscores only. Function entry field is "entry" not "entrypoint" +|CLI: Backend functions import from "npm:@base44/sdk" (Deno requires npm: prefix) diff --git a/evals/fixtures/sdk-entity-crud/project/CLAUDE.md b/evals/fixtures/sdk-entity-crud/project/CLAUDE.md new file mode 100644 index 0000000..724ce4d --- /dev/null +++ b/evals/fixtures/sdk-entity-crud/project/CLAUDE.md @@ -0,0 +1,7 @@ +# Base44 Development Guidelines + +When working with Base44 projects, first explore the existing project structure to understand what already exists. Look at existing code, config files, and patterns in use. + +Then consult the AGENTS.md index in the project root for correct API patterns. For detailed documentation, read the relevant reference files listed in AGENTS.md. + +IMPORTANT: Prefer retrieval-led reasoning over pre-training-led reasoning. Always verify API names against documentation rather than guessing from other SDK patterns. diff --git a/evals/fixtures/sdk-entity-crud/project/base44/config.jsonc b/evals/fixtures/sdk-entity-crud/project/base44/config.jsonc new file mode 100644 index 0000000..0667f39 --- /dev/null +++ b/evals/fixtures/sdk-entity-crud/project/base44/config.jsonc @@ -0,0 +1,10 @@ +{ + "name": "Entity CRUD App", + "description": "A test application for SDK entity CRUD operations", + "entitiesDir": "./entities", + "functionsDir": "./functions", + "site": { + "buildCommand": "npm run build", + "outputDirectory": "dist" + } +} diff --git a/evals/fixtures/sdk-entity-crud/project/base44/entities/product.jsonc b/evals/fixtures/sdk-entity-crud/project/base44/entities/product.jsonc new file mode 100644 index 0000000..da2dfd6 --- /dev/null +++ b/evals/fixtures/sdk-entity-crud/project/base44/entities/product.jsonc @@ -0,0 +1,23 @@ +{ + "name": "Product", + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "Product name" + }, + "price": { + "type": "number", + "description": "Product price" + }, + "category": { + "type": "string", + "description": "Product category" + }, + "in_stock": { + "type": "boolean", + "default": true + } + }, + "required": ["name", "price"] +} diff --git a/evals/fixtures/sdk-entity-crud/project/package.json b/evals/fixtures/sdk-entity-crud/project/package.json new file mode 100644 index 0000000..3e6b824 --- /dev/null +++ b/evals/fixtures/sdk-entity-crud/project/package.json @@ -0,0 +1,20 @@ +{ + "name": "entity-crud-app", + "version": "0.1.0", + "private": true, + "type": "module", + "scripts": { + "dev": "vite", + "build": "vite build" + }, + "dependencies": { + "@base44/sdk": "^1.0.0", + "react": "^18.2.0", + "react-dom": "^18.2.0" + }, + "devDependencies": { + "@vitejs/plugin-react": "^4.2.1", + "base44": "latest", + "vite": "^5.2.0" + } +} diff --git a/evals/fixtures/sdk-entity-crud/project/src/App.jsx b/evals/fixtures/sdk-entity-crud/project/src/App.jsx new file mode 100644 index 0000000..5553fc1 --- /dev/null +++ b/evals/fixtures/sdk-entity-crud/project/src/App.jsx @@ -0,0 +1,11 @@ +import React from 'react'; + +function App() { + return ( +
+

Auth Test App

+
+ ); +} + +export default App; diff --git a/evals/fixtures/sdk-entity-crud/project/src/api/base44Client.js b/evals/fixtures/sdk-entity-crud/project/src/api/base44Client.js new file mode 100644 index 0000000..7887147 --- /dev/null +++ b/evals/fixtures/sdk-entity-crud/project/src/api/base44Client.js @@ -0,0 +1,5 @@ +import { createClient } from "@base44/sdk"; + +const base44 = createClient({ appId: "test-app" }); + +export default base44; diff --git a/evals/fixtures/sdk-entity-crud/skills/base44-cli/SKILL.md b/evals/fixtures/sdk-entity-crud/skills/base44-cli/SKILL.md new file mode 100644 index 0000000..3c33f05 --- /dev/null +++ b/evals/fixtures/sdk-entity-crud/skills/base44-cli/SKILL.md @@ -0,0 +1,384 @@ +--- +name: base44-cli +description: "The base44 CLI is used for EVERYTHING related to base44 projects: resource configuration (entities, backend functions, ai agents), initialization and actions (resource creation, deployment). This skill is the place for learning about how to configure resources. When you plan or implement a feature, you must learn this skill" +--- + +# Base44 CLI + +Create and manage Base44 apps (projects) using the Base44 CLI tool. + +## ⚡ IMMEDIATE ACTION REQUIRED - Read This First + +This skill activates on ANY mention of "base44" or when a `base44/` folder exists. **DO NOT read documentation files or search the web before acting.** + +**Your first action MUST be:** +1. Check if `base44/config.jsonc` exists in the current directory +2. If **NO** (new project scenario): + - This skill (base44-cli) handles the request + - Guide user through project initialization + - Do NOT activate base44-sdk yet +3. If **YES** (existing project scenario): + - Transfer to base44-sdk skill for implementation + - This skill only handles CLI commands (login, deploy, entities push) + +## Critical: Local Installation Only + +NEVER call `base44` directly. The CLI is installed locally as a dev dependency and must be accessed via a package manager: + +- `npx base44 ` (npm - recommended) +- `yarn base44 ` (yarn) +- `pnpm base44 ` (pnpm) + +WRONG: `base44 login` +RIGHT: `npx base44 login` + +## MANDATORY: Authentication Check at Session Start + +**CRITICAL**: At the very start of every AI session when this skill is activated, you MUST: + +1. **Check authentication status** by running: + ```bash + npx base44 whoami + ``` + +2. **If the user is logged in** (command succeeds and shows an email): + - Continue with the requested task + +3. **If the user is NOT logged in** (command fails or shows an error): + - **STOP immediately** + - **DO NOT proceed** with any CLI operations + - **Ask the user to login manually** by running: + ```bash + npx base44 login + ``` + - Wait for the user to confirm they have logged in before continuing + +**This check is mandatory and must happen before executing any other Base44 CLI commands.** + +## Overview + +The Base44 CLI provides command-line tools for authentication, creating projects, managing entities, and deploying Base44 applications. It is framework-agnostic and works with popular frontend frameworks like Vite, Next.js, and Create React App, Svelte, Vue, and more. + +## When to Use This Skill vs base44-sdk + +**Use base44-cli when:** +- Creating a **NEW** Base44 project from scratch +- Initializing a project in an empty directory +- Directory is missing `base44/config.jsonc` +- User mentions: "create a new project", "initialize project", "setup a project", "start a new Base44 app" +- Deploying, pushing entities, or authenticating via CLI +- Working with CLI commands (`npx base44 ...`) + +**Use base44-sdk when:** +- Building features in an **EXISTING** Base44 project +- `base44/config.jsonc` already exists +- Writing JavaScript/TypeScript code using Base44 SDK +- Implementing functionality, components, or features +- User mentions: "implement", "build a feature", "add functionality", "write code" + +**Skill Dependencies:** +- `base44-cli` is a **prerequisite** for `base44-sdk` in new projects +- If user wants to "create an app" and no Base44 project exists, use `base44-cli` first +- `base44-sdk` assumes a Base44 project is already initialized + +**State Check Logic:** +Before selecting a skill, check: +- IF (user mentions "create/build app" OR "make a project"): + - IF (directory is empty OR no `base44/config.jsonc` exists): + → Use **base44-cli** (project initialization needed) + - ELSE: + → Use **base44-sdk** (project exists, build features) + +## Project Structure + +A Base44 project combines a standard frontend project with a `base44/` configuration folder: + +``` +my-app/ +├── base44/ # Base44 configuration (created by CLI) +│ ├── config.jsonc # Project settings, site config +│ ├── entities/ # Entity schema definitions +│ │ ├── task.jsonc +│ │ └── board.jsonc +│ ├── functions/ # Backend functions (optional) +│ │ └── my-function/ +│ │ ├── function.jsonc +│ │ └── index.ts +│ └── agents/ # Agent configurations (optional) +│ └── support_agent.jsonc +├── src/ # Frontend source code +│ ├── api/ +│ │ └── base44Client.js # Base44 SDK client +│ ├── pages/ +│ ├── components/ +│ └── main.jsx +├── index.html # SPA entry point +├── package.json +└── vite.config.js # Or your framework's config +``` + +**Key files:** +- `base44/config.jsonc` - Project name, description, site build settings +- `base44/entities/*.jsonc` - Data model schemas (see Entity Schema section) +- `base44/agents/*.jsonc` - Agent configurations (optional) +- `src/api/base44Client.js` - Pre-configured SDK client for frontend use + +**config.jsonc example:** +```jsonc +{ + "name": "My App", // Required: project name + "description": "App description", // Optional: project description + "entitiesDir": "./entities", // Optional: default "entities" + "functionsDir": "./functions", // Optional: default "functions" + "agentsDir": "./agents", // Optional: default "agents" + "site": { // Optional: site deployment config + "installCommand": "npm install", // Optional: install dependencies + "buildCommand": "npm run build", // Optional: build command + "serveCommand": "npm run dev", // Optional: local dev server + "outputDirectory": "./dist" // Optional: build output directory + } +} +``` + +**Config properties:** + +| Property | Description | Default | +|----------|-------------|---------| +| `name` | Project name (required) | - | +| `description` | Project description | - | +| `entitiesDir` | Directory for entity schemas | `"entities"` | +| `functionsDir` | Directory for backend functions | `"functions"` | +| `agentsDir` | Directory for agent configs | `"agents"` | +| `site.installCommand` | Command to install dependencies | - | +| `site.buildCommand` | Command to build the project | - | +| `site.serveCommand` | Command to run dev server | - | +| `site.outputDirectory` | Build output directory for deployment | - | + +## Installation + +Install the Base44 CLI as a dev dependency in your project: + +```bash +npm install --save-dev base44 +``` + +**Important:** Never assume or hardcode the `base44` package version. Always install without a version specifier to get the latest version. + +Then run commands using `npx`: + +```bash +npx base44 +``` + +**Note:** All commands in this documentation use `npx base44`. You can also use `yarn base44`, or `pnpm base44` if preferred. + +## Available Commands + +### Authentication + +| Command | Description | Reference | +| --------------- | ----------------------------------------------- | ------------------------------------------- | +| `base44 login` | Authenticate with Base44 using device code flow | [auth-login.md](references/auth-login.md) | +| `base44 logout` | Logout from current device | [auth-logout.md](references/auth-logout.md) | +| `base44 whoami` | Display current authenticated user | [auth-whoami.md](references/auth-whoami.md) | + +### Project Management + +| Command | Description | Reference | +|---------|-------------|-----------| +| `base44 create` | Create a new Base44 project from a template | [create.md](references/create.md) ⚠️ **MUST READ** | +| `base44 link` | Link an existing local project to Base44 | [link.md](references/link.md) | +| `base44 dashboard` | Open the app dashboard in your browser | [dashboard.md](references/dashboard.md) | + +### Deployment + +| Command | Description | Reference | +|---------|-------------|-----------| +| `base44 deploy` | Deploy all resources (entities, functions, site) | [deploy.md](references/deploy.md) | + +### Entity Management + +| Action / Command | Description | Reference | +| ---------------------- | ------------------------------------------- | --------------------------------------------------- | +| Create Entities | Define entities in `base44/entities` folder | [entities-create.md](references/entities-create.md) | +| `base44 entities push` | Push local entities to Base44 | [entities-push.md](references/entities-push.md) | + +#### Entity Schema (Quick Reference) + +ALWAYS follow this exact structure when creating entity files: + +**File naming:** `base44/entities/{kebab-case-name}.jsonc` (e.g., `team-member.jsonc` for `TeamMember`) + +**Schema template:** +```jsonc +{ + "name": "EntityName", + "type": "object", + "properties": { + "field_name": { + "type": "string", + "description": "Field description" + } + }, + "required": ["field_name"] +} +``` + +**Field types:** `string`, `number`, `integer`, `boolean`, `array`, `object`, `binary` +**String formats:** `date`, `date-time`, `time`, `email`, `uri`, `hostname`, `ipv4`, `ipv6`, `uuid`, `file`, `regex`, `richtext` +**For enums:** Add `"enum": ["value1", "value2"]` and optionally `"default": "value1"` +**Entity names:** Must be alphanumeric only (pattern: `/^[a-zA-Z0-9]+$/`) + +For complete documentation, see [entities-create.md](references/entities-create.md). + +### Function Management + +| Action / Command | Description | Reference | +| ------------------------- | --------------------------------------------- | ------------------------------------------------------- | +| Create Functions | Define functions in `base44/functions` folder | [functions-create.md](references/functions-create.md) | +| `base44 functions deploy` | Deploy local functions to Base44 | [functions-deploy.md](references/functions-deploy.md) | + +### Agent Management + +Agents are conversational AI assistants that can interact with users, access your app's entities, and call backend functions. Use these commands to manage agent configurations. + +| Action / Command | Description | Reference | +| ----------------------- | --------------------------------------- | ----------------------------------------------- | +| Create Agents | Define agents in `base44/agents` folder | See Agent Schema below | +| `base44 agents pull` | Pull remote agents to local files | [agents-pull.md](references/agents-pull.md) | +| `base44 agents push` | Push local agents to Base44 | [agents-push.md](references/agents-push.md) | + +**Note:** Agent commands perform full synchronization - pushing replaces all remote agents with local ones, and pulling replaces all local agents with remote ones. + +#### Agent Schema (Quick Reference) + +**File naming:** `base44/agents/{agent_name}.jsonc` (e.g., `support_agent.jsonc`) + +**Schema template:** +```jsonc +{ + "name": "agent_name", + "description": "Brief description of what this agent does", + "instructions": "Detailed instructions for the agent's behavior", + "tool_configs": [ + // Entity tool - gives agent access to entity operations + { "entity_name": "tasks", "allowed_operations": ["read", "create", "update", "delete"] }, + // Backend function tool - gives agent access to a function + { "function_name": "send_email", "description": "Send an email notification" } + ], + "whatsapp_greeting": "Hello! How can I help you today?" +} +``` + +**Naming rules:** +- Agent names must match pattern: `/^[a-z0-9_]+$/` (lowercase alphanumeric with underscores, 1-100 chars) +- Valid: `support_agent`, `order_bot` +- Invalid: `Support-Agent`, `OrderBot` + +**Required fields:** `name`, `description`, `instructions` +**Optional fields:** `tool_configs` (defaults to `[]`), `whatsapp_greeting` + +**Tool config types:** +- **Entity tools**: `entity_name` + `allowed_operations` (array of: `read`, `create`, `update`, `delete`) +- **Backend function tools**: `function_name` + `description` + +### Site Deployment + +| Command | Description | Reference | +| -------------------- | ----------------------------------------- | ------------------------------------------- | +| `base44 site deploy` | Deploy built site files to Base44 hosting | [site-deploy.md](references/site-deploy.md) | + +**SPA only**: Base44 hosting supports Single Page Applications with a single `index.html` entry point. All routes are served from `index.html` (client-side routing). + +## Quick Start + +1. Install the CLI in your project: + ```bash + npm install --save-dev base44 + ``` + +2. Authenticate with Base44: + ```bash + npx base44 login + ``` + +3. Create a new project (ALWAYS provide name and `--path` flag): + ```bash + npx base44 create my-app -p . + ``` + +4. Build and deploy everything: + ```bash + npm run build + npx base44 deploy -y + ``` + +Or deploy individual resources: +- `npx base44 entities push` - Push entities only +- `npx base44 functions deploy` - Deploy functions only +- `npx base44 agents push` - Push agents only +- `npx base44 site deploy -y` - Deploy site only + +## Common Workflows + +### Creating a New Project + +**⚠️ MANDATORY: Before running `base44 create`, you MUST read [create.md](references/create.md) for:** +- **Template selection** - Choose the correct template (`backend-and-client` vs `backend-only`) +- **Correct workflow** - Different templates require different setup steps +- **Common pitfalls** - Avoid folder creation errors that cause failures + +Failure to follow the create.md instructions will result in broken project scaffolding. + +### Linking an Existing Project +```bash +# If you have base44/config.jsonc but no .app.jsonc +npx base44 link --create --name my-app +``` + +### Deploying All Changes +```bash +# Build your project first +npm run build + +# Deploy everything (entities, functions, and site) +npx base44 deploy -y +``` + +### Deploying Individual Resources +```bash +# Push only entities +npx base44 entities push + +# Deploy only functions +npx base44 functions deploy + +# Push only agents +npx base44 agents push + +# Deploy only site +npx base44 site deploy -y +``` + +### Opening the Dashboard +```bash +# Open app dashboard in browser +npx base44 dashboard +``` + +## Authentication + +Most commands require authentication. If you're not logged in, the CLI will automatically prompt you to login. Your session is stored locally and persists across CLI sessions. + +## Troubleshooting + +| Error | Solution | +| --------------------------- | ----------------------------------------------------------------------------------- | +| Not authenticated | Run `npx base44 login` first | +| No entities found | Ensure entities exist in `base44/entities/` directory | +| Entity not recognized | Ensure file uses kebab-case naming (e.g., `team-member.jsonc` not `TeamMember.jsonc`) | +| No functions found | Ensure functions exist in `base44/functions/` with valid `function.jsonc` configs | +| No agents found | Ensure agents exist in `base44/agents/` directory with valid `.jsonc` configs | +| Invalid agent name | Agent names must be lowercase alphanumeric with underscores only | +| No site configuration found | Check that `site.outputDirectory` is configured in project config | +| Site deployment fails | Ensure you ran `npm run build` first and the build succeeded | diff --git a/evals/fixtures/sdk-entity-crud/skills/base44-cli/references/agents-pull.md b/evals/fixtures/sdk-entity-crud/skills/base44-cli/references/agents-pull.md new file mode 100644 index 0000000..6a700f0 --- /dev/null +++ b/evals/fixtures/sdk-entity-crud/skills/base44-cli/references/agents-pull.md @@ -0,0 +1,73 @@ +# base44 agents pull + +Pull AI agent configurations from Base44 to local files. Agents are conversational AI assistants that can interact with users, access your app's entities, and call backend functions. + +## Syntax + +```bash +npx base44 agents pull +``` + +## Authentication + +**Required**: Yes. If not authenticated, you'll be prompted to login first. + +## What It Does + +1. Fetches all agents from Base44 +2. Writes agent files to the `base44/agents/` directory +3. Deletes local agent files that don't exist remotely +4. Reports written and deleted agents + +## Prerequisites + +- Must be run from a Base44 project directory +- Project must be linked to a Base44 app + +## Output + +```bash +$ npx base44 agents pull + +Fetching agents from Base44... +✓ Agents fetched successfully + +Writing agent files... +✓ Agent files written successfully + +Written: support_agent, order_bot +Deleted: old_agent + +Pulled 2 agents to base44/agents +``` + +## Agent Synchronization + +The pull operation synchronizes remote agents to your local files: + +- **Written**: Agent files created or updated from remote +- **Deleted**: Local agent files removed (didn't exist remotely) + +**Warning**: This operation replaces all local agent configurations with remote versions. Any local changes not pushed to Base44 will be overwritten. + +## Error Handling + +If no agents exist on Base44: +```bash +$ npx base44 agents pull +No agents found on Base44 +``` + +## Use Cases + +- Sync agent configurations to a new development machine +- Get the latest agent configurations from your team +- Restore local agent files after accidental deletion +- Start working on an existing project with agents + +## Notes + +- This command syncs agent configurations, not conversation data +- Agent files are stored as `.jsonc` in the `base44/agents/` directory +- The directory location is configurable via `agentsDir` in `config.jsonc` +- Use `base44 agents push` to upload local changes to Base44 diff --git a/evals/fixtures/sdk-entity-crud/skills/base44-cli/references/agents-push.md b/evals/fixtures/sdk-entity-crud/skills/base44-cli/references/agents-push.md new file mode 100644 index 0000000..fa2795b --- /dev/null +++ b/evals/fixtures/sdk-entity-crud/skills/base44-cli/references/agents-push.md @@ -0,0 +1,156 @@ +# base44 agents push + +Push local AI agent configurations to Base44. Agents are conversational AI assistants that can interact with users, access your app's entities, and call backend functions. + +## Syntax + +```bash +npx base44 agents push +``` + +## Authentication + +**Required**: Yes. If not authenticated, you'll be prompted to login first. + +## What It Does + +1. Reads all agent files from the `base44/agents/` directory +2. Validates agent configurations +3. Displays the count of agents to be pushed +4. Uploads agents to the Base44 backend +5. Reports the results: created, updated, and deleted agents + +## Prerequisites + +- Must be run from a Base44 project directory +- Project must have agent definitions in the `base44/agents/` folder + +## Output + +```bash +$ npx base44 agents push + +Found 2 agents to push +Pushing agents to Base44... + +Created: support_agent +Updated: order_bot +Deleted: old_agent + +✓ Agents pushed to Base44 +``` + +## Agent Synchronization + +The push operation synchronizes your local agents with Base44: + +- **Created**: New agents that didn't exist in Base44 +- **Updated**: Existing agents with modified configuration +- **Deleted**: Agents that were removed from your local configuration + +**Warning**: This is a full sync operation. Agents removed locally will be deleted from Base44. + +## Error Handling + +If no agents are found in your project: +```bash +$ npx base44 agents push +No local agents found - this will delete all remote agents +``` + +If an agent has an invalid name: +```bash +$ npx base44 agents push +Error: Agent name must be lowercase alphanumeric with underscores +``` + +## Agent Configuration Schema + +Each agent file should be a `.jsonc` file in `base44/agents/` with this structure: + +```jsonc +{ + "name": "agent_name", // Required: lowercase alphanumeric with underscores, 1-100 chars + "description": "Brief description of what this agent does", // Required: min 1 char + "instructions": "Detailed instructions for the agent's behavior", // Required: min 1 char + "tool_configs": [ // Optional: defaults to [] + // Entity tool - gives agent access to entity operations + { "entity_name": "Task", "allowed_operations": ["read", "create", "update", "delete"] }, + // Backend function tool - gives agent access to a function + { "function_name": "send_email", "description": "Send an email notification" } + ], + "whatsapp_greeting": "Hello! How can I help you today?" // Optional +} +``` + +**Naming rules:** +- **Agent names** must match pattern: `/^[a-z0-9_]+$/` (lowercase alphanumeric with underscores only, 1-100 characters) + - Valid: `support_agent`, `order_bot`, `task_helper` + - Invalid: `Support-Agent`, `OrderBot`, `task helper` +- **Agent file names** must use underscores (matching the agent name) + - Valid: `support_agent.jsonc`, `order_bot.jsonc` + - Invalid: `support-agent.jsonc` (hyphens not allowed) +- **Entity names in `tool_configs`** must use PascalCase (matching the entity's `name` field) + - Valid: `"entity_name": "Task"`, `"entity_name": "TeamMember"` + - Invalid: `"entity_name": "task"`, `"entity_name": "team_member"` + +**Required fields:** +- `name`: Required, must follow naming rules above +- `description`: Required, minimum 1 character +- `instructions`: Required, minimum 1 character +- `tool_configs`: Optional, defaults to empty array +- `whatsapp_greeting`: Optional + +### Common Mistake: Wrong tool_configs Format + +**WRONG** - Do NOT use `tools` with `type` and `entity`: +```jsonc +{ + "name": "my_agent", + "tools": [ // ❌ WRONG + { "type": "entity_query", "entity": "Task" } + ] +} +``` + +**CORRECT** - Use `tool_configs` with `entity_name` and `allowed_operations`: +```jsonc +{ + "name": "my_agent", + "tool_configs": [ // ✅ CORRECT + { "entity_name": "Task", "allowed_operations": ["read"] } + ] +} +``` + +### Best Practices for Agent Instructions + +When giving agents access to entities, be explicit in the instructions about using the tools: + +```jsonc +{ + "name": "support_agent", + "instructions": "You are a helpful support agent.\n\nIMPORTANT: You have access to customer data through entity tools. When users ask about their orders or account:\n1. ALWAYS use the Order entity tool to query their order history\n2. Use the Customer entity tool to look up account details\n3. Analyze the data and provide personalized responses\n\nAlways query the relevant entities first before answering questions about user data.", + "tool_configs": [ + { "entity_name": "Order", "allowed_operations": ["read"] }, + { "entity_name": "Customer", "allowed_operations": ["read"] } + ] +} +``` + +Without explicit instructions to use the entity tools, the agent may not proactively query user data when asked. + +## Use Cases + +- After defining new agents in your project +- When modifying existing agent configurations +- To sync agent changes before testing +- As part of your development workflow when agent behavior changes + +## Notes + +- This command syncs the agent configuration, not conversation data +- Changes are applied to your Base44 project immediately +- Make sure to test agent changes in a development environment first +- Agent definitions are located in the `base44/agents/` directory +- Use `base44 agents pull` to download agents from Base44 diff --git a/evals/fixtures/sdk-entity-crud/skills/base44-cli/references/auth-login.md b/evals/fixtures/sdk-entity-crud/skills/base44-cli/references/auth-login.md new file mode 100644 index 0000000..adebd27 --- /dev/null +++ b/evals/fixtures/sdk-entity-crud/skills/base44-cli/references/auth-login.md @@ -0,0 +1,54 @@ +# base44 login + +Authenticate with Base44 using device code flow. + +## Syntax + +```bash +npx base44 login +``` + +## Authentication + +**Required**: No (this is the login command itself) + +## How It Works + +The login command uses OAuth 2.0 device code flow for authentication: + +1. Generates a device code for authentication +2. Displays a verification code and verification URI +3. Directs you to visit the URI and enter the code +4. Polls for authentication completion (up to device code expiration) +5. Retrieves access and refresh tokens upon successful authentication +6. Fetches and displays your user information +7. Saves authentication data locally with expiration timestamp + +## Interactive Flow + +```bash +$ npx base44 login + +Please visit: https://auth.base44.com/device +Enter code: ABCD-EFGH + +Waiting for authentication... +✓ Successfully authenticated! + +Logged in as: user@example.com +``` + +## Session Management + +- Authentication tokens are stored locally on your device +- Tokens include expiration timestamps +- The session persists across CLI sessions +- Other commands will automatically use your stored credentials +- Use `npx base44 logout` to clear your session +- Use `npx base44 whoami` to check your current authentication status + +## Notes + +- You only need to login once per device +- If your session expires, you'll be prompted to login again when running authenticated commands +- The CLI automatically prompts for login when you run commands that require authentication diff --git a/evals/fixtures/sdk-entity-crud/skills/base44-cli/references/auth-logout.md b/evals/fixtures/sdk-entity-crud/skills/base44-cli/references/auth-logout.md new file mode 100644 index 0000000..33c2ddf --- /dev/null +++ b/evals/fixtures/sdk-entity-crud/skills/base44-cli/references/auth-logout.md @@ -0,0 +1,32 @@ +# base44 logout + +Logout from current device and clear stored authentication data. + +## Syntax + +```bash +npx base44 logout +``` + +## Authentication + +**Required**: No + +## What It Does + +- Deletes stored authentication data from your device +- Clears your local session +- Removes access and refresh tokens + +## Output + +```bash +$ npx base44 logout +Logged out successfully +``` + +## Notes + +- You can logout even if you're not currently logged in (no error) +- After logout, you'll need to run `npx base44 login` again to use authenticated commands +- This only affects the current device; your Base44 account remains active diff --git a/evals/fixtures/sdk-entity-crud/skills/base44-cli/references/auth-whoami.md b/evals/fixtures/sdk-entity-crud/skills/base44-cli/references/auth-whoami.md new file mode 100644 index 0000000..abe450c --- /dev/null +++ b/evals/fixtures/sdk-entity-crud/skills/base44-cli/references/auth-whoami.md @@ -0,0 +1,37 @@ +# base44 whoami + +Display the currently authenticated user. + +## Syntax + +```bash +npx base44 whoami +``` + +## Authentication + +**Required**: Yes. If not authenticated, you'll be prompted to login first. + +## What It Does + +- Reads stored authentication data +- Displays the email of the currently logged-in user + +## Output + +```bash +$ npx base44 whoami +Logged in as: user@example.com +``` + +## Use Cases + +- Verify you're logged in before running other commands +- Check which account you're currently using +- Confirm authentication is working properly +- Useful in scripts or automation to verify credentials + +## Notes + +- If you're not logged in, the command will prompt you to authenticate first +- The email displayed matches your Base44 account email diff --git a/evals/fixtures/sdk-entity-crud/skills/base44-cli/references/create.md b/evals/fixtures/sdk-entity-crud/skills/base44-cli/references/create.md new file mode 100644 index 0000000..7580645 --- /dev/null +++ b/evals/fixtures/sdk-entity-crud/skills/base44-cli/references/create.md @@ -0,0 +1,111 @@ +# base44 create + +Creates a new Base44 project from a template. This command is framework-agnostic and can either scaffold a complete project or add Base44 configuration to an existing project. + +## Critical: Non-Interactive Mode Required + +ALWAYS provide both the project name AND `--path` flag. Without both, the command opens an interactive TUI which agents cannot use properly. + +WRONG: `npx base44 create` +WRONG: `npx base44 create my-app` +RIGHT: `npx base44 create my-app -p ./my-app` + +## Syntax + +```bash +npx base44 create [name] --path [options] +``` + +## Arguments & Options + +| Argument/Option | Description | Required | +|--------|-------------|----------| +| `name` | Project name (positional argument) | Yes* | +| `-p, --path ` | Path where to create the project | Yes* | +| `-t, --template ` | Template ID (see templates below) | No | +| `--deploy` | Build and deploy the site (includes pushing entities) | No | +| `--no-skills` | Skip AI agent skills installation (skills are added by default) | No | + +*Required for non-interactive mode. Both `name` and `--path` must be provided together. + +## Template Selection (CRITICAL - Choose Appropriately) + +**You MUST select the most appropriate template based on user requirements:** + +| Template ID | When to Use | Example Scenarios | +|-------------|-------------|-------------------| +| `backend-and-client` | Creating a NEW full-stack web app from scratch | "Create a task app", "Build me a dashboard", "Make a SaaS app" | +| `backend-only` | Adding Base44 to an EXISTING project OR using a different framework (Next.js, Vue, Svelte, etc.) | "Add Base44 to my project", "I want to use Next.js", "I already have a frontend" | + +**Default Choice:** When the user asks to "create an app" or "build a project" without specifying a particular framework, use `backend-and-client` to provide a complete, production-ready application with Vite + React + Tailwind. + +## The `--path` Flag + +- **For `backend-and-client` template (new projects):** Use a new subfolder path + ```bash + npx base44 create my-app -p ./my-app -t backend-and-client + ``` +- **For `backend-only` template (existing projects):** Use `-p .` in the current directory + ```bash + npx base44 create my-app -p . + ``` + +## Workflow: Using `backend-only` with External Frameworks + +**CRITICAL: The project folder MUST exist BEFORE running `base44 create` with `backend-only`** + +The `backend-only` template only adds Base44 configuration files - it does NOT create a frontend. If you need a frontend with a specific framework: + +```bash +# Step 1: Initialize the frontend project FIRST +npm create vite@latest my-app -- --template react # or vue, svelte, etc. +# OR: npx create-next-app@latest my-app +# OR: any other framework's init command + +# Step 2: Navigate into the created folder +cd my-app + +# Step 3: Install Base44 CLI +npm install --save-dev base44 + +# Step 4: Add Base44 configuration +npx base44 create my-app -p . +``` + +**WARNING:** Do NOT: +- Create an empty folder manually, then try to run `npx create vite` inside it (will fail - folder exists) +- Run `base44 create` with `backend-only` expecting it to create a frontend (it won't) + +**DO:** +- Run the external framework's init command FIRST (it creates its own folder) +- Then run `base44 create` inside that folder with `-p .` + +## Examples + +```bash +# RECOMMENDED: Create full-stack project (for new apps) +npx base44 create my-app -p ./my-app -t backend-and-client + +# Create full-stack and deploy in one step +npx base44 create my-app -p ./my-app -t backend-and-client --deploy + +# Add Base44 to EXISTING project (must be inside the project folder) +npx base44 create my-app -p . + +# Add Base44 to existing project and deploy +npx base44 create my-app -p . --deploy + +# Create without adding AI agent skills +npx base44 create my-app -p . --no-skills +``` + +## What It Does + +1. Applies the selected template to the target path +2. Creates a `base44/` folder with configuration files +3. Registers the project with Base44 backend +4. Creates `base44/.app.jsonc` with the app ID +5. If `--deploy` is used: + - Pushes any entities defined in `base44/entities/` + - Runs install and build commands (for templates with frontend) + - Deploys the site to Base44 hosting diff --git a/evals/fixtures/sdk-entity-crud/skills/base44-cli/references/dashboard.md b/evals/fixtures/sdk-entity-crud/skills/base44-cli/references/dashboard.md new file mode 100644 index 0000000..95a534a --- /dev/null +++ b/evals/fixtures/sdk-entity-crud/skills/base44-cli/references/dashboard.md @@ -0,0 +1,35 @@ +# base44 dashboard + +Opens the Base44 app dashboard in your default web browser. + +## Syntax + +```bash +npx base44 dashboard +``` + +## Options + +This command has no options. + +## What It Does + +1. Reads the project's app ID from `base44/.app.jsonc` +2. Opens the dashboard URL in your default browser + +## Example + +```bash +# Open dashboard for current project +npx base44 dashboard +``` + +## Requirements + +- Must be run from a linked Base44 project directory (contains `base44/.app.jsonc`) +- Must be authenticated (run `npx base44 login` first) + +## Notes + +- The dashboard provides a web interface to manage your app's entities, functions, users, and settings +- If you're not in a project directory, the command will fail with an error diff --git a/evals/fixtures/sdk-entity-crud/skills/base44-cli/references/deploy.md b/evals/fixtures/sdk-entity-crud/skills/base44-cli/references/deploy.md new file mode 100644 index 0000000..500526f --- /dev/null +++ b/evals/fixtures/sdk-entity-crud/skills/base44-cli/references/deploy.md @@ -0,0 +1,86 @@ +# base44 deploy + +Deploys all project resources (entities, functions, and site) to Base44 in a single command. + +## Syntax + +```bash +npx base44 deploy [options] +``` + +## Options + +| Option | Description | +|--------|-------------| +| `-y, --yes` | Skip confirmation prompt | + +## What It Deploys + +The command automatically detects and deploys: + +1. **Entities** - All `.jsonc` files in `base44/entities/` +2. **Functions** - All functions in `base44/functions/` +3. **Agents** - All agent configurations in `base44/agents/` +4. **Site** - Built files from `site.outputDirectory` (if configured) + +## Examples + +```bash +# Interactive mode - shows what will be deployed and asks for confirmation +npx base44 deploy + +# Non-interactive - skip confirmation (for CI/CD or agent use) +npx base44 deploy -y +``` + +## Typical Workflow + +```bash +# 1. Make your changes (entities, functions, frontend code) + +# 2. Build the frontend (if you have one) +npm run build + +# 3. Deploy everything +npx base44 deploy -y +``` + +## What It Does + +1. Reads project configuration from `base44/config.jsonc` +2. Detects available resources (entities, functions, site) +3. Shows a summary of what will be deployed +4. Asks for confirmation (unless `-y` flag is used) +5. Deploys all resources in sequence: + - Pushes entity schemas + - Deploys functions + - Pushes agent configurations + - Uploads site files +6. Displays the dashboard URL and app URL (if site was deployed) + +## Requirements + +- Must be run from a linked Base44 project directory +- Must be authenticated (run `npx base44 login` first) +- For site deployment, must run `npm run build` first + +## Output + +After successful deployment: +- **Dashboard**: Link to your app's management dashboard +- **App URL**: Your deployed site's public URL (if site was included) + +## Notes + +- If no resources are found, the command exits with a message +- Use individual commands (`entities push`, `functions deploy`, `site deploy`) if you only want to deploy specific resources +- The site must be built before deployment - this command does not run `npm run build` for you + +## Related Commands + +| Command | Description | +|---------|-------------| +| `base44 entities push` | Push only entities | +| `base44 functions deploy` | Deploy only functions | +| `base44 agents push` | Push only agents | +| `base44 site deploy` | Deploy only the site | diff --git a/evals/fixtures/sdk-entity-crud/skills/base44-cli/references/entities-create.md b/evals/fixtures/sdk-entity-crud/skills/base44-cli/references/entities-create.md new file mode 100644 index 0000000..29b40aa --- /dev/null +++ b/evals/fixtures/sdk-entity-crud/skills/base44-cli/references/entities-create.md @@ -0,0 +1,552 @@ +# Creating Entities + +Base44 entities are defined locally in your project and then pushed to the Base44 backend. + +## Critical: File Naming + +Entity files MUST use kebab-case naming: `{kebab-case-name}.jsonc` + +| Entity Name | File Name | +|-------------|-----------| +| `Task` | `task.jsonc` | +| `TeamMember` | `team-member.jsonc` | +| `ActivityLog` | `activity-log.jsonc` | + +WRONG: `TeamMember.jsonc`, `teamMember.jsonc` +RIGHT: `team-member.jsonc` + +## Table of Contents + +- [Creating Entities](#creating-entities) + - [Entity Directory](#entity-directory) + - [How to Create an Entity](#how-to-create-an-entity) + - [Entity Schema Structure](#entity-schema-structure) + - [Supported Field Types](#supported-field-types) + - [Field Properties](#field-properties) + - [Complete Example](#complete-example) + - [Naming Conventions](#naming-conventions) + - [Relationships Between Entities](#relationships-between-entities) + - [Row Level Security (RLS)](#row-level-security-rls) + - [Field Level Security (FLS)](#field-level-security-fls) + - [Pushing Entities](#pushing-entities) + +## Entity Directory + +All entity definitions must be placed in the `base44/entities/` folder in your project root. Each entity is defined in its own `.jsonc` file. + +Example structure: +``` +my-app/ + base44/ + entities/ + user.jsonc + product.jsonc + order.jsonc +``` + +## How to Create an Entity + +1. Create a new `.jsonc` file in the `base44/entities/` directory +2. Define your entity schema following the structure below +3. Push the changes to Base44 using the CLI + +## Entity Schema Structure + +Each entity file follows a JSON Schema-like structure: + +```jsonc +{ + "name": "EntityName", // PascalCase entity name + "type": "object", // Always "object" + "properties": { + // Define your fields here + }, + "required": ["field1"] // Array of required field names +} +``` + +### Common Mistake: Nested Schema Property + +**WRONG** - Do NOT wrap properties in a `schema` object: +```jsonc +{ + "name": "Task", + "description": "A task entity", + "schema": { // ❌ WRONG - don't use nested "schema" + "type": "object", + "properties": { ... } + } +} +``` + +**CORRECT** - Put `type` and `properties` at the top level: +```jsonc +{ + "name": "Task", + "description": "A task entity", + "type": "object", // ✅ CORRECT - top level + "properties": { ... } // ✅ CORRECT - top level +} +``` + +This is a common mistake that will cause "Invalid schema: Schema must have a 'type' field" errors when pushing entities. + +## Supported Field Types + +### String + +Basic text field: +```jsonc +{ + "title": { + "type": "string", + "description": "Task title" + } +} +``` + +With format: +```jsonc +{ + "due_date": { + "type": "string", + "format": "date", + "description": "Due date" + } +} +``` + +Available formats: `date`, `date-time`, `time`, `email`, `uri`, `hostname`, `ipv4`, `ipv6`, `uuid`, `file`, `regex`, `richtext` + +### String with Enum + +Constrained to specific values: +```jsonc +{ + "status": { + "type": "string", + "enum": ["todo", "in_progress", "done"], + "default": "todo", + "description": "Current status" + } +} +``` + +### Number + +```jsonc +{ + "position": { + "type": "number", + "description": "Position for ordering" + } +} +``` + +### Integer + +For whole numbers only: +```jsonc +{ + "quantity": { + "type": "integer", + "description": "Item quantity", + "minimum": 0, + "maximum": 1000 + } +} +``` + +### Binary + +For file/blob data: +```jsonc +{ + "attachment": { + "type": "binary", + "description": "File attachment" + } +} +``` + +### Boolean + +```jsonc +{ + "notify_on_change": { + "type": "boolean", + "default": true, + "description": "Enable notifications" + } +} +``` + +### Array of Strings + +```jsonc +{ + "labels": { + "type": "array", + "items": { "type": "string" }, + "description": "Task labels/tags" + } +} +``` + +### Array of Objects + +```jsonc +{ + "attachments": { + "type": "array", + "description": "File attachments", + "items": { + "type": "object", + "properties": { + "name": { "type": "string" }, + "url": { "type": "string" }, + "type": { "type": "string" } + } + } + } +} +``` + +## Field Properties + +| Property | Description | +| ------------- | ---------------------------------------------------------------------------------------- | +| `type` | Data type: `string`, `number`, `integer`, `boolean`, `array`, `object`, `binary` | +| `description` | Human-readable description of the field | +| `enum` | Array of allowed values (for strings) | +| `enumNames` | Human-readable labels for enum values (same order as `enum`) | +| `default` | Default value when not provided | +| `format` | Format hint: `date`, `date-time`, `time`, `email`, `uri`, `hostname`, `ipv4`, `ipv6`, `uuid`, `file`, `regex`, `richtext` | +| `items` | Schema for array items | +| `properties` | Nested properties for object types | +| `$ref` | Reference to another schema definition | +| `minLength` | Minimum string length | +| `maxLength` | Maximum string length | +| `pattern` | Regex pattern for string validation | +| `minimum` | Minimum value for numbers | +| `maximum` | Maximum value for numbers | +| `rls` | Field-level security rules (see Field Level Security section) | + +## Complete Example + +Here's a complete entity definition for a Task: + +```jsonc +{ + "name": "Task", + "type": "object", + "properties": { + "title": { + "type": "string", + "description": "Task title" + }, + "description": { + "type": "string", + "description": "Task description" + }, + "status": { + "type": "string", + "enum": ["todo", "in_progress", "done"], + "default": "todo", + "description": "Current status of the task" + }, + "board_id": { + "type": "string", + "description": "Board this task belongs to" + }, + "assignee_email": { + "type": "string", + "description": "Email of assigned user" + }, + "priority": { + "type": "string", + "enum": ["low", "medium", "high"], + "default": "medium", + "description": "Task priority" + }, + "due_date": { + "type": "string", + "format": "date", + "description": "Due date" + }, + "labels": { + "type": "array", + "items": { "type": "string" }, + "description": "Task labels/tags" + } + }, + "required": ["title"] +} +``` + +## Naming Conventions + +- **Entity name**: Use PascalCase with alphanumeric characters only (e.g., `Task`, `TeamMember`, `ActivityLog`) + - Must match pattern: `/^[a-zA-Z0-9]+$/` + - Valid: `Task`, `TeamMember`, `Order123` + - Invalid: `Team_Member`, `Team-Member`, `Team Member` +- **File name**: Use kebab-case matching the entity (e.g., `task.jsonc`, `team-member.jsonc`, `activity-log.jsonc`) +- **Field names**: Use snake_case (e.g., `board_id`, `user_email`, `due_date`) + +## Relationships Between Entities + +To create relationships between entities, use ID reference fields: + +```jsonc +{ + "board_id": { + "type": "string", + "description": "Board this task belongs to" + }, + "team_id": { + "type": "string", + "description": "Associated team ID" + } +} +``` + +## Row Level Security (RLS) + +Row Level Security (RLS) controls which records users can access based on their identity and attributes. RLS rules are defined per entity inside the `rls` field of the schema. + +**Important:** If no RLS is defined, all records are accessible to all users. + +### RLS Operations + +RLS supports five operations: + +| Operation | Description | +|-----------|-------------| +| `create` | Control who can add new records | +| `read` | Control who can view records | +| `update` | Control who can modify records | +| `delete` | Control who can remove records | +| `write` | Shorthand for `create`, `update`, and `delete` combined | + +### Permission Values + +Each operation accepts one of the following values: + +1. **`true`** - Allow all users (including anonymous/unauthenticated) +2. **`false`** - Block all users +3. **Condition object** - Allow users matching the condition + +### Template Variables + +Use template variables to reference the current user's attributes: + +| Template | Description | +|----------|-------------| +| `{{user.id}}` | The user's ID | +| `{{user.email}}` | The user's email | +| `{{user.role}}` | The user's role | +| `{{user.data.field_name}}` | Custom field from the user's `data` object | + +### Built-in Entity Attributes + +Every entity record has these built-in attributes available for RLS rules: + +| Attribute | Description | +|-----------|-------------| +| `id` | Unique record identifier | +| `created_date` | Timestamp when record was created | +| `updated_date` | Timestamp when record was last updated | +| `created_by` | Email of the user who created the record | + +### Rule Types + +There are two condition types you can use: + +**1. Entity-to-user comparison** - Compare record fields to the current user's values: +```jsonc +{ + "created_by": "{{user.email}}" +} +``` + +**2. User condition check** - Check user properties directly using `user_condition`: +```jsonc +{ + "user_condition": { "role": "admin" } +} +``` + +**Important limitations:** +- `user_condition` only supports **simple equality** (e.g., `{ "role": "admin" }`) +- For `data.*` field comparisons, you can use operators: `$in`, `$nin`, `$ne`, `$all` +- Logical operators `$or`, `$and`, `$nor` are available for combining conditions +- You cannot filter by entity field values directly (e.g., `{"status": "published"}`) +- Only user-related conditions are allowed + +### RLS Examples + +**Owner-only access:** +```jsonc +{ + "created_by": "{{user.email}}" +} +``` + +**Department-based access:** +```jsonc +{ + "data.department": "{{user.data.department}}" +} +``` + +**Admin-only access:** +```jsonc +{ + "user_condition": { "role": "admin" } +} +``` + +**Complete RLS configuration:** +```jsonc +{ + "name": "Task", + "type": "object", + "properties": { + "title": { + "type": "string", + "description": "Task title" + }, + "status": { + "type": "string", + "enum": ["todo", "in_progress", "done"], + "default": "todo" + } + }, + "required": ["title"], + "rls": { + "create": true, + "read": { "created_by": "{{user.email}}" }, + "update": { "created_by": "{{user.email}}" }, + "delete": { "created_by": "{{user.email}}" } + } +} +``` + +### Common RLS Patterns + +**Public create, admin-only management (e.g., contact forms, waitlists):** +```jsonc +{ + "rls": { + "create": true, + "read": { "user_condition": { "role": "admin" } }, + "update": { "user_condition": { "role": "admin" } }, + "delete": { "user_condition": { "role": "admin" } } + } +} +``` + +**Owner-only access:** +```jsonc +{ + "rls": { + "create": true, + "read": { "created_by": "{{user.email}}" }, + "update": { "created_by": "{{user.email}}" }, + "delete": { "created_by": "{{user.email}}" } + } +} +``` + +**Logged-in users only:** +```jsonc +{ + "rls": { + "create": { "user_condition": { "id": "{{user.id}}" } }, + "read": true, + "update": { "created_by": "{{user.email}}" }, + "delete": { "created_by": "{{user.email}}" } + } +} +``` + +### Limitations + +- **user_condition is equality only:** `user_condition` only supports exact match (e.g., `{ "role": "admin" }`) - no operators +- **No comparison operators on user_condition:** `$gt`, `$lt`, `$regex`, `$expr`, `$where` are NOT supported for user conditions +- **No entity field filtering:** Cannot filter by entity field values (e.g., `{"status": "published"}`) +- **No deeply nested templates:** Templates like `{{user.data.profile.department}}` may not work + +**Supported operators:** +- **Logical operators:** `$or`, `$and`, `$nor` for combining multiple conditions +- **Field operators (for `data.*` fields only):** `$in`, `$nin`, `$ne`, `$all` + +### Complex Access Patterns + +For complex access patterns that require multiple conditions (e.g., "owner OR admin"), you have two options: + +1. **Use the Base44 Dashboard UI** - The dashboard allows adding multiple rules per operation with OR logic +2. **Use separate entities** - Split data into multiple entities with different access rules +3. **Use backend functions** - Implement custom access logic in backend functions + +## Field Level Security (FLS) + +Field Level Security allows you to control access to individual fields within an entity. FLS rules are defined within each field's schema using the `rls` property. + +### FLS Operations + +FLS supports the same operations as entity-level RLS: + +| Operation | Description | +|-----------|-------------| +| `create` | Control who can set this field when creating records | +| `read` | Control who can view this field | +| `update` | Control who can modify this field | +| `delete` | Control who can clear this field | +| `write` | Shorthand for `create`, `update`, and `delete` combined | + +### FLS Example + +```jsonc +{ + "name": "Employee", + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "Employee name" + }, + "salary": { + "type": "number", + "description": "Employee salary", + "rls": { + "read": { "user_condition": { "role": "hr" } }, + "update": { "user_condition": { "role": "hr" } } + } + }, + "department": { + "type": "string", + "description": "Department name" + } + }, + "required": ["name"] +} +``` + +In this example, only users with the `hr` role can read or update the `salary` field. All users with access to the entity can read/update other fields. + +### FLS Notes + +- If no field-level RLS is defined, the field inherits the entity-level RLS rules +- FLS rules follow the same condition format as entity-level RLS +- Use FLS for sensitive fields like salary, SSN, or internal notes + +## Pushing Entities + +The `entities push` command will push all entities that exist in the `base44/entities` folder. + +```bash +npx base44 entities push +``` + +For more details on the push command, see [entities-push.md](entities-push.md). diff --git a/evals/fixtures/sdk-entity-crud/skills/base44-cli/references/entities-push.md b/evals/fixtures/sdk-entity-crud/skills/base44-cli/references/entities-push.md new file mode 100644 index 0000000..63b92d7 --- /dev/null +++ b/evals/fixtures/sdk-entity-crud/skills/base44-cli/references/entities-push.md @@ -0,0 +1,71 @@ +# base44 entities push + +Push local entity definitions to Base44. + +## Syntax + +```bash +npx base44 entities push +``` + +## Authentication + +**Required**: Yes. If not authenticated, you'll be prompted to login first. + +## What It Does + +1. Pushes all entities that exist in the `base44/entities` folder +2. Validates that entities exist in the folder +3. Displays the count of entities to be pushed +4. Uploads entities to the Base44 backend +5. Reports the results: created, updated, and deleted entities + +## Prerequisites + +- Must be run from a Base44 project directory +- Project must have entity definitions in the `base44/entities` folder + +## Output + +```bash +$ npx base44 entities push + +Found 3 entities to push +Pushing entities to Base44... + +Created: User, Post +Updated: Comment +Deleted: OldEntity + +✓ Entities pushed successfully +``` + +## Entity Synchronization + +The push operation synchronizes your local entity schema with Base44: + +- **Created**: New entities that didn't exist in Base44 +- **Updated**: Existing entities with modified schema or configuration +- **Deleted**: Entities that were removed from your local configuration + +## Error Handling + +If no entities are found in your project: +```bash +$ npx base44 entities push +No entities found in project +``` + +## Use Cases + +- After defining new entities in your project +- When modifying existing entity schemas +- To sync entity changes before deploying +- As part of your development workflow when data models change + +## Notes + +- This command syncs the entity schema/structure, not the actual data +- Changes are applied to your Base44 project immediately +- Make sure to test entity changes in a development environment first +- Entity definitions are located in the `base44/entities/` directory diff --git a/evals/fixtures/sdk-entity-crud/skills/base44-cli/references/functions-create.md b/evals/fixtures/sdk-entity-crud/skills/base44-cli/references/functions-create.md new file mode 100644 index 0000000..c523cdc --- /dev/null +++ b/evals/fixtures/sdk-entity-crud/skills/base44-cli/references/functions-create.md @@ -0,0 +1,229 @@ +# Creating Functions + +Base44 functions are serverless backend functions that run on Deno. They are defined locally in your project and deployed to the Base44 backend. + +## Function Directory + +All function definitions must be placed in the `base44/functions/` folder in your project. Each function lives in its own subdirectory with a configuration file and entry point. + +Example structure: +``` +my-app/ + base44/ + functions/ + process-order/ + function.jsonc + index.ts + send-notification/ + function.jsonc + index.ts +``` + +## How to Create a Function + +1. Create a new directory in `base44/functions/` with your function name (use kebab-case) +2. Create a `function.jsonc` configuration file in the directory +3. Create the entry point file (e.g., `index.ts`) +4. Deploy the function using the CLI + +## Function Configuration + +Each function requires a `function.jsonc` configuration file: + +```jsonc +{ + "name": "my-function", + "entry": "index.ts" +} +``` + +### Configuration Properties + +| Property | Description | Required | +|----------|-------------|----------| +| `name` | Function name (must match `/^[^.]+$/` - no dots allowed) | Yes | +| `entry` | Entry point file path relative to the function directory (min 1 char) | Yes | + +## Entry Point File + +Functions run on Deno and must export using `Deno.serve()`. Use `npm:` prefix for npm packages. + +```typescript +import { createClientFromRequest } from "npm:@base44/sdk"; + +Deno.serve(async (req) => { + // Get authenticated client from request + const base44 = createClientFromRequest(req); + + // Parse input + const { orderId, action } = await req.json(); + + // Your logic here + const order = await base44.entities.Orders.get(orderId); + + // Return response + return Response.json({ + success: true, + order: order + }); +}); +``` + +### Request Object + +The function receives a standard Deno `Request` object: +- `req.json()` - Parse JSON body +- `req.text()` - Get raw text body +- `req.headers` - Access request headers +- `req.method` - HTTP method + +### Response Object + +Return using `Response.json()` for JSON responses: + +```typescript +// Success response +return Response.json({ data: result }); + +// Error response with status code +return Response.json({ error: "Something went wrong" }, { status: 400 }); + +// Not found +return Response.json({ error: "Order not found" }, { status: 404 }); +``` + +## Complete Example + +### Directory Structure +``` +base44/ + functions/ + process-order/ + function.jsonc + index.ts +``` + +### function.jsonc +```jsonc +{ + "name": "process-order", + "entry": "index.ts" +} +``` + +### index.ts +```typescript +import { createClientFromRequest } from "npm:@base44/sdk"; + +Deno.serve(async (req) => { + try { + const base44 = createClientFromRequest(req); + const { orderId } = await req.json(); + + // Validate input + if (!orderId) { + return Response.json( + { error: "Order ID is required" }, + { status: 400 } + ); + } + + // Fetch and process the order + const order = await base44.entities.Orders.get(orderId); + if (!order) { + return Response.json( + { error: "Order not found" }, + { status: 404 } + ); + } + + return Response.json({ + success: true, + orderId: order.id, + processedAt: new Date().toISOString() + }); + + } catch (error) { + return Response.json( + { error: error.message }, + { status: 500 } + ); + } +}); +``` + +## Using Service Role Access + +For admin-level operations, use `asServiceRole`: + +```typescript +import { createClientFromRequest } from "npm:@base44/sdk"; + +Deno.serve(async (req) => { + const base44 = createClientFromRequest(req); + + // Check user is authenticated + const user = await base44.auth.me(); + if (!user) { + return Response.json({ error: "Unauthorized" }, { status: 401 }); + } + + // Use service role for admin operations + const allOrders = await base44.asServiceRole.entities.Orders.list(); + + return Response.json({ orders: allOrders }); +}); +``` + +## Using Secrets + +Access environment variables configured in the app dashboard: + +```typescript +Deno.serve(async (req) => { + // Access environment variables (configured in app settings) + const apiKey = Deno.env.get("STRIPE_API_KEY"); + + const response = await fetch("https://api.stripe.com/v1/charges", { + headers: { + "Authorization": `Bearer ${apiKey}` + } + }); + + return Response.json(await response.json()); +}); +``` + +## Naming Conventions + +- **Directory name**: Use kebab-case (e.g., `process-order`, `send-notification`) +- **Function name**: Match the directory name, must match pattern `/^[^.]+$/` (no dots allowed) + - Valid: `process-order`, `send_notification`, `myFunction` + - Invalid: `process.order`, `send.notification.v2` +- **Entry file**: Typically `index.ts` or `index.js` + +## Deploying Functions + +After creating your function, deploy it to Base44: + +```bash +npx base44 functions deploy +``` + +For more details on deploying, see [functions-deploy.md](functions-deploy.md). + +## Notes + +- Functions run on Deno runtime, not Node.js +- Use `npm:` prefix for npm packages (e.g., `npm:@base44/sdk`) +- Use `createClientFromRequest(req)` to get a client that inherits the caller's auth context +- Configure secrets via app dashboard for API keys +- Make sure to handle errors gracefully and return appropriate HTTP status codes + +## Common Mistakes + +| Wrong | Correct | Why | +|-------|---------|-----| +| `functions/myFunction.js` (single file) | `functions/my-function/index.ts` + `function.jsonc` | Functions require subdirectory with config | +| `import { ... } from "@base44/sdk"` | `import { ... } from "npm:@base44/sdk"` | Deno requires `npm:` prefix for npm packages | +| `MyFunction` or `myFunction` directory | `my-function` directory | Use kebab-case for directory names | diff --git a/evals/fixtures/sdk-entity-crud/skills/base44-cli/references/functions-deploy.md b/evals/fixtures/sdk-entity-crud/skills/base44-cli/references/functions-deploy.md new file mode 100644 index 0000000..f89d796 --- /dev/null +++ b/evals/fixtures/sdk-entity-crud/skills/base44-cli/references/functions-deploy.md @@ -0,0 +1,78 @@ +# base44 functions deploy + +Deploy local function definitions to Base44. + +## Syntax + +```bash +npx base44 functions deploy +``` + +## Authentication + +**Required**: Yes. If not authenticated, you'll be prompted to login first. + +## What It Does + +1. Scans the `base44/functions/` directory for function definitions +2. Validates that functions exist and have valid configurations +3. Displays the count of functions to be deployed +4. Uploads function code and configuration to Base44 +5. Reports the results: deployed and deleted functions + +## Prerequisites + +- Must be run from a Base44 project directory +- Project must have function definitions in the `base44/functions/` folder +- Each function must have a valid `function.jsonc` config file + +## Output + +```bash +$ npx base44 functions deploy + +Found 2 functions to deploy +Deploying functions to Base44... + +Deployed: process-order, send-notification +Deleted: old-function + +✓ Functions deployed successfully +``` + +## Function Synchronization + +The deploy operation synchronizes your local functions with Base44: + +- **Deployed**: Functions that were created or updated +- **Deleted**: Functions that were removed from your local configuration + +## Error Handling + +If no functions are found in your project: +```bash +$ npx base44 functions deploy +No functions found. Create functions in the 'functions' directory. +``` + +If a function has configuration errors: +```bash +$ npx base44 functions deploy +Function deployment errors: +'my-function' function: Entry point cannot be empty +``` + +## Use Cases + +- After creating new functions in your project +- When modifying existing function code or configuration +- To sync function changes before testing +- As part of your development workflow when backend logic changes + +## Notes + +- This command deploys the function code and configuration +- Changes are applied to your Base44 project immediately +- Make sure to test functions in a development environment first +- Function definitions are located in the `base44/functions/` directory +- For how to create functions, see [functions-create.md](functions-create.md) diff --git a/evals/fixtures/sdk-entity-crud/skills/base44-cli/references/link.md b/evals/fixtures/sdk-entity-crud/skills/base44-cli/references/link.md new file mode 100644 index 0000000..0b009ce --- /dev/null +++ b/evals/fixtures/sdk-entity-crud/skills/base44-cli/references/link.md @@ -0,0 +1,81 @@ +# base44 link + +Links an existing local Base44 project to a Base44 app in the cloud. Use this when you have a `base44/config.jsonc` but haven't connected it to a Base44 app yet. + +## Critical: When to Use Link vs Create + +| Scenario | Command | +|----------|---------| +| Starting fresh, no `base44/` folder | `npx base44 create` | +| Have `base44/config.jsonc` but no `.app.jsonc` | `npx base44 link` | +| Project already linked (has `.app.jsonc`) | Already done, use `deploy` | + +## Syntax + +```bash +npx base44 link [options] +``` + +## Options + +| Option | Description | Required | +|--------|-------------|----------| +| `-c, --create` | Create a new project (skip selection prompt) | No | +| `-n, --name ` | Project name (required when `--create` is used) | With `--create` | +| `-d, --description ` | Project description | No | +| `-p, --projectId ` | Project ID to link to an existing project (skip selection prompt) | No | + +## Non-Interactive Mode + +For CI/CD or agent use: + +**Create a new project:** +```bash +npx base44 link --create --name my-app +``` + +**Link to an existing project:** +```bash +npx base44 link --projectId +``` + +WRONG: `npx base44 link --create` (missing --name) +WRONG: `npx base44 link --create --projectId ` (cannot use both) +RIGHT: `npx base44 link --create --name my-app` +RIGHT: `npx base44 link --projectId ` + +## Examples + +```bash +# Interactive mode - prompts for project details +npx base44 link + +# Non-interactive - create and link in one step +npx base44 link --create --name my-app + +# With description +npx base44 link --create --name my-app --description "My awesome app" + +# Link to a specific existing project by ID +npx base44 link --projectId abc123 +``` + +## What It Does + +1. Finds the `base44/config.jsonc` in the current directory (or parent directories) +2. Verifies no `.app.jsonc` exists (project not already linked) +3. Either: + - Creates a new Base44 app in the cloud (with `--create`), OR + - Links to an existing project (with `--projectId` or interactive selection) +4. Writes the app ID to `base44/.app.jsonc` + +## Requirements + +- Must have `base44/config.jsonc` in the project +- Must NOT have `base44/.app.jsonc` (use `deploy` if already linked) +- Must be authenticated (run `npx base44 login` first) + +## Notes + +- After linking, you can deploy resources with `npx base44 deploy` +- The `.app.jsonc` file should be git-ignored (contains your app ID) diff --git a/evals/fixtures/sdk-entity-crud/skills/base44-cli/references/rls-examples.md b/evals/fixtures/sdk-entity-crud/skills/base44-cli/references/rls-examples.md new file mode 100644 index 0000000..7d3ef42 --- /dev/null +++ b/evals/fixtures/sdk-entity-crud/skills/base44-cli/references/rls-examples.md @@ -0,0 +1,463 @@ +# RLS Examples + +Practical Row-Level Security patterns for common application types. + +**Important:** Base44 RLS supports: +- **Logical operators:** `$or`, `$and`, `$nor` for combining conditions +- **Field operators (for `data.*` fields):** `$in`, `$nin`, `$ne`, `$all` +- **user_condition:** Equality only (no operators) + +## Contents +- [Simple Patterns (JSON Schema)](#simple-patterns-json-schema) +- [Using Operators](#using-operators) +- [Field-Level Security Examples](#field-level-security-examples) +- [Complex Patterns (Dashboard UI or Backend)](#complex-patterns-dashboard-ui-or-backend) +- [Best Practices](#best-practices) + +--- + +## Simple Patterns (JSON Schema) + +These patterns work with the JSON schema RLS format. + +### Todo App - Owner-only access + +Users see and manage only their own tasks. + +```jsonc +{ + "name": "Task", + "type": "object", + "properties": { + "title": { "type": "string" }, + "description": { "type": "string" }, + "completed": { "type": "boolean" }, + "priority": { "type": "string", "enum": ["low", "medium", "high"] }, + "due_date": { "type": "string", "format": "date" } + }, + "rls": { + "create": true, + "read": { "created_by": "{{user.email}}" }, + "update": { "created_by": "{{user.email}}" }, + "delete": { "created_by": "{{user.email}}" } + } +} +``` + +### Contact Form - Public create, admin-only read + +Anyone can submit, only admins can view submissions. + +```jsonc +{ + "name": "ContactSubmission", + "type": "object", + "properties": { + "name": { "type": "string" }, + "email": { "type": "string", "format": "email" }, + "message": { "type": "string" } + }, + "rls": { + "create": true, + "read": { "user_condition": { "role": "admin" } }, + "update": { "user_condition": { "role": "admin" } }, + "delete": { "user_condition": { "role": "admin" } } + } +} +``` + +### User Profile - Self-management + +Users can only access their own profile. + +```jsonc +{ + "name": "UserProfile", + "type": "object", + "properties": { + "name": { "type": "string" }, + "avatar_url": { "type": "string" }, + "bio": { "type": "string" }, + "preferences": { "type": "object" } + }, + "rls": { + "create": true, + "read": { "created_by": "{{user.email}}" }, + "update": { "created_by": "{{user.email}}" }, + "delete": { "created_by": "{{user.email}}" } + } +} +``` + +### Department Data - Same department access + +Users can only see records from their department. + +```jsonc +{ + "name": "DepartmentAnnouncement", + "type": "object", + "properties": { + "title": { "type": "string" }, + "content": { "type": "string" }, + "department": { "type": "string" } + }, + "rls": { + "create": { "user_condition": { "role": "manager" } }, + "read": { "data.department": "{{user.data.department}}" }, + "update": { "user_condition": { "role": "manager" } }, + "delete": { "user_condition": { "role": "admin" } } + } +} +``` + +### Subscription - Admin-managed, user-readable via email field + +```jsonc +{ + "name": "Subscription", + "type": "object", + "properties": { + "user_email": { "type": "string" }, + "tier": { "type": "string", "enum": ["free", "basic", "pro", "enterprise"] }, + "credits": { "type": "number" }, + "renewal_date": { "type": "string", "format": "date" } + }, + "rls": { + "create": { "user_condition": { "role": "admin" } }, + "read": { "data.user_email": "{{user.email}}" }, + "update": { "user_condition": { "role": "admin" } }, + "delete": { "user_condition": { "role": "admin" } } + } +} +``` + +**Note:** This pattern only allows users to read their own subscription. Admins need to use the Dashboard UI to configure additional read access for themselves. + +### Private Data - Owner-only + +```jsonc +{ + "name": "PrivateNotes", + "type": "object", + "properties": { + "title": { "type": "string" }, + "content": { "type": "string" }, + "tags": { "type": "array", "items": { "type": "string" } } + }, + "rls": { + "create": true, + "read": { "created_by": "{{user.email}}" }, + "update": { "created_by": "{{user.email}}" }, + "delete": { "created_by": "{{user.email}}" } + } +} +``` + +### Public Read, Authenticated Write + +Anyone can read, only logged-in users can create/edit their own records. + +```jsonc +{ + "name": "BlogPost", + "type": "object", + "properties": { + "title": { "type": "string" }, + "content": { "type": "string" }, + "author_email": { "type": "string" } + }, + "rls": { + "create": true, + "read": true, + "update": { "created_by": "{{user.email}}" }, + "delete": { "created_by": "{{user.email}}" } + } +} +``` + +--- + +## Using Operators + +### Logical Operators + +Combine multiple conditions using `$or`, `$and`, or `$nor`: + +**Owner OR Admin access:** +```jsonc +{ + "name": "Document", + "type": "object", + "properties": { + "title": { "type": "string" }, + "content": { "type": "string" } + }, + "rls": { + "create": true, + "read": { + "$or": [ + { "created_by": "{{user.email}}" }, + { "user_condition": { "role": "admin" } } + ] + }, + "update": { + "$or": [ + { "created_by": "{{user.email}}" }, + { "user_condition": { "role": "admin" } } + ] + }, + "delete": { "user_condition": { "role": "admin" } } + } +} +``` + +**Multiple roles with $or:** +```jsonc +{ + "rls": { + "read": { + "$or": [ + { "user_condition": { "role": "admin" } }, + { "user_condition": { "role": "manager" } }, + { "user_condition": { "role": "hr" } } + ] + } + } +} +``` + +### Field Operators for data.* Fields + +Use `$in`, `$nin`, `$ne`, `$all` for comparing entity data fields: + +**Access based on tags ($in):** +```jsonc +{ + "rls": { + "read": { + "data.category": { "$in": ["public", "shared"] } + } + } +} +``` + +**Exclude specific statuses ($nin):** +```jsonc +{ + "rls": { + "read": { + "data.status": { "$nin": ["archived", "deleted"] } + } + } +} +``` + +**Not equal ($ne):** +```jsonc +{ + "rls": { + "read": { + "data.visibility": { "$ne": "private" } + } + } +} +``` + +**All tags must match ($all):** +```jsonc +{ + "rls": { + "read": { + "data.required_tags": { "$all": ["approved", "reviewed"] } + } + } +} +``` + +### Combining Logical and Field Operators + +```jsonc +{ + "rls": { + "read": { + "$and": [ + { "data.status": { "$ne": "draft" } }, + { + "$or": [ + { "created_by": "{{user.email}}" }, + { "data.visibility": "public" } + ] + } + ] + } + } +} +``` + +--- + +## Field-Level Security Examples + +Control access to specific fields within an entity. + +### Sensitive Salary Field + +```jsonc +{ + "name": "Employee", + "type": "object", + "properties": { + "name": { "type": "string" }, + "email": { "type": "string", "format": "email" }, + "salary": { + "type": "number", + "description": "Annual salary", + "rls": { + "read": { "user_condition": { "role": "hr" } }, + "write": { "user_condition": { "role": "hr" } } + } + }, + "performance_notes": { + "type": "string", + "description": "Manager notes", + "rls": { + "read": { + "$or": [ + { "user_condition": { "role": "manager" } }, + { "user_condition": { "role": "hr" } } + ] + }, + "write": { "user_condition": { "role": "manager" } } + } + } + } +} +``` + +### Admin-Only Internal Fields + +```jsonc +{ + "name": "Order", + "type": "object", + "properties": { + "order_number": { "type": "string" }, + "total": { "type": "number" }, + "internal_notes": { + "type": "string", + "description": "Internal processing notes", + "rls": { + "read": { "user_condition": { "role": "admin" } }, + "write": { "user_condition": { "role": "admin" } } + } + }, + "profit_margin": { + "type": "number", + "description": "Profit margin percentage", + "rls": { + "read": { "user_condition": { "role": "admin" } }, + "write": false + } + } + } +} +``` + +--- + +## Complex Patterns (Dashboard UI or Backend) + +Some patterns may still require the Dashboard UI or backend functions. + +### Bidirectional Relationships (e.g., Friendships, Matches) + +**Requirement:** Either party in a relationship should have access. + +**Now possible with $or:** +```jsonc +{ + "rls": { + "read": { + "$or": [ + { "data.user_a_email": "{{user.email}}" }, + { "data.user_b_email": "{{user.email}}" } + ] + } + } +} +``` + +**Alternative solutions:** +1. **Entity redesign:** Store two records per relationship (one for each party) +2. **Backend function:** Query with custom logic + +### Complex Business Logic + +**Requirement:** Access depends on multiple entity fields with complex conditions. + +**JSON Schema limitation:** While operators help, very complex business logic may still be hard to express. + +**Solution options:** +1. **Backend function:** Implement custom access logic +2. **Combine simpler rules:** Break complex rules into simpler entity-level and field-level rules + +--- + +## Best Practices + +### Security Strategy + +Use a combination of entity-level RLS and field-level security: + +| Data Type | Approach | Example | +|-----------|----------|---------| +| User-editable | Entity RLS: Owner-only | UserProfile with `created_by` check | +| Sensitive fields | Field-level RLS | Salary field with HR role check | +| Multi-role access | `$or` with user_condition | Admin OR Manager access | +| Conditional access | Field operators | `$in`, `$ne` on data fields | +| Public content | Entity RLS: `read: true` | PublicPost | +| Private content | Entity RLS: Owner-only | PrivateNote | + +### When to Use Each Approach + +| Requirement | Approach | +|-------------|----------| +| Single condition (owner, admin, department) | JSON Schema RLS | +| Multiple OR/AND conditions | JSON Schema RLS with `$or`/`$and` | +| Field value checks with `$in`/`$ne`/etc. | JSON Schema RLS for `data.*` fields | +| Field-level access control | JSON Schema FLS (field-level `rls`) | +| Complex comparison operators (`$gt`, `$lt`) | Backend functions | +| Very complex business logic | Backend functions | + +### Common Role Patterns + +| Role | Typical Access | +|------|----------------| +| `admin` | Full access to all records | +| `moderator` | Read/update access, limited delete | +| `manager` | Department-scoped access | +| `user` | Own records only | + +### Supported Operators Summary + +| Operator | Supported | Notes | +|----------|-----------|-------| +| `$or` | Yes | Combine multiple conditions | +| `$and` | Yes | All conditions must match | +| `$nor` | Yes | None of the conditions match | +| `$in` | Yes | For `data.*` fields only | +| `$nin` | Yes | For `data.*` fields only | +| `$ne` | Yes | For `data.*` fields only | +| `$all` | Yes | For `data.*` fields only | +| `$gt`, `$lt`, `$gte`, `$lte` | No | Use backend functions | +| `$regex` | No | Use backend functions | + +### Limitations Summary + +| Not Supported | Alternative | +|---------------|-------------| +| Operators on `user_condition` | Use equality only for user checks | +| Comparison operators (`$gt`, `$lt`) | Backend functions | +| Regex matching (`$regex`) | Backend functions | +| Cross-entity relationships | Backend functions | diff --git a/evals/fixtures/sdk-entity-crud/skills/base44-cli/references/site-deploy.md b/evals/fixtures/sdk-entity-crud/skills/base44-cli/references/site-deploy.md new file mode 100644 index 0000000..929d302 --- /dev/null +++ b/evals/fixtures/sdk-entity-crud/skills/base44-cli/references/site-deploy.md @@ -0,0 +1,118 @@ +# base44 site deploy + +Deploy built site files to Base44 hosting. + +## Table of Contents + +- [Syntax](#syntax) +- [Authentication](#authentication) +- [Prerequisites](#prerequisites) +- [How It Works](#how-it-works) +- [Interactive Flow](#interactive-flow) +- [Typical Workflow](#typical-workflow) +- [Configuration](#configuration) +- [Error Handling](#error-handling) +- [Use Cases](#use-cases) +- [Notes](#notes) + +## Syntax + +```bash +npx base44 site deploy [options] +``` + +## Options + +| Option | Description | +| ------------ | ------------------------- | +| `-y, --yes` | Skip confirmation prompt | + +Use `-y` flag for non-interactive/automated deployments: + +```bash +npx base44 site deploy -y +``` + +## Authentication + +**Required**: Yes. If not authenticated, you'll be prompted to login first. + +## Prerequisites + +- Must be run from a Base44 project directory +- Project must have `site.outputDirectory` configured in project config +- Site must be built before deploying (run your build command first) +- **SPA only**: Base44 hosting supports Single Page Applications with a single `index.html` entry point. All routes are served from `index.html` (client-side routing). + +## How It Works + +1. Reads project configuration +2. Validates that site configuration exists +3. Prompts for deployment confirmation showing the output directory +4. Creates an archive of site files from the output directory +5. Deploys to Base44 hosting +6. Returns the app URL + +## Interactive Flow + +```bash +$ npx base44 site deploy + +Deploy site from ./dist? (yes/no) yes + +Creating archive... +Uploading to Base44... +Deploying... + +✓ Deployment successful! + +Visit your site at: https://my-app.base44.app +``` + +## Typical Workflow + +```bash +# 1. Build your site using your framework's build command +npm run build + +# 2. Deploy to Base44 +npx base44 site deploy +``` + +## Configuration + +The `site.outputDirectory` in your project configuration should point to where your framework outputs built files: + +- Vite: typically `./dist` +- Next.js: typically `./.next` or `./out` +- Create React App: typically `./build` +- Custom: whatever your build tool outputs to + +## Error Handling + +If site configuration is missing: +```bash +$ npx base44 site deploy +Error: No site configuration found in project +``` + +If you cancel the deployment: +```bash +Deploy site from ./dist? (yes/no) no +Deployment cancelled +``` + +## Use Cases + +- Deploy your site after making changes +- Push new versions of your application +- Deploy after updating content or functionality +- Part of your CI/CD pipeline + +## Notes + +- Always build your site before deploying +- The command deploys whatever is in your output directory +- Make sure your build completed successfully before deploying +- Previous deployments are preserved (versioned) in Base44 +- Deployment is immediate and updates your live site diff --git a/evals/fixtures/sdk-entity-crud/skills/base44-sdk/SKILL.md b/evals/fixtures/sdk-entity-crud/skills/base44-sdk/SKILL.md new file mode 100644 index 0000000..01ddd7f --- /dev/null +++ b/evals/fixtures/sdk-entity-crud/skills/base44-sdk/SKILL.md @@ -0,0 +1,296 @@ +--- +name: base44-sdk +description: "The base44 SDK is the library to communicate with base44 services. In projects, you use it to communicate with remote resources (entities, backend functions, ai agents) and to write backend functions. This skill is the place for learning about available modules and types. When you plan or implement a feature, you must learn this skill" +--- + +# Base44 Coder + +Build apps on the Base44 platform using the Base44 JavaScript SDK. + +## ⚡ IMMEDIATE ACTION REQUIRED - Read This First + +This skill activates on ANY mention of "base44" or when a `base44/` folder exists. **DO NOT read documentation files or search the web before acting.** + +**Your first action MUST be:** +1. Check if `base44/config.jsonc` exists in the current directory +2. If **YES** (existing project scenario): + - This skill (base44-sdk) handles the request + - Implement features using Base44 SDK + - Do NOT use base44-cli unless user explicitly requests CLI commands +3. If **NO** (new project scenario): + - Transfer to base44-cli skill for project initialization + - This skill cannot help until project is initialized + +## When to Use This Skill vs base44-cli + +**Use base44-sdk when:** +- Building features in an **EXISTING** Base44 project +- `base44/config.jsonc` already exists in the project +- Base44 SDK imports are present (`@base44/sdk`) +- Writing JavaScript/TypeScript code using Base44 SDK modules +- Implementing functionality, components, or features +- User mentions: "implement", "build a feature", "add functionality", "write code for" +- User says "create a [type] app" **and** a Base44 project already exists + +**DO NOT USE base44-sdk for:** +- ❌ Initializing new Base44 projects (use `base44-cli` instead) +- ❌ Empty directories without Base44 configuration +- ❌ When user says "create a new Base44 project/app/site" and no project exists +- ❌ CLI commands like `npx base44 create`, `npx base44 deploy`, `npx base44 login` (use `base44-cli`) + +**Skill Dependencies:** +- `base44-sdk` assumes a Base44 project is **already initialized** +- `base44-cli` is a **prerequisite** for `base44-sdk` in new projects +- If user wants to "create an app" and no Base44 project exists, use `base44-cli` first + +**State Check Logic:** +Before selecting this skill, verify: +- IF (user mentions "create/build app" OR "make a project"): + - IF (directory is empty OR no `base44/config.jsonc` exists): + → Use **base44-cli** (project initialization needed) + - ELSE: + → Use **base44-sdk** (project exists, build features) + +## Quick Start + +```javascript +// In Base44-generated apps, base44 client is pre-configured and available + +// CRUD operations +const task = await base44.entities.Task.create({ title: "New task", status: "pending" }); +const tasks = await base44.entities.Task.list(); +await base44.entities.Task.update(task.id, { status: "done" }); + +// Get current user +const user = await base44.auth.me(); +``` + +```javascript +// External apps +import { createClient } from "@base44/sdk"; + +// IMPORTANT: Use 'appId' (NOT 'clientId' or 'id') +const base44 = createClient({ appId: "your-app-id" }); +await base44.auth.loginViaEmailPassword("user@example.com", "password"); +``` + +## ⚠️ CRITICAL: Do Not Hallucinate APIs + +**Before writing ANY Base44 code, verify method names against this table or [QUICK_REFERENCE.md](references/QUICK_REFERENCE.md).** + +Base44 SDK has unique method names. Do NOT assume patterns from Firebase, Supabase, or other SDKs. + +### Authentication - WRONG vs CORRECT + +| ❌ WRONG (hallucinated) | ✅ CORRECT | +|------------------------|-----------| +| `signInWithGoogle()` | `loginWithProvider('google')` | +| `signInWithProvider('google')` | `loginWithProvider('google')` | +| `auth.google()` | `loginWithProvider('google')` | +| `signInWithEmailAndPassword(email, pw)` | `loginViaEmailPassword(email, pw)` | +| `signIn(email, pw)` | `loginViaEmailPassword(email, pw)` | +| `createUser()` / `signUp()` | `register({email, password})` | +| `onAuthStateChanged()` | `me()` (no listener, call when needed) | +| `currentUser` | `await auth.me()` | + +### Functions - WRONG vs CORRECT + +| ❌ WRONG (hallucinated) | ✅ CORRECT | +|------------------------|-----------| +| `functions.call('name', data)` | `functions.invoke('name', data)` | +| `functions.run('name', data)` | `functions.invoke('name', data)` | +| `callFunction('name', data)` | `functions.invoke('name', data)` | +| `httpsCallable('name')(data)` | `functions.invoke('name', data)` | + +### Integrations - WRONG vs CORRECT + +| ❌ WRONG (hallucinated) | ✅ CORRECT | +|------------------------|-----------| +| `ai.generate(prompt)` | `integrations.Core.InvokeLLM({prompt})` | +| `openai.chat(prompt)` | `integrations.Core.InvokeLLM({prompt})` | +| `llm(prompt)` | `integrations.Core.InvokeLLM({prompt})` | +| `sendEmail(to, subject, body)` | `integrations.Core.SendEmail({to, subject, body})` | +| `email.send()` | `integrations.Core.SendEmail({to, subject, body})` | +| `uploadFile(file)` | `integrations.Core.UploadFile({file})` | +| `storage.upload(file)` | `integrations.Core.UploadFile({file})` | + +### Entities - WRONG vs CORRECT + +| ❌ WRONG (hallucinated) | ✅ CORRECT | +|------------------------|-----------| +| `entities.Task.find({...})` | `entities.Task.filter({...})` | +| `entities.Task.findOne(id)` | `entities.Task.get(id)` | +| `entities.Task.insert(data)` | `entities.Task.create(data)` | +| `entities.Task.remove(id)` | `entities.Task.delete(id)` | +| `entities.Task.onChange(cb)` | `entities.Task.subscribe(cb)` | + +## SDK Modules + +| Module | Purpose | Reference | +|--------|---------|-----------| +| `entities` | CRUD operations on data models | [entities.md](references/entities.md) | +| `auth` | Login, register, user management | [auth.md](references/auth.md) | +| `agents` | AI conversations and messages | [base44-agents.md](references/base44-agents.md) | +| `functions` | Backend function invocation | [functions.md](references/functions.md) | +| `integrations` | AI, email, file uploads, custom APIs | [integrations.md](references/integrations.md) | +| `connectors` | OAuth tokens (service role only) | [connectors.md](references/connectors.md) | +| `analytics` | Track custom events and user activity | [analytics.md](references/analytics.md) | +| `appLogs` | Log user activity in app | [app-logs.md](references/app-logs.md) | +| `users` | Invite users to the app | [users.md](references/users.md) | + +For client setup and authentication modes, see [client.md](references/client.md). + +**TypeScript Support:** Each reference file includes a "Type Definitions" section with TypeScript interfaces and types for the module's methods, parameters, and return values. + +## Installation + +Install the Base44 SDK: + +```bash +npm install @base44/sdk +``` + +**Important:** Never assume or hardcode the `@base44/sdk` package version. Always install without a version specifier to get the latest version. + +## Creating a Client (External Apps) + +When creating a client in external apps, **ALWAYS use `appId` as the parameter name**: + +```javascript +import { createClient } from "@base44/sdk"; + +// ✅ CORRECT +const base44 = createClient({ appId: "your-app-id" }); + +// ❌ WRONG - Do NOT use these: +// const base44 = createClient({ clientId: "your-app-id" }); // WRONG +// const base44 = createClient({ id: "your-app-id" }); // WRONG +``` + +**Required parameter:** `appId` (string) - Your Base44 application ID + +**Optional parameters:** +- `token` (string) - Pre-authenticated user token +- `options` (object) - Configuration options + - `options.onError` (function) - Global error handler + +**Example with error handler:** +```javascript +const base44 = createClient({ + appId: "your-app-id", + options: { + onError: (error) => { + console.error("Base44 error:", error); + } + } +}); +``` + +## Module Selection + +**Working with app data?** +- Create/read/update/delete records → `entities` +- Import data from file → `entities.importEntities()` +- Realtime updates → `entities.EntityName.subscribe()` + +**User management?** +- Login/register/logout → `auth` +- Get current user → `auth.me()` +- Update user profile → `auth.updateMe()` +- Invite users → `users.inviteUser()` + +**AI features?** +- Chat with AI agents → `agents` (requires logged-in user) +- Create new conversation → `agents.createConversation()` +- Manage conversations → `agents.getConversations()` +- Generate text/JSON with AI → `integrations.Core.InvokeLLM()` +- Generate images → `integrations.Core.GenerateImage()` + +**Custom backend logic?** +- Run server-side code → `functions.invoke()` +- Need admin access → `base44.asServiceRole.functions.invoke()` + +**External services?** +- Send emails → `integrations.Core.SendEmail()` +- Upload files → `integrations.Core.UploadFile()` +- Custom APIs → `integrations.custom.call()` +- OAuth tokens (Google, Slack) → `connectors` (backend only) + +**Tracking and analytics?** +- Track custom events → `analytics.track()` +- Log page views/activity → `appLogs.logUserInApp()` + +## Common Patterns + +### Filter and Sort Data + +```javascript +const pendingTasks = await base44.entities.Task.filter( + { status: "pending", assignedTo: userId }, // query + "-created_date", // sort (descending) + 10, // limit + 0 // skip +); +``` + +### Protected Routes (check auth) + +```javascript +const user = await base44.auth.me(); +if (!user) { + // Navigate to your custom login page + navigate('/login', { state: { returnTo: window.location.pathname } }); + return; +} +``` + +### Backend Function Call + +```javascript +// Frontend +const result = await base44.functions.invoke("processOrder", { + orderId: "123", + action: "ship" +}); + +// Backend function (Deno) +import { createClientFromRequest } from "npm:@base44/sdk"; + +Deno.serve(async (req) => { + const base44 = createClientFromRequest(req); + const { orderId, action } = await req.json(); + // Process with service role for admin access + const order = await base44.asServiceRole.entities.Orders.get(orderId); + return Response.json({ success: true }); +}); +``` + +### Service Role Access + +Use `asServiceRole` in backend functions for admin-level operations: + +```javascript +// User mode - respects permissions +const myTasks = await base44.entities.Task.list(); + +// Service role - full access (backend only) +const allTasks = await base44.asServiceRole.entities.Task.list(); +const token = await base44.asServiceRole.connectors.getAccessToken("slack"); +``` + +## Frontend vs Backend + +| Capability | Frontend | Backend | +|------------|----------|---------| +| `entities` (user's data) | Yes | Yes | +| `auth` | Yes | Yes | +| `agents` | Yes | Yes | +| `functions.invoke()` | Yes | Yes | +| `integrations` | Yes | Yes | +| `analytics` | Yes | Yes | +| `appLogs` | Yes | Yes | +| `users` | Yes | Yes | +| `asServiceRole.*` | No | Yes | +| `connectors` | No | Yes | + +Backend functions use `Deno.serve()` and `createClientFromRequest(req)` to get a properly authenticated client. diff --git a/evals/fixtures/sdk-entity-crud/skills/base44-sdk/references/QUICK_REFERENCE.md b/evals/fixtures/sdk-entity-crud/skills/base44-sdk/references/QUICK_REFERENCE.md new file mode 100644 index 0000000..d357384 --- /dev/null +++ b/evals/fixtures/sdk-entity-crud/skills/base44-sdk/references/QUICK_REFERENCE.md @@ -0,0 +1,155 @@ +# Base44 SDK Quick Reference + +Compact method signatures for all SDK modules. **Verify against this before writing code.** + +--- + +## Auth (`base44.auth.*`) + +``` +loginViaEmailPassword(email, password, turnstileToken?) → Promise<{access_token, user}> +loginWithProvider('google' | 'microsoft' | 'facebook', fromUrl?) → void +me() → Promise +updateMe(data) → Promise +isAuthenticated() → Promise +logout(redirectUrl?) → void +redirectToLogin(nextUrl) → void # ⚠️ Avoid - prefer custom login UI +register({email, password, turnstile_token?, referral_code?}) → Promise +verifyOtp({email, otpCode}) → Promise +resendOtp(email) → Promise +inviteUser(userEmail, role) → Promise +resetPasswordRequest(email) → Promise +resetPassword({resetToken, newPassword}) → Promise +changePassword({userId, currentPassword, newPassword}) → Promise +setToken(token, saveToStorage?) → void +``` + +--- + +## Entities (`base44.entities.EntityName.*`) + +``` +create(data) → Promise +bulkCreate(dataArray) → Promise +list(sort?, limit?, skip?, fields?) → Promise +filter(query, sort?, limit?, skip?, fields?) → Promise +get(id) → Promise +update(id, data) → Promise +delete(id) → Promise +deleteMany(query) → Promise +importEntities(file) → Promise // frontend only +subscribe(callback) → () => void // returns unsubscribe fn +``` + +**Sort:** Use `-fieldName` for descending (e.g., `-created_date`) + +--- + +## Functions (`base44.functions.*`) + +``` +invoke(functionName, data) → Promise +``` + +**Backend:** Use `base44.asServiceRole.functions.invoke()` for admin access. + +--- + +## Integrations (`base44.integrations.Core.*`) + +``` +InvokeLLM({prompt, add_context_from_internet?, response_json_schema?, file_urls?}) → Promise +GenerateImage({prompt}) → Promise<{url}> +SendEmail({to, subject, body, from_name?}) → Promise +UploadFile({file}) → Promise<{file_url}> +UploadPrivateFile({file}) → Promise<{file_uri}> +CreateFileSignedUrl({file_uri, expires_in?}) → Promise<{signed_url}> +ExtractDataFromUploadedFile({file_url, json_schema}) → Promise +``` + +### Custom Integrations (`base44.integrations.custom.*`) + +``` +call(slug, operationId, {payload?, pathParams?, queryParams?, headers?}?) → Promise<{success, status_code, data}> +``` + +**operationId format:** `"method:/path"` (e.g., `"get:/contacts"`, `"post:/users/{id}"`) + +--- + +## Analytics (`base44.analytics.*`) + +``` +track({eventName, properties?}) → void +``` + +--- + +## App Logs (`base44.appLogs.*`) + +``` +logUserInApp(pageName) → Promise +``` + +--- + +## Users (`base44.users.*`) + +``` +inviteUser(userEmail, role) → Promise // role: 'user' | 'admin' +``` + +--- + +## Connectors (`base44.asServiceRole.connectors.*`) + +**Backend only, service role required.** + +``` +getAccessToken(integrationType) → Promise +``` + +**Types:** `'googlecalendar'`, `'googledrive'`, `'slack'`, `'notion'`, `'salesforce'`, `'hubspot'`, `'linkedin'`, `'tiktok'`, `'github'` + +--- + +## Service Role Access + +**Backend functions only.** Prefix any module with `asServiceRole` for admin access: + +```javascript +base44.asServiceRole.entities.Task.list() +base44.asServiceRole.functions.invoke('name', data) +base44.asServiceRole.connectors.getAccessToken('slack') +``` + +--- + +## Backend Function Template + +```javascript +import { createClientFromRequest } from "@base44/sdk"; + +Deno.serve(async (req) => { + const base44 = createClientFromRequest(req); + const data = await req.json(); + + // User context + const user = await base44.auth.me(); + + // Service role for admin operations + const allRecords = await base44.asServiceRole.entities.Task.list(); + + return Response.json({ success: true }); +}); +``` + +--- + +## Client Initialization (External Apps) + +```javascript +import { createClient } from "@base44/sdk"; + +const base44 = createClient({ appId: "your-app-id" }); // MUST use 'appId' +``` diff --git a/evals/fixtures/sdk-entity-crud/skills/base44-sdk/references/analytics.md b/evals/fixtures/sdk-entity-crud/skills/base44-sdk/references/analytics.md new file mode 100644 index 0000000..d1b2c69 --- /dev/null +++ b/evals/fixtures/sdk-entity-crud/skills/base44-sdk/references/analytics.md @@ -0,0 +1,113 @@ +# Analytics Module + +Track custom events and user activity via `base44.analytics`. + +## Contents +- [Methods](#methods) +- [Examples](#examples) +- [Automatic Tracking](#automatic-tracking) +- [Best Practices](#best-practices) + +## Methods + +| Method | Signature | Description | +|--------|-----------|-------------| +| `track(params)` | `void` | Track a custom event | + +## Examples + +### Track Custom Event + +```javascript +// Track a simple event +base44.analytics.track({ + eventName: "button_clicked" +}); + +// Track event with properties +base44.analytics.track({ + eventName: "purchase_completed", + properties: { + product_id: "prod-123", + amount: 99.99, + currency: "USD" + } +}); +``` + +### Track User Actions + +```javascript +// Track page view +base44.analytics.track({ + eventName: "page_view", + properties: { + page: "/dashboard", + referrer: document.referrer + } +}); + +// Track feature usage +base44.analytics.track({ + eventName: "feature_used", + properties: { + feature: "export_data", + format: "csv" + } +}); +``` + +## Automatic Tracking + +The analytics module automatically tracks: +- **Initialization events**: When the app loads +- **Heartbeat events**: Periodic activity signals +- **Session duration**: Time spent in the app + +These internal events help measure user engagement without manual instrumentation. + +## Best Practices + +1. **Use descriptive event names**: `order_completed` instead of `click` +2. **Include relevant properties**: Add context that helps analyze the event +3. **Be consistent**: Use the same event names and property keys across your app +4. **Don't track sensitive data**: Avoid PII in event properties + +```javascript +// Good: Descriptive with relevant properties +base44.analytics.track({ + eventName: "subscription_started", + properties: { + plan: "pro", + billing_cycle: "annual" + } +}); + +// Avoid: Vague event name, no context +base44.analytics.track({ + eventName: "click" +}); +``` + +## Type Definitions + +```typescript +/** Properties that can be attached to a tracked event. */ +type TrackEventProperties = { + [key: string]: string | number | boolean | null | undefined; +}; + +/** Parameters for the track() method. */ +interface TrackEventParams { + /** The name of the event to track. */ + eventName: string; + /** Optional properties to attach to the event. */ + properties?: TrackEventProperties; +} + +/** The analytics module interface. */ +interface AnalyticsModule { + /** Track a custom event with optional properties. */ + track(params: TrackEventParams): void; +} +``` diff --git a/evals/fixtures/sdk-entity-crud/skills/base44-sdk/references/app-logs.md b/evals/fixtures/sdk-entity-crud/skills/base44-sdk/references/app-logs.md new file mode 100644 index 0000000..0439805 --- /dev/null +++ b/evals/fixtures/sdk-entity-crud/skills/base44-sdk/references/app-logs.md @@ -0,0 +1,78 @@ +# App Logs Module + +Log user activity in your app via `base44.appLogs`. + +## Contents +- [Methods](#methods) +- [Examples](#examples) +- [Use Cases](#use-cases) + +## Methods + +| Method | Signature | Description | +|--------|-----------|-------------| +| `logUserInApp(pageName)` | `Promise` | Log user activity on a page | + +## Examples + +### Log User Activity + +```javascript +// Log when user visits a page +await base44.appLogs.logUserInApp("dashboard"); + +// Log specific page visits +await base44.appLogs.logUserInApp("settings"); +await base44.appLogs.logUserInApp("profile"); + +// Log feature usage +await base44.appLogs.logUserInApp("export-button-click"); +``` + +The page name doesn't have to be an actual page - it can be any string you want to track. + +## Use Cases + +### Track Page Views in React + +```javascript +// Log page views on route change +useEffect(() => { + base44.appLogs.logUserInApp(window.location.pathname); +}, [location.pathname]); +``` + +### Track Feature Usage + +```javascript +// Log when user uses specific features +function handleExport() { + base44.appLogs.logUserInApp("export-data"); + // ... export logic +} + +function handleSettingsChange() { + base44.appLogs.logUserInApp("settings-updated"); + // ... save settings +} +``` + +## Notes + +- Logs appear in the Analytics page of your app dashboard +- App logs track page-level and feature-level activity +- Use `analytics.track()` for custom events with properties, `appLogs.logUserInApp()` for simple page/feature tracking + +## Type Definitions + +```typescript +/** App Logs module for tracking and analyzing app usage. */ +interface AppLogsModule { + /** + * Log user activity in the app. + * @param pageName - Name of the page or section being visited. + * @returns Promise that resolves when the log is recorded. + */ + logUserInApp(pageName: string): Promise; +} +``` diff --git a/evals/fixtures/sdk-entity-crud/skills/base44-sdk/references/auth.md b/evals/fixtures/sdk-entity-crud/skills/base44-sdk/references/auth.md new file mode 100644 index 0000000..5195b3a --- /dev/null +++ b/evals/fixtures/sdk-entity-crud/skills/base44-sdk/references/auth.md @@ -0,0 +1,761 @@ +# Auth Module + +User authentication, registration, and session management via `base44.auth`. + +## Contents +- [TypeScript Types](#typescript-types) +- [Methods](#methods) +- [Examples](#examples) +- [Error Handling](#error-handling) +- [Auth Providers](#auth-providers) +- [Environment Availability](#environment-availability) +- [App Visibility](#app-visibility) +- [Limitations](#limitations) + +--- + +## TypeScript Types + +### User Interface +```typescript +interface User { + id: string; + created_date: string; + updated_date: string; + email: string; + full_name: string | null; + disabled: boolean | null; + is_verified: boolean; + app_id: string; + is_service: boolean; + role: string; + [key: string]: any; // Custom schema fields +} +``` + +### LoginResponse Interface +```typescript +interface LoginResponse { + access_token: string; // JWT token + user: User; // Complete user object +} +``` + +### Parameter Interfaces + +#### RegisterParams +```typescript +interface RegisterParams { + email: string; // Required + password: string; // Required + turnstile_token?: string | null; // Optional: Cloudflare Turnstile for bot protection + referral_code?: string | null; // Optional: Referral code +} +``` + +#### VerifyOtpParams +```typescript +interface VerifyOtpParams { + email: string; // User's email + otpCode: string; // OTP code from email +} +``` + +#### ResetPasswordParams +```typescript +interface ResetPasswordParams { + resetToken: string; // Token from password reset email + newPassword: string; // New password to set +} +``` + +#### ChangePasswordParams +```typescript +interface ChangePasswordParams { + userId: string; // User ID + currentPassword: string; // Current password for verification + newPassword: string; // New password to set +} +``` + +### Provider Type +```typescript +type Provider = 'google' | 'microsoft' | 'facebook'; +``` + +--- + +## Methods + +### Module Interface +```typescript +interface AuthModule { + // User Info + me(): Promise; + updateMe(data: Partial>): Promise; + isAuthenticated(): Promise; + + // Login/Logout + loginViaEmailPassword(email: string, password: string, turnstileToken?: string): Promise; + loginWithProvider(provider: Provider, fromUrl?: string): void; + logout(redirectUrl?: string): void; + redirectToLogin(nextUrl: string): void; + + // Token Management + setToken(token: string, saveToStorage?: boolean): void; + + // Registration + register(params: RegisterParams): Promise; + verifyOtp(params: VerifyOtpParams): Promise; + resendOtp(email: string): Promise; + + // User Management + inviteUser(userEmail: string, role: string): Promise; + + // Password Management + resetPasswordRequest(email: string): Promise; + resetPassword(params: ResetPasswordParams): Promise; + changePassword(params: ChangePasswordParams): Promise; +} +``` + +### Method Reference Table + +| Method | Parameters | Return Type | Description | +|--------|-----------|-------------|-------------| +| `register()` | `params: RegisterParams` | `Promise` | Create new user account | +| `loginViaEmailPassword()` | `email: string, password: string, turnstileToken?: string` | `Promise` | Authenticate with email/password | +| `loginWithProvider()` | `provider: Provider, fromUrl?: string` | `void` | Initiate OAuth login flow | +| `me()` | None | `Promise` | Get current authenticated user | +| `updateMe()` | `data: Partial` | `Promise` | Update current user's profile | +| `logout()` | `redirectUrl?: string` | `void` | Clear session, optionally redirect | +| `redirectToLogin()` | `nextUrl: string` | `void` | ⚠️ **Avoid** - Prefer custom login UI with `loginViaEmailPassword()` or `loginWithProvider()` | +| `isAuthenticated()` | None | `Promise` | Check if user is logged in | +| `setToken()` | `token: string, saveToStorage?: boolean` | `void` | Manually set auth token | +| `inviteUser()` | `userEmail: string, role: string` | `Promise` | Send invitation email | +| `verifyOtp()` | `params: VerifyOtpParams` | `Promise` | Verify OTP code | +| `resendOtp()` | `email: string` | `Promise` | Resend OTP code | +| `resetPasswordRequest()` | `email: string` | `Promise` | Request password reset | +| `resetPassword()` | `params: ResetPasswordParams` | `Promise` | Reset password with token | +| `changePassword()` | `params: ChangePasswordParams` | `Promise` | Change user password | + +--- + +## Examples + +### Register New User (Complete Flow) + +Registration requires email verification before login. Complete flow: + +1. **Register** - Create the user account +2. **Verification email sent** - User receives an OTP code +3. **Verify OTP** - User enters code to verify email +4. **Login** - User can now log in + +```javascript +try { + // Step 1: Register the user + await base44.auth.register({ + email: "user@example.com", + password: "securePassword123", + referral_code: "OPTIONAL_CODE", // optional + turnstile_token: "CAPTCHA_TOKEN" // optional, for bot protection + }); + console.log('Registration successful. Check email for OTP code.'); + + // Step 2: User receives email with OTP code (e.g., "123456") + + // Step 3: Verify the OTP code + await base44.auth.verifyOtp({ + email: "user@example.com", + otpCode: "123456" // code from verification email + }); + console.log('Email verified successfully.'); + + // Step 4: Now the user can log in + const loginResponse = await base44.auth.loginViaEmailPassword( + "user@example.com", + "securePassword123" + ); + console.log('Login successful:', loginResponse.user); + +} catch (error) { + console.error('Registration flow failed:', error.message); + // Handle specific errors (see Error Handling section) +} +``` + +> **Important**: Users cannot log in until they complete OTP verification. Attempting to call `loginViaEmailPassword` before verification will fail. + +### Login with Email/Password + +```javascript +try { + const response = await base44.auth.loginViaEmailPassword( + "user@example.com", + "password123", + turnstileToken // optional: for bot protection + ); + + console.log('Login successful'); + console.log('User:', response.user); + console.log('Token:', response.access_token); + + // JWT is automatically stored for subsequent requests + +} catch (error) { + console.error('Login failed:', error.message); + if (error.status === 401) { + console.error('Invalid credentials'); + } else if (error.status === 403) { + console.error('Email not verified. Please check your email for OTP.'); + } +} +``` + +### Login with OAuth Provider + +```javascript +// Redirect to Google OAuth +base44.auth.loginWithProvider('google'); + +// Redirect to Google OAuth and return to current page after +base44.auth.loginWithProvider('google', window.location.href); + +// Supported providers: 'google', 'microsoft', 'facebook' +base44.auth.loginWithProvider('microsoft'); +``` + +### Get Current User + +```javascript +try { + const user = await base44.auth.me(); + + if (user) { + console.log('User ID:', user.id); + console.log('Email:', user.email); + console.log('Name:', user.full_name); + console.log('Role:', user.role); + console.log('Verified:', user.is_verified); + } else { + console.log('User not authenticated'); + } + +} catch (error) { + console.error('Failed to fetch user:', error.message); + if (error.status === 401) { + // Token expired or invalid - navigate to your custom login page + navigate('/login'); + } +} +``` + +### Update User Profile + +```javascript +try { + const updatedUser = await base44.auth.updateMe({ + full_name: "John Doe", + // Custom schema fields can be updated here + phone: "+1234567890", + preferences: { theme: "dark" } + }); + + console.log('Profile updated:', updatedUser); + +} catch (error) { + console.error('Profile update failed:', error.message); + if (error.status === 400) { + console.error('Invalid data provided'); + } else if (error.status === 401) { + console.error('Authentication required'); + navigate('/login'); + } +} +``` + +### Check Authentication Status + +```javascript +try { + const isLoggedIn = await base44.auth.isAuthenticated(); + + if (isLoggedIn) { + // Show authenticated UI + console.log('User is authenticated'); + } else { + // Show login button + console.log('User is not authenticated'); + } + +} catch (error) { + console.error('Failed to check authentication:', error.message); + // On error, treat as not authenticated +} +``` + +### Logout + +```javascript +// Simple logout +base44.auth.logout(); + +// Logout and redirect to goodbye page +base44.auth.logout("/goodbye"); + +// Logout and redirect to homepage +base44.auth.logout("/"); +``` + +### Protected Route Pattern + +```javascript +// Using a navigation function (e.g., React Router's useNavigate, Next.js router) +async function requireAuth(navigate) { + try { + const user = await base44.auth.me(); + + if (!user) { + // Navigate to your custom login page + navigate('/login', { state: { returnTo: window.location.pathname } }); + return null; + } + + return user; + + } catch (error) { + console.error('Authentication check failed:', error.message); + navigate('/login', { state: { returnTo: window.location.pathname } }); + return null; + } +} + +// Usage in your app +async function loadProtectedPage(navigate) { + const user = await requireAuth(navigate); + if (!user) { + // Will navigate to login + return; + } + + // Continue with authenticated logic + console.log('Welcome,', user.full_name); +} +``` + +### Set Authentication Token + +```javascript +// SECURITY WARNING: Never hardcode tokens or expose them in client code +// Tokens should only be received from secure authentication flows + +// Set token and save to localStorage (default) +base44.auth.setToken(receivedToken); + +// Set token without saving to localStorage (temporary session) +base44.auth.setToken(receivedToken, false); + +// Verify token was set +try { + const isAuthenticated = await base44.auth.isAuthenticated(); + if (!isAuthenticated) { + console.error('Token validation failed'); + } +} catch (error) { + console.error('Failed to set token:', error.message); +} +``` + +### Invite User to Application + +```javascript +try { + // Note: Typically requires admin privileges + const response = await base44.auth.inviteUser( + "newuser@example.com", + "user" // or "admin" + ); + + console.log('Invitation sent successfully'); + +} catch (error) { + console.error('Failed to invite user:', error.message); + if (error.status === 403) { + console.error('Insufficient permissions to invite users'); + } else if (error.status === 400) { + console.error('Invalid email or role'); + } +} +``` + +### OTP Verification + +```javascript +try { + // Verify OTP code sent to user's email + await base44.auth.verifyOtp({ + email: "user@example.com", + otpCode: "123456" + }); + + console.log('OTP verified successfully'); + +} catch (error) { + console.error('OTP verification failed:', error.message); + if (error.status === 400) { + console.error('Invalid or expired OTP code'); + } else if (error.status === 429) { + console.error('Too many attempts. Please try again later.'); + } +} + +// Resend OTP if needed +try { + await base44.auth.resendOtp("user@example.com"); + console.log('OTP resent successfully'); + +} catch (error) { + console.error('Failed to resend OTP:', error.message); + if (error.status === 429) { + console.error('Too many requests. Please wait before trying again.'); + } +} +``` + +### Password Reset Flow + +```javascript +// Step 1: Request password reset +try { + await base44.auth.resetPasswordRequest("user@example.com"); + console.log('Password reset email sent. Check your inbox.'); + +} catch (error) { + console.error('Password reset request failed:', error.message); + if (error.status === 429) { + console.error('Too many requests. Please try again later.'); + } + // Note: For security, don't reveal if email exists +} + +// Step 2: Reset password with token from email +try { + await base44.auth.resetPassword({ + resetToken: "token-from-email", + newPassword: "newSecurePassword123" + }); + + console.log('Password reset successfully. You can now log in.'); + +} catch (error) { + console.error('Password reset failed:', error.message); + if (error.status === 400) { + console.error('Invalid or expired reset token'); + } else if (error.status === 422) { + console.error('Password does not meet requirements'); + } +} +``` + +### Change Password + +```javascript +try { + const currentUser = await base44.auth.me(); + + if (!currentUser) { + throw new Error('User must be authenticated to change password'); + } + + await base44.auth.changePassword({ + userId: currentUser.id, + currentPassword: "oldPassword123", + newPassword: "newSecurePassword456" + }); + + console.log('Password changed successfully'); + +} catch (error) { + console.error('Password change failed:', error.message); + if (error.status === 401) { + console.error('Current password is incorrect'); + } else if (error.status === 422) { + console.error('New password does not meet requirements'); + } else if (error.status === 403) { + console.error('Not authorized to change this password'); + } +} +``` + +--- + +## Error Handling + +### Common Error Scenarios + +The auth module can throw various errors. Here are common scenarios and how to handle them: + +#### Authentication Errors (401/403) +```javascript +try { + const user = await base44.auth.me(); +} catch (error) { + if (error.status === 401) { + // Token expired or invalid - navigate to your custom login page + navigate('/login'); + } else if (error.status === 403) { + // Email not verified or insufficient permissions + console.error('Access denied:', error.message); + } +} +``` + +#### Validation Errors (400/422) +```javascript +try { + await base44.auth.register({ + email: "invalid-email", + password: "weak" + }); +} catch (error) { + if (error.status === 400) { + console.error('Invalid input:', error.message); + // Handle validation errors + } else if (error.status === 422) { + console.error('Data validation failed:', error.details); + } +} +``` + +#### Rate Limiting (429) +```javascript +try { + await base44.auth.resendOtp("user@example.com"); +} catch (error) { + if (error.status === 429) { + console.error('Too many requests. Please wait before trying again.'); + // Show countdown or disable button temporarily + } +} +``` + +#### Generic Error Handler +```javascript +function handleAuthError(error) { + switch (error.status) { + case 400: + return 'Invalid input. Please check your data.'; + case 401: + return 'Authentication required. Redirecting to login...'; + case 403: + return 'Access denied. You may need to verify your email.'; + case 404: + return 'Resource not found.'; + case 422: + return 'Validation failed. Please check your input.'; + case 429: + return 'Too many requests. Please try again later.'; + case 500: + return 'Server error. Please try again.'; + default: + return 'An unexpected error occurred.'; + } +} + +// Usage +try { + await base44.auth.loginViaEmailPassword(email, password); +} catch (error) { + console.error(handleAuthError(error)); +} +``` + +--- + +## Auth Providers + +Configure authentication providers in your app dashboard: + +### Available Providers + +**Built-in (All Plans):** +- **Email/Password** - Default, always enabled +- **Google** - OAuth authentication +- **Microsoft** - OAuth authentication +- **Facebook** - OAuth authentication + +**SSO Providers (Elite Plan):** +- **Okta** +- **Azure AD** +- **GitHub** + +### Using OAuth Providers + +```javascript +// Initiate OAuth login flow +base44.auth.loginWithProvider('google'); + +// Return to specific page after authentication +base44.auth.loginWithProvider('microsoft', '/dashboard'); + +// Supported values: 'google', 'microsoft', 'facebook' +``` + +--- + +## Environment Availability + +| Environment | Availability | Notes | +|------------|--------------|-------| +| **Frontend** | ✅ Yes | All methods available | +| **Backend Functions** | ✅ Yes | Use `createClientFromRequest(req)` for authenticated client | +| **Service Role** | ❌ No | Auth methods not available in service role context | + +### Frontend Usage +```javascript +// Standard browser usage +const user = await base44.auth.me(); +``` + +### Backend Functions Usage +```javascript +import { createClientFromRequest } from "@base44/sdk"; + +Deno.serve(async (req) => { + const base44 = createClientFromRequest(req); + + // Get user from request context + const user = await base44.auth.me(); + + // User operations here... + return Response.json({ user }); +}); +``` + +--- + +## App Visibility + +Control who can access your app in the app settings: + +### Public Apps +- No login required for basic access +- Users can view public content without authentication +- Authenticated users get additional features/data + +### Private Apps +- Login required to access any content +- Unauthenticated users are automatically redirected to login +- All content is protected by default + +--- + +## Limitations + +### Authentication UI Options +- **Recommended:** Build custom login/signup UI using `loginViaEmailPassword()` and `loginWithProvider()` for full control over user experience and branding +- **Alternative:** `redirectToLogin()` uses Base44's hosted authentication pages with limited customization + +### Hosted Login (via redirectToLogin) +- `redirectToLogin()` shows both login and signup options on the same page +- No separate `redirectToSignup()` method +- Users can switch between login/signup on the hosted page +- ⚠️ **Note:** Prefer building custom login UI for better user experience + +### Password Requirements +- Minimum length and complexity requirements enforced +- Requirements not exposed via API +- Validation errors returned when requirements not met + +### Rate Limiting +- OTP requests are rate-limited to prevent abuse +- Password reset requests are rate-limited +- Login attempts may be rate-limited with Turnstile protection + +### Token Management +- JWTs are automatically stored in localStorage by default +- Token expiration and refresh not exposed in API +- Call `me()` or `isAuthenticated()` to verify token validity + +--- + +## Best Practices + +### 1. Always Handle Errors +```javascript +try { + await base44.auth.loginViaEmailPassword(email, password); +} catch (error) { + // Always handle authentication errors + console.error('Login failed:', error.message); +} +``` + +### 2. Verify Authentication Before Protected Actions +```javascript +const user = await requireAuth(); +if (!user) return; // Will redirect to login +// Proceed with authenticated action +``` + +### 3. Use Type Safety with TypeScript +```typescript +import type { User, LoginResponse } from '@base44/sdk'; + +const user: User = await base44.auth.me(); +const response: LoginResponse = await base44.auth.loginViaEmailPassword(email, password); +``` + +### 4. Don't Hardcode Credentials +```javascript +// ❌ BAD +base44.auth.setToken("hardcoded-token-here"); + +// ✅ GOOD +const token = await secureAuthFlow(); +base44.auth.setToken(token); +``` + +### 5. Provide User Feedback +```javascript +try { + await base44.auth.register(params); + showSuccessMessage('Registration successful! Check your email for verification code.'); +} catch (error) { + showErrorMessage('Registration failed: ' + error.message); +} +``` + +### 6. Handle Token Expiration Gracefully +```javascript +try { + const user = await base44.auth.me(); +} catch (error) { + if (error.status === 401) { + // Clear local state and navigate to login + localStorage.clear(); + navigate('/login'); + } +} +``` + +### 7. Build Custom Login UI (Recommended) +```javascript +// ✅ RECOMMENDED - Custom login form with direct methods +const handleLogin = async (email, password) => { + try { + const { user } = await base44.auth.loginViaEmailPassword(email, password); + navigate('/dashboard'); + } catch (error) { + setError(error.message); + } +}; + +const handleGoogleLogin = () => { + base44.auth.loginWithProvider('google', '/dashboard'); +}; + +// ❌ AVOID - Redirecting to hosted login page +// base44.auth.redirectToLogin(window.location.href); +``` diff --git a/evals/fixtures/sdk-entity-crud/skills/base44-sdk/references/base44-agents.md b/evals/fixtures/sdk-entity-crud/skills/base44-sdk/references/base44-agents.md new file mode 100644 index 0000000..ee55dee --- /dev/null +++ b/evals/fixtures/sdk-entity-crud/skills/base44-sdk/references/base44-agents.md @@ -0,0 +1,364 @@ +# Agents Module + +AI agent conversations and messages via `base44.agents`. + +> **Note:** This module requires a logged-in user. All agent methods work in the context of the authenticated user. + +## Contents +- [Concepts](#concepts) +- [Methods](#methods) +- [Examples](#examples) (Create, Get Conversations, List, Subscribe, Send Message, WhatsApp) +- [Message Structure](#message-structure) +- [Conversation Structure](#conversation-structure) +- [Common Patterns](#common-patterns) + +## Concepts + +- **Conversation**: A dialogue between user and an AI agent. Has unique ID, agent name, user reference, and metadata. +- **Message**: Single message in a conversation. Has role (`user`, `assistant`, `system`), content, timestamps, and optional metadata. + +## Methods + +| Method | Signature | Description | +|--------|-----------|-------------| +| `createConversation(params)` | `Promise` | Create a new conversation with an agent | +| `getConversations()` | `Promise` | Get all user's conversations | +| `getConversation(id)` | `Promise` | Get conversation with messages | +| `listConversations(filterParams)` | `Promise` | Filter/sort/paginate conversations | +| `subscribeToConversation(id, onUpdate?)` | `() => void` | Real-time updates via WebSocket (returns unsubscribe function) | +| `addMessage(conversation, message)` | `Promise` | Send a message | +| `getWhatsAppConnectURL(agentName)` | `string` | Get WhatsApp connection URL for agent | + +## Examples + +### Create Conversation + +```javascript +const conversation = await base44.agents.createConversation({ + agent_name: "support-agent", + metadata: { + order_id: "ORD-123", + category: "billing" + } +}); + +console.log(conversation.id); +``` + +### Get All Conversations + +```javascript +const conversations = await base44.agents.getConversations(); + +conversations.forEach(conv => { + console.log(conv.id, conv.agent_name, conv.created_date); +}); +``` + +### Get Single Conversation (with messages) + +```javascript +const conversation = await base44.agents.getConversation("conv-id-123"); + +console.log(conversation.messages); +``` + +### List with Filters + +```javascript +// Using filterParams object with q, sort, limit, skip, fields +const recent = await base44.agents.listConversations({ + q: { agent_name: "support-agent" }, + sort: "-created_date", + limit: 10, + skip: 0 +}); + +// Filter by metadata +const highPriority = await base44.agents.listConversations({ + q: { + agent_name: "support-agent", + "metadata.priority": "high" + }, + sort: "-updated_date", + limit: 20 +}); +``` + +### Subscribe to Updates (Real-time) + +```javascript +const unsubscribe = base44.agents.subscribeToConversation( + "conv-id-123", + (updatedConversation) => { + // Called when new messages arrive + console.log("New messages:", updatedConversation.messages); + } +); + +// Later: unsubscribe +unsubscribe(); +``` + +### Send a Message + +```javascript +const conversation = await base44.agents.getConversation("conv-id-123"); + +await base44.agents.addMessage(conversation, { + role: "user", + content: "What's the weather like today?" +}); +``` + +### Get WhatsApp Connection URL + +```javascript +const whatsappUrl = base44.agents.getWhatsAppConnectURL("support-agent"); +// Returns URL for users to connect with agent via WhatsApp +console.log(whatsappUrl); +``` + +## Message Structure + +```javascript +{ + role: "user" | "assistant" | "system", + content: "Message text or structured object", + created_date: "2024-01-15T10:30:00Z", + updated_date: "2024-01-15T10:30:00Z", + + // Optional fields + reasoning: { + content: "Agent's reasoning process", + timing: 1500 + }, + tool_calls: [{ + name: "search", + arguments: { query: "weather" }, + result: { ... }, + status: "success" + }], + file_urls: ["https://..."], + usage: { + prompt_tokens: 150, + completion_tokens: 50 + }, + metadata: { ... }, + custom_context: { ... } +} +``` + +## Conversation Structure + +```javascript +{ + id: "conv-id-123", + app_id: "app-id", + agent_name: "support-agent", + created_by_id: "user-id", + created_date: "2024-01-15T10:00:00Z", + updated_date: "2024-01-15T10:30:00Z", + messages: [ ... ], + metadata: { ... } +} +``` + +## Common Patterns + +### Chat Interface + +```javascript +// Load conversation +const conv = await base44.agents.getConversation(conversationId); +setMessages(conv.messages); + +// Subscribe to updates +const unsubscribe = base44.agents.subscribeToConversation(conversationId, (updated) => { + setMessages(updated.messages); +}); + +// Send message +async function sendMessage(text) { + await base44.agents.addMessage(conv, { role: "user", content: text }); +} + +// Cleanup on unmount +return () => unsubscribe(); +``` + +## Type Definitions + +### AgentConversation + +```typescript +/** An agent conversation containing messages exchanged with an AI agent. */ +interface AgentConversation { + /** Unique identifier for the conversation. */ + id: string; + /** Application ID. */ + app_id: string; + /** Name of the agent in this conversation. */ + agent_name: string; + /** ID of the user who created the conversation. */ + created_by_id: string; + /** When the conversation was created. */ + created_date: string; + /** When the conversation was last updated. */ + updated_date: string; + /** Array of messages in the conversation. */ + messages: AgentMessage[]; + /** Optional metadata associated with the conversation. */ + metadata?: Record; +} +``` + +### AgentMessage + +```typescript +/** A message in an agent conversation. */ +interface AgentMessage { + /** Unique identifier for the message. */ + id: string; + /** Role of the message sender. */ + role: "user" | "assistant" | "system"; + /** When the message was created. */ + created_date: string; + /** When the message was last updated. */ + updated_date: string; + /** Message content. */ + content?: string | Record; + /** Optional reasoning information for the message. */ + reasoning?: AgentMessageReasoning | null; + /** URLs to files attached to the message. */ + file_urls?: string[]; + /** Tool calls made by the agent. */ + tool_calls?: AgentMessageToolCall[]; + /** Token usage statistics. */ + usage?: AgentMessageUsage; + /** Whether the message is hidden from the user. */ + hidden?: boolean; + /** Custom context provided with the message. */ + custom_context?: AgentMessageCustomContext[]; + /** Model used to generate the message. */ + model?: string; + /** Checkpoint ID for the message. */ + checkpoint_id?: string; + /** Metadata about when and by whom the message was created. */ + metadata?: AgentMessageMetadata; +} +``` + +### Supporting Types + +```typescript +/** Reasoning information for an agent message. */ +interface AgentMessageReasoning { + /** When reasoning started. */ + start_date: string; + /** When reasoning ended. */ + end_date?: string; + /** Reasoning content. */ + content: string; +} + +/** A tool call made by the agent. */ +interface AgentMessageToolCall { + /** Tool call ID. */ + id: string; + /** Name of the tool called. */ + name: string; + /** Arguments passed to the tool as JSON string. */ + arguments_string: string; + /** Status of the tool call. */ + status: "running" | "success" | "error" | "stopped"; + /** Results from the tool call. */ + results?: string; +} + +/** Token usage statistics for an agent message. */ +interface AgentMessageUsage { + /** Number of tokens in the prompt. */ + prompt_tokens?: number; + /** Number of tokens in the completion. */ + completion_tokens?: number; +} + +/** Custom context provided with an agent message. */ +interface AgentMessageCustomContext { + /** Context message. */ + message: string; + /** Associated data for the context. */ + data: Record; + /** Type of context. */ + type: string; +} + +/** Metadata about when and by whom a message was created. */ +interface AgentMessageMetadata { + /** When the message was created. */ + created_date: string; + /** Email of the user who created the message. */ + created_by_email: string; + /** Full name of the user who created the message. */ + created_by_full_name: string; +} +``` + +### CreateConversationParams + +```typescript +/** Parameters for creating a new conversation. */ +interface CreateConversationParams { + /** The name of the agent to create a conversation with. */ + agent_name: string; + /** Optional metadata to attach to the conversation. */ + metadata?: Record; +} +``` + +### ModelFilterParams + +```typescript +/** Parameters for filtering, sorting, and paginating conversations. */ +interface ModelFilterParams { + /** Query object with field-value pairs for filtering. */ + q?: Record; + /** Sort parameter (e.g., "-created_date" for descending). */ + sort?: string | null; + /** Maximum number of results to return. */ + limit?: number | null; + /** Number of results to skip for pagination. */ + skip?: number | null; + /** Array of field names to include in the response. */ + fields?: string[] | null; +} +``` + +### AgentsModule + +```typescript +/** Agents module for managing AI agent conversations. */ +interface AgentsModule { + /** Gets all conversations from all agents in the app. */ + getConversations(): Promise; + + /** Gets a specific conversation by ID. */ + getConversation(conversationId: string): Promise; + + /** Lists conversations with filtering, sorting, and pagination. */ + listConversations(filterParams: ModelFilterParams): Promise; + + /** Creates a new conversation with an agent. */ + createConversation(conversation: CreateConversationParams): Promise; + + /** Adds a message to a conversation. */ + addMessage(conversation: AgentConversation, message: Partial): Promise; + + /** Subscribes to real-time updates for a conversation. Returns unsubscribe function. */ + subscribeToConversation(conversationId: string, onUpdate?: (conversation: AgentConversation) => void): () => void; + + /** Gets WhatsApp connection URL for an agent. */ + getWhatsAppConnectURL(agentName: string): string; +} +``` diff --git a/evals/fixtures/sdk-entity-crud/skills/base44-sdk/references/client.md b/evals/fixtures/sdk-entity-crud/skills/base44-sdk/references/client.md new file mode 100644 index 0000000..554bf2b --- /dev/null +++ b/evals/fixtures/sdk-entity-crud/skills/base44-sdk/references/client.md @@ -0,0 +1,257 @@ +# Client Setup + +How to create and configure the Base44 client. + +## Contents +- [In Base44-Generated Apps](#in-base44-generated-apps) +- [In External Apps](#in-external-apps) +- [In Backend Functions](#in-backend-functions) +- [Authentication Modes](#authentication-modes) (Anonymous, User, Service Role) +- [Available Modules](#available-modules) +- [Client Methods](#client-methods) +- [Client Configuration Options](#client-configuration-options) + +## In Base44-Generated Apps + +The client is pre-configured and available as `base44`. Just use it: + +```javascript +const tasks = await base44.entities.Task.list(); +``` + +## In External Apps + +Install the SDK and create a client: + +```bash +npm install @base44/sdk +``` + +```javascript +import { createClient } from "@base44/sdk"; + +// IMPORTANT: The parameter name is 'appId' (NOT 'clientId', NOT 'id') +// IMPORTANT: onError must be nested inside 'options' object +const base44 = createClient({ + appId: "your-app-id", // Required: Use 'appId' parameter + token: "optional-user-token", // Optional: for pre-authenticated requests + options: { // Optional: configuration options + onError: (error) => { // Optional: error handler (must be in options) + console.error("Base44 error:", error); + } + } +}); +``` + +**Common Mistakes:** +- ❌ `createClient({ clientId: "..." })` - WRONG parameter name +- ❌ `createClient({ id: "..." })` - WRONG parameter name +- ❌ `createClient({ appId: "...", onError: ... })` - WRONG: onError must be in options +- ✅ `createClient({ appId: "..." })` - CORRECT parameter name +- ✅ `createClient({ appId: "...", options: { onError: ... } })` - CORRECT: onError in options + +## In Backend Functions + +Use `createClientFromRequest` to get a client with the caller's auth context: + +```javascript +import { createClientFromRequest } from "@base44/sdk"; + +Deno.serve(async (req) => { + const base44 = createClientFromRequest(req); + + // Client inherits authentication from the request + const user = await base44.auth.me(); + + return Response.json({ user }); +}); +``` + +## Authentication Modes + +| Mode | How to Get | Permissions | +|------|-----------|-------------| +| **Anonymous** | `createClient({ appId })` without token | Public data only | +| **User** | After `loginViaEmailPassword()` or via `createClientFromRequest` | User's own data | +| **Service Role** | `base44.asServiceRole.*` in backend | Full admin access | + +## Anonymous Mode + +No authentication. Can only access public resources. + +```javascript +const base44 = createClient({ appId: "your-app-id" }); + +// Only works if Task entity allows anonymous read +const publicTasks = await base44.entities.Task.list(); +``` + +## User Mode + +After user logs in, the client automatically includes their token. + +```javascript +const base44 = createClient({ appId: "your-app-id" }); + +// Login sets the token +await base44.auth.loginViaEmailPassword("user@example.com", "password"); + +// Subsequent requests are authenticated +const user = await base44.auth.me(); +const myTasks = await base44.entities.Task.list(); // filtered by permissions +``` + +## Service Role Mode + +Admin-level access. **Backend only.** + +```javascript +// Inside a backend function +Deno.serve(async (req) => { + const base44 = createClientFromRequest(req); + + // User mode - respects permissions + const myTasks = await base44.entities.Task.list(); + + // Service role - bypasses permissions + const allTasks = await base44.asServiceRole.entities.Task.list(); + const allUsers = await base44.asServiceRole.entities.User.list(); + const oauthToken = await base44.asServiceRole.connectors.getAccessToken("slack"); + + return Response.json({ myTasks, allTasks }); +}); +``` + +## Available Modules + +The client exposes these modules: + +```javascript +base44.entities // CRUD operations +base44.auth // Authentication +base44.agents // AI conversations +base44.functions // Backend function invocation +base44.integrations // Third-party services +base44.analytics // Event tracking +base44.appLogs // App usage logging +base44.users // User invitations + +// Service role only (backend) +base44.asServiceRole.entities +base44.asServiceRole.agents +base44.asServiceRole.functions +base44.asServiceRole.integrations +base44.asServiceRole.appLogs +base44.asServiceRole.connectors +``` + +## Client Methods + +The client provides these methods: + +```javascript +// Set authentication token for all subsequent requests +base44.setToken(newToken); + +// Cleanup WebSocket connections (call when done with client) +base44.cleanup(); +``` + +### setToken + +Updates the authentication token for all subsequent API requests and WebSocket connections. + +```javascript +// After receiving a token (e.g., from external auth) +base44.setToken("new-jwt-token"); +``` + +### cleanup + +Disconnects WebSocket connections. Call when you're done with the client or when the component unmounts. + +```javascript +// Cleanup on component unmount (React example) +useEffect(() => { + return () => base44.cleanup(); +}, []); +``` + +## Client Configuration Options + +```javascript +createClient({ + appId: "your-app-id", // Required: MUST use 'appId' (not 'clientId' or 'id') + token: "jwt-token", // Optional: pre-set auth token + options: { // Optional: configuration options + onError: (error) => {} // Optional: global error handler (must be in options) + } +}); +``` + +**⚠️ Critical:** +- The parameter name is `appId`, not `clientId` or `id`. Using the wrong parameter name will cause errors. +- The `onError` handler must be nested inside the `options` object, not at the top level. + +## Type Definitions + +### CreateClientConfig + +```typescript +/** Configuration for creating a Base44 client. */ +interface CreateClientConfig { + /** The Base44 app ID (required). */ + appId: string; + /** User authentication token. Used to authenticate as a specific user. */ + token?: string; + /** Service role authentication token (backend only). */ + serviceToken?: string; + /** Additional client options. */ + options?: CreateClientOptions; +} + +/** Options for creating a Base44 client. */ +interface CreateClientOptions { + /** Optional error handler called whenever an API error occurs. */ + onError?: (error: Error) => void; +} +``` + +### Base44Client + +```typescript +/** The Base44 client instance. */ +interface Base44Client { + /** Entities module for CRUD operations on your data models. */ + entities: EntitiesModule; + /** Integrations module for calling pre-built integration endpoints. */ + integrations: IntegrationsModule; + /** Auth module for user authentication and management. */ + auth: AuthModule; + /** Functions module for invoking custom backend functions. */ + functions: FunctionsModule; + /** Agents module for managing AI agent conversations. */ + agents: AgentsModule; + /** App logs module for tracking app usage. */ + appLogs: AppLogsModule; + /** Analytics module for tracking custom events. */ + analytics: AnalyticsModule; + + /** Cleanup function to disconnect WebSocket connections. */ + cleanup(): void; + + /** Sets a new authentication token for all subsequent requests. */ + setToken(newToken: string): void; + + /** Provides access to modules with elevated service role permissions (backend only). */ + readonly asServiceRole: { + entities: EntitiesModule; + integrations: IntegrationsModule; + connectors: ConnectorsModule; + functions: FunctionsModule; + agents: AgentsModule; + appLogs: AppLogsModule; + cleanup(): void; + }; +} +``` diff --git a/evals/fixtures/sdk-entity-crud/skills/base44-sdk/references/connectors.md b/evals/fixtures/sdk-entity-crud/skills/base44-sdk/references/connectors.md new file mode 100644 index 0000000..f3fbcb4 --- /dev/null +++ b/evals/fixtures/sdk-entity-crud/skills/base44-sdk/references/connectors.md @@ -0,0 +1,113 @@ +# Connectors Module + +OAuth token management for external services. **Service role only** (backend functions). + +## Method + +```javascript +base44.asServiceRole.connectors.getAccessToken(integrationType): Promise +``` + +Returns a raw OAuth access token for the specified service. + +## Supported Services + +| Integration Type | Service | +|-----------------|---------| +| `"googlecalendar"` | Google Calendar | +| `"googledrive"` | Google Drive | +| `"slack"` | Slack | +| `"notion"` | Notion | +| `"salesforce"` | Salesforce | +| `"hubspot"` | HubSpot | +| `"linkedin"` | LinkedIn | +| `"tiktok"` | TikTok | +| `"github"` | GitHub | + +## Example Usage + +```javascript +// Backend function only +Deno.serve(async (req) => { + const base44 = createClientFromRequest(req); + + // Get OAuth token for Slack + const slackToken = await base44.asServiceRole.connectors.getAccessToken("slack"); + + // Use token directly with Slack API + const response = await fetch("https://slack.com/api/chat.postMessage", { + method: "POST", + headers: { + "Authorization": `Bearer ${slackToken}`, + "Content-Type": "application/json" + }, + body: JSON.stringify({ + channel: "#general", + text: "Hello from Base44!" + }) + }); + + return Response.json(await response.json()); +}); +``` + +## Google Calendar Example + +```javascript +Deno.serve(async (req) => { + const base44 = createClientFromRequest(req); + + const token = await base44.asServiceRole.connectors.getAccessToken("googlecalendar"); + + // List upcoming events + const response = await fetch( + "https://www.googleapis.com/calendar/v3/calendars/primary/events?" + + new URLSearchParams({ + maxResults: "10", + orderBy: "startTime", + singleEvents: "true", + timeMin: new Date().toISOString() + }), + { + headers: { "Authorization": `Bearer ${token}` } + } + ); + + const events = await response.json(); + return Response.json(events); +}); +``` + +## Setup Requirements + +1. **Builder plan** or higher +2. **Backend functions** enabled +3. **Connector configured** in Base44 dashboard (OAuth flow completed) + +## Important Notes + +- **One account per connector per app**: All users share the same connected account +- **Backend only**: `connectors` module not available in frontend code +- **Service role required**: Must use `base44.asServiceRole.connectors` +- **You handle the API calls**: Base44 only provides the token; you make the actual API requests +- **Token refresh**: Base44 handles token refresh automatically + +## Type Definitions + +```typescript +/** + * The type of external integration/connector. + * Examples: 'googlecalendar', 'slack', 'github', 'notion', etc. + */ +type ConnectorIntegrationType = string; + +/** Connectors module for managing OAuth tokens for external services. */ +interface ConnectorsModule { + /** + * Retrieves an OAuth access token for a specific external integration type. + * @param integrationType - The type of integration (e.g., 'googlecalendar', 'slack'). + * @returns Promise resolving to the access token string. + */ + getAccessToken(integrationType: ConnectorIntegrationType): Promise; +} +``` diff --git a/evals/fixtures/sdk-entity-crud/skills/base44-sdk/references/entities.md b/evals/fixtures/sdk-entity-crud/skills/base44-sdk/references/entities.md new file mode 100644 index 0000000..a7bdf35 --- /dev/null +++ b/evals/fixtures/sdk-entity-crud/skills/base44-sdk/references/entities.md @@ -0,0 +1,253 @@ +# Entities Module + +CRUD operations on data models. Access via `base44.entities.EntityName.method()`. + +## Contents +- [Methods](#methods) +- [Examples](#examples) (Create, Bulk Create, List, Filter, Get, Update, Delete, Subscribe) +- [User Entity](#user-entity) +- [Service Role Access](#service-role-access) +- [Permissions](#permissions) + +## Methods + +| Method | Signature | Description | +|--------|-----------|-------------| +| `create(data)` | `Promise` | Create one record | +| `bulkCreate(dataArray)` | `Promise` | Create multiple records | +| `list(sort?, limit?, skip?, fields?)` | `Promise` | Get all records (paginated) | +| `filter(query, sort?, limit?, skip?, fields?)` | `Promise` | Get records matching conditions | +| `get(id)` | `Promise` | Get single record by ID | +| `update(id, data)` | `Promise` | Update record (partial update) | +| `delete(id)` | `Promise` | Delete record by ID | +| `deleteMany(query)` | `Promise` | Delete all matching records | +| `importEntities(file)` | `Promise` | Import from CSV (frontend only) | +| `subscribe(callback)` | `() => void` | Subscribe to realtime updates (returns unsubscribe function) | + +## Examples + +### Create + +```javascript +const task = await base44.entities.Task.create({ + title: "Complete documentation", + status: "pending", + dueDate: "2024-12-31" +}); +``` + +### Bulk Create + +```javascript +const tasks = await base44.entities.Task.bulkCreate([ + { title: "Task 1", status: "pending" }, + { title: "Task 2", status: "pending" } +]); +``` + +### List with Pagination + +```javascript +// Get first 10 records, sorted by created_date descending +const tasks = await base44.entities.Task.list( + "-created_date", // sort (string: prefix with - for descending) + 10, // limit + 0 // skip +); + +// Get next page +const page2 = await base44.entities.Task.list("-created_date", 10, 10); +``` + +### Filter + +```javascript +// Simple filter +const pending = await base44.entities.Task.filter({ status: "pending" }); + +// Multiple conditions +const myPending = await base44.entities.Task.filter({ + status: "pending", + assignedTo: userId +}); + +// With sort, limit, skip +const recent = await base44.entities.Task.filter( + { status: "pending" }, + "-created_date", // sort (string: prefix with - for descending) + 5, + 0 +); + +// Select specific fields +const titles = await base44.entities.Task.filter( + { status: "pending" }, + null, + null, + null, + ["id", "title"] +); +``` + +### Get by ID + +```javascript +const task = await base44.entities.Task.get("task-id-123"); +``` + +### Update + +```javascript +// Partial update - only specified fields change +await base44.entities.Task.update("task-id-123", { + status: "completed", + completedAt: new Date().toISOString() +}); +``` + +### Delete + +```javascript +// Single record +await base44.entities.Task.delete("task-id-123"); + +// Multiple records matching query +await base44.entities.Task.deleteMany({ status: "archived" }); +``` + +### Subscribe to Realtime Updates + +```javascript +// Subscribe to all changes on Task entity +const unsubscribe = base44.entities.Task.subscribe((event) => { + console.log(`Task ${event.id} was ${event.type}:`, event.data); + // event.type is "create", "update", or "delete" +}); + +// Later: unsubscribe to stop receiving updates +unsubscribe(); +``` + +**Event structure:** +```javascript +{ + type: "create" | "update" | "delete", + data: { ... }, // the entity data + id: "entity-id", // the affected entity's ID + timestamp: "2024-01-15T10:30:00Z" +} +``` + +## User Entity + +Every app has a built-in `User` entity with special rules: + +- Regular users can only read/update **their own** record +- Cannot create users via `entities.create()` - use `auth.register()` instead +- Service role has full access to all user records + +```javascript +// Get current user's record +const me = await base44.entities.User.get(currentUserId); + +// Service role: get any user +const anyUser = await base44.asServiceRole.entities.User.get(userId); +``` + +## Service Role Access + +For admin-level operations (bypass user permissions): + +```javascript +// Backend only +const allTasks = await base44.asServiceRole.entities.Task.list(); +const allUsers = await base44.asServiceRole.entities.User.list(); +``` + +## Permissions (RLS & FLS) + +Data access is controlled by **Row Level Security (RLS)** and **Field Level Security (FLS)** rules defined in entity schemas. + +1. **Authentication level**: anonymous, authenticated, or service role +2. **RLS rules**: Control which records (rows) users can create/read/update/delete +3. **FLS rules**: Control which fields users can read/write within accessible records + +Operations succeed or fail based on these rules - no partial results. + +RLS and FLS are configured in entity schema files (`base44/entities/*.jsonc`). See [entities-create.md](../../base44-cli/references/entities-create.md#row-level-security-rls) for configuration details. + +**Note:** `asServiceRole` sets the user's role to `"admin"` but does NOT bypass RLS. Your RLS rules must include admin access (e.g., `{ "user_condition": { "role": "admin" } }`) for service role operations to succeed. + +## Type Definitions + +### RealtimeEvent + +```typescript +/** Event types for realtime entity updates. */ +type RealtimeEventType = "create" | "update" | "delete"; + +/** Payload received when a realtime event occurs. */ +interface RealtimeEvent { + /** The type of change that occurred. */ + type: RealtimeEventType; + /** The entity data. */ + data: any; + /** The unique identifier of the affected entity. */ + id: string; + /** ISO 8601 timestamp of when the event occurred. */ + timestamp: string; +} + +/** Callback function invoked when a realtime event occurs. */ +type RealtimeCallback = (event: RealtimeEvent) => void; + +/** Function returned from subscribe, call it to unsubscribe. */ +type Subscription = () => void; +``` + +### EntityHandler + +```typescript +/** Entity handler providing CRUD operations for a specific entity type. */ +interface EntityHandler { + /** Lists records with optional pagination and sorting. */ + list(sort?: string, limit?: number, skip?: number, fields?: string[]): Promise; + + /** Filters records based on a query. */ + filter(query: Record, sort?: string, limit?: number, skip?: number, fields?: string[]): Promise; + + /** Gets a single record by ID. */ + get(id: string): Promise; + + /** Creates a new record. */ + create(data: Record): Promise; + + /** Updates an existing record. */ + update(id: string, data: Record): Promise; + + /** Deletes a single record by ID. */ + delete(id: string): Promise; + + /** Deletes multiple records matching a query. */ + deleteMany(query: Record): Promise; + + /** Creates multiple records in a single request. */ + bulkCreate(data: Record[]): Promise; + + /** Imports records from a file (frontend only). */ + importEntities(file: File): Promise; + + /** Subscribes to realtime updates. Returns unsubscribe function. */ + subscribe(callback: RealtimeCallback): Subscription; +} +``` + +### EntitiesModule + +```typescript +/** Entities module for managing app data. */ +interface EntitiesModule { + /** Access any entity by name dynamically. */ + [entityName: string]: EntityHandler; +} +``` diff --git a/evals/fixtures/sdk-entity-crud/skills/base44-sdk/references/functions.md b/evals/fixtures/sdk-entity-crud/skills/base44-sdk/references/functions.md new file mode 100644 index 0000000..8072465 --- /dev/null +++ b/evals/fixtures/sdk-entity-crud/skills/base44-sdk/references/functions.md @@ -0,0 +1,216 @@ +# Functions Module + +Invoke custom backend functions via `base44.functions`. + +## Contents +- [Method](#method) +- [Invoking Functions](#invoking-functions) (Frontend, File Upload, Service Role, REST API) +- [Writing Backend Functions](#writing-backend-functions) (Basic, Service Role, Secrets, Errors) +- [Setup Requirements](#setup-requirements) +- [Authentication Modes](#authentication-modes) + +## Method + +```javascript +base44.functions.invoke(functionName, data): Promise +``` + +- `functionName`: Name of the backend function +- `data`: Object of parameters (sent as JSON, or multipart if contains File objects) +- Returns: Whatever the function returns + +## Invoking Functions + +### From Frontend + +```javascript +const result = await base44.functions.invoke("processOrder", { + orderId: "order-123", + action: "ship" +}); + +console.log(result); +``` + +### With File Upload + +```javascript +const fileInput = document.querySelector('input[type="file"]'); +const file = fileInput.files[0]; + +// Automatically uses multipart/form-data when File objects present +const result = await base44.functions.invoke("uploadDocument", { + file: file, + category: "invoices" +}); +``` + +### With Service Role (Backend) + +```javascript +// Inside another backend function +const result = await base44.asServiceRole.functions.invoke("adminTask", { + userId: "user-123" +}); +``` + +### Via REST API (curl) + +Functions can be called via HTTP POST to your app domain: + +```bash +curl -X POST "https:///functions/" \ + -H "Content-Type: application/json" \ + -d '{"key": "value"}' +``` + +## Writing Backend Functions + +Backend functions run on Deno. Must export using `Deno.serve()`. + +### Required Directory Structure + +Each function must be in its own subdirectory under `base44/functions/` with a configuration file: + +``` +base44/ + functions/ + process-order/ # kebab-case directory name + function.jsonc # required configuration + index.ts # entry point +``` + +**function.jsonc:** +```jsonc +{ + "name": "process-order", + "entry": "index.ts" +} +``` + +For complete setup and deployment instructions, see [functions-create.md](../../base44-cli/references/functions-create.md) in base44-cli. + +### Basic Structure + +```javascript +// base44/functions/process-order/index.ts +import { createClientFromRequest } from "npm:@base44/sdk"; + +Deno.serve(async (req) => { + // Get authenticated client from request + const base44 = createClientFromRequest(req); + + // Parse input + const { orderId, action } = await req.json(); + + // Your logic here + const order = await base44.entities.Orders.get(orderId); + + // Return response + return Response.json({ + success: true, + order: order + }); +}); +``` + +### With Service Role Access + +```javascript +import { createClientFromRequest } from "npm:@base44/sdk"; + +Deno.serve(async (req) => { + const base44 = createClientFromRequest(req); + + // Check user is authenticated + const user = await base44.auth.me(); + if (!user) { + return Response.json({ error: "Unauthorized" }, { status: 401 }); + } + + // Use service role for admin operations + const allOrders = await base44.asServiceRole.entities.Orders.list(); + + return Response.json({ orders: allOrders }); +}); +``` + +### Using Secrets + +```javascript +Deno.serve(async (req) => { + // Access environment variables (configured in app settings) + const apiKey = Deno.env.get("STRIPE_API_KEY"); + + const response = await fetch("https://api.stripe.com/v1/charges", { + headers: { + "Authorization": `Bearer ${apiKey}` + } + }); + + return Response.json(await response.json()); +}); +``` + +### Error Handling + +```javascript +import { createClientFromRequest } from "npm:@base44/sdk"; + +Deno.serve(async (req) => { + try { + const base44 = createClientFromRequest(req); + const { orderId } = await req.json(); + + const order = await base44.entities.Orders.get(orderId); + if (!order) { + return Response.json( + { error: "Order not found" }, + { status: 404 } + ); + } + + return Response.json({ order }); + + } catch (error) { + return Response.json( + { error: error.message }, + { status: 500 } + ); + } +}); +``` + +## Setup Requirements + +1. Enable Backend Functions in app settings (requires appropriate plan) +2. Create function files in `/functions` folder +3. Configure secrets via app dashboard for API keys + +## Authentication Modes + +| Mode | Context | Permissions | +|------|---------|-------------| +| User | `base44.functions.invoke()` | Runs under calling user's permissions | +| Service Role | `base44.asServiceRole.functions.invoke()` | Admin-level access | + +Inside the function, use `createClientFromRequest(req)` to get a client that inherits the caller's auth context. + +## Type Definitions + +```typescript +/** Functions module for invoking custom backend functions. */ +interface FunctionsModule { + /** + * Invokes a custom backend function by name. + * + * If any parameter is a File object, the request will automatically be + * sent as multipart/form-data. Otherwise, it will be sent as JSON. + * + * @param functionName - The name of the function to invoke. + * @param data - An object containing named parameters for the function. + * @returns Promise resolving to the function's response. + */ + invoke(functionName: string, data: Record): Promise; +} +``` diff --git a/evals/fixtures/sdk-entity-crud/skills/base44-sdk/references/integrations.md b/evals/fixtures/sdk-entity-crud/skills/base44-sdk/references/integrations.md new file mode 100644 index 0000000..f356a18 --- /dev/null +++ b/evals/fixtures/sdk-entity-crud/skills/base44-sdk/references/integrations.md @@ -0,0 +1,377 @@ +# Integrations Module + +Access third-party services via `base44.integrations`. + +## Types of Integrations + +1. **Core/Built-in**: AI, email, file uploads (available by default) +2. **Catalog integrations**: Pre-built connectors from Base44 catalog +3. **Custom integrations**: Your own OpenAPI-based integrations + +## Accessing Integrations + +```javascript +// Core integrations +base44.integrations.Core.FunctionName(params) + +// Custom integrations +base44.integrations.custom.call(slug, operationId, params) +``` + +## Core Integrations + +### InvokeLLM (AI Text Generation) + +Generate text or structured JSON data using AI models. + +```javascript +// Basic prompt - returns string +const response = await base44.integrations.Core.InvokeLLM({ + prompt: "Summarize this text: ..." +}); + +// With internet context (uses Google Search, Maps, News) +const response = await base44.integrations.Core.InvokeLLM({ + prompt: "What's the current weather in NYC?", + add_context_from_internet: true +}); + +// Structured JSON response - returns object +const response = await base44.integrations.Core.InvokeLLM({ + prompt: "Analyze the sentiment of: 'Great product but slow shipping'", + response_json_schema: { + type: "object", + properties: { + sentiment: { type: "string", enum: ["positive", "negative", "mixed"] }, + score: { type: "number", description: "Score from 1-10" }, + key_points: { type: "array", items: { type: "string" } } + } + } +}); +// Returns: { sentiment: "mixed", score: 7, key_points: ["great product", "slow shipping"] } + +// With file attachments (uploaded via UploadFile) +const response = await base44.integrations.Core.InvokeLLM({ + prompt: "Describe what's in this image", + file_urls: ["https://...uploaded_image.png"] +}); +``` + +**Parameters:** +- `prompt` (string, required): The prompt text to send to the model +- `add_context_from_internet` (boolean, optional): If true, uses Google Search/Maps/News for real-time context +- `response_json_schema` (object, optional): JSON schema for structured output +- `file_urls` (string[], optional): URLs of uploaded files for context + +### GenerateImage + +Create AI-generated images from text prompts. + +```javascript +const { url } = await base44.integrations.Core.GenerateImage({ + prompt: "A serene mountain landscape with a lake" +}); +console.log(url); // https://...generated_image.png +``` + +### SendEmail + +Send emails to registered users. Every app gets this integration (no plan upgrade required). + +```javascript +await base44.integrations.Core.SendEmail({ + to: "user@example.com", + subject: "Welcome!", + body: "

Hello

Welcome to our app.

", + from_name: "My App" // optional, defaults to app name +}); +``` + +**Parameters:** +- `to` (string, required): Recipient email address +- `subject` (string, required): Email subject line +- `body` (string, required): Plain text or HTML email body +- `from_name` (string, optional): Sender name displayed to recipient + +**Limitations:** +- 1 credit per email (2 credits with custom domain) + +### UploadFile (Public) + +Upload files to public storage. + +```javascript +const fileInput = document.querySelector('input[type="file"]'); +const { file_url } = await base44.integrations.Core.UploadFile({ + file: fileInput.files[0] +}); +console.log(file_url); // https://...uploaded_file.pdf +``` + +### UploadPrivateFile + +Upload files to private storage that requires a signed URL to access. + +```javascript +const { file_uri } = await base44.integrations.Core.UploadPrivateFile({ + file: fileInput.files[0] +}); +console.log(file_uri); // "private/user123/document.pdf" + +// Create a signed URL to access the file +const { signed_url } = await base44.integrations.Core.CreateFileSignedUrl({ + file_uri, + expires_in: 3600 // URL expires in 1 hour (default: 300 seconds) +}); +console.log(signed_url); // Temporary URL to access the private file +``` + +### CreateFileSignedUrl + +Generate temporary access links for private files. + +```javascript +const { signed_url } = await base44.integrations.Core.CreateFileSignedUrl({ + file_uri: "private/user123/document.pdf", + expires_in: 7200 // 2 hours +}); +``` + +**Parameters:** +- `file_uri` (string, required): URI from UploadPrivateFile +- `expires_in` (number, optional): Expiration time in seconds (default: 300) + +### ExtractDataFromUploadedFile + +Extract structured data from uploaded files using AI. + +```javascript +// First upload the file +const { file_url } = await base44.integrations.Core.UploadFile({ file }); + +// Then extract structured data +const result = await base44.integrations.Core.ExtractDataFromUploadedFile({ + file_url, + json_schema: { + type: "object", + properties: { + invoice_number: { type: "string" }, + total_amount: { type: "number" }, + date: { type: "string" }, + vendor_name: { type: "string" } + } + } +}); +console.log(result); // { invoice_number: "INV-12345", total_amount: 1250.00, ... } +``` + +## Custom Integrations + +Custom integrations allow workspace administrators to connect any external API by importing an OpenAPI specification. Use `base44.integrations.custom.call()` to invoke them. + +### Syntax + +```javascript +const response = await base44.integrations.custom.call( + slug, // Integration identifier (set by admin) + operationId, // Endpoint in "method:path" format + params // Optional: payload, pathParams, queryParams +); +``` + +### Examples + +```javascript +// GET request with query params +const response = await base44.integrations.custom.call( + "my-crm", + "get:/contacts", + { queryParams: { limit: 10, status: "active" } } +); + +// POST request with body +const response = await base44.integrations.custom.call( + "my-crm", + "post:/contacts", + { payload: { name: "John Doe", email: "john@example.com" } } +); + +// Request with path parameters +const response = await base44.integrations.custom.call( + "github", + "post:/repos/{owner}/{repo}/issues", + { + pathParams: { owner: "myorg", repo: "myrepo" }, + payload: { title: "Bug report", body: "Something is broken" } + } +); +``` + +### Response Structure + +```javascript +{ + success: true, // Whether external API returned 2xx + status_code: 200, // HTTP status code + data: { ... } // Response from external API +} +``` + +## Requirements + +- **Core integrations**: Available on all plans +- **Catalog/Custom integrations**: Require Builder plan or higher + +## Type Definitions + +### Core Integration Parameters + +```typescript +/** Parameters for the InvokeLLM function. */ +interface InvokeLLMParams { + /** The prompt text to send to the model. */ + prompt: string; + /** If true, uses Google Search/Maps/News for real-time context. */ + add_context_from_internet?: boolean; + /** JSON schema for structured output. If provided, returns object instead of string. */ + response_json_schema?: object; + /** File URLs (from UploadFile) to provide as context. */ + file_urls?: string[]; +} + +/** Parameters for the GenerateImage function. */ +interface GenerateImageParams { + /** Description of the image to generate. */ + prompt: string; +} + +/** Result from GenerateImage. */ +interface GenerateImageResult { + /** URL of the generated image. */ + url: string; +} + +/** Parameters for the UploadFile function. */ +interface UploadFileParams { + /** The file object to upload. */ + file: File; +} + +/** Result from UploadFile. */ +interface UploadFileResult { + /** URL of the uploaded file. */ + file_url: string; +} + +/** Parameters for the SendEmail function. */ +interface SendEmailParams { + /** Recipient email address. */ + to: string; + /** Email subject line. */ + subject: string; + /** Plain text or HTML email body. */ + body: string; + /** Sender name (defaults to app name). */ + from_name?: string; +} + +/** Parameters for ExtractDataFromUploadedFile. */ +interface ExtractDataFromUploadedFileParams { + /** URL of the uploaded file. */ + file_url: string; + /** JSON schema defining fields to extract. */ + json_schema: object; +} + +/** Parameters for UploadPrivateFile. */ +interface UploadPrivateFileParams { + /** The file object to upload. */ + file: File; +} + +/** Result from UploadPrivateFile. */ +interface UploadPrivateFileResult { + /** URI of the private file (used for signed URLs). */ + file_uri: string; +} + +/** Parameters for CreateFileSignedUrl. */ +interface CreateFileSignedUrlParams { + /** URI from UploadPrivateFile. */ + file_uri: string; + /** Expiration time in seconds (default: 300). */ + expires_in?: number; +} + +/** Result from CreateFileSignedUrl. */ +interface CreateFileSignedUrlResult { + /** Temporary signed URL to access the file. */ + signed_url: string; +} +``` + +### CoreIntegrations + +```typescript +/** Core package containing built-in Base44 integration functions. */ +interface CoreIntegrations { + InvokeLLM(params: InvokeLLMParams): Promise; + GenerateImage(params: GenerateImageParams): Promise; + UploadFile(params: UploadFileParams): Promise; + SendEmail(params: SendEmailParams): Promise; + ExtractDataFromUploadedFile(params: ExtractDataFromUploadedFileParams): Promise; + UploadPrivateFile(params: UploadPrivateFileParams): Promise; + CreateFileSignedUrl(params: CreateFileSignedUrlParams): Promise; +} +``` + +### Custom Integrations + +```typescript +/** Parameters for calling a custom integration endpoint. */ +interface CustomIntegrationCallParams { + /** Request body payload. */ + payload?: Record; + /** Path parameters to substitute in the URL. */ + pathParams?: Record; + /** Query string parameters. */ + queryParams?: Record; + /** Additional headers for this request. */ + headers?: Record; +} + +/** Response from a custom integration call. */ +interface CustomIntegrationCallResponse { + /** Whether the external API returned a 2xx status code. */ + success: boolean; + /** The HTTP status code from the external API. */ + status_code: number; + /** The response data from the external API. */ + data: any; +} + +/** Module for calling custom workspace-level API integrations. */ +interface CustomIntegrationsModule { + /** + * Call a custom integration endpoint. + * @param slug - The integration's unique identifier. + * @param operationId - The operation ID from the OpenAPI spec. + * @param params - Optional payload, pathParams, queryParams, headers. + */ + call(slug: string, operationId: string, params?: CustomIntegrationCallParams): Promise; +} +``` + +### IntegrationsModule + +```typescript +/** Integrations module for calling integration endpoints. */ +type IntegrationsModule = { + /** Core package with built-in integrations. */ + Core: CoreIntegrations; + /** Custom integrations module. */ + custom: CustomIntegrationsModule; + /** Additional integration packages (dynamic). */ + [packageName: string]: any; +}; +``` diff --git a/evals/fixtures/sdk-entity-crud/skills/base44-sdk/references/users.md b/evals/fixtures/sdk-entity-crud/skills/base44-sdk/references/users.md new file mode 100644 index 0000000..4205b06 --- /dev/null +++ b/evals/fixtures/sdk-entity-crud/skills/base44-sdk/references/users.md @@ -0,0 +1,82 @@ +# Users Module + +Invite users to the app via `base44.users`. + +## Contents +- [Methods](#methods) +- [Examples](#examples) (Invite User) +- [Roles](#roles) +- [Notes](#notes) + +## Methods + +| Method | Signature | Description | +|--------|-----------|-------------| +| `inviteUser(user_email, role)` | `Promise` | Invite a user to the app | + +## Examples + +### Invite User + +```javascript +// Invite a user with "user" role +await base44.users.inviteUser("newuser@example.com", "user"); + +// Invite an admin +await base44.users.inviteUser("admin@example.com", "admin"); +``` + +### Invite Multiple Users + +```javascript +const usersToInvite = [ + { email: "user1@example.com", role: "user" }, + { email: "user2@example.com", role: "user" }, + { email: "manager@example.com", role: "admin" } +]; + +for (const user of usersToInvite) { + await base44.users.inviteUser(user.email, user.role); + console.log(`Invited ${user.email} as ${user.role}`); +} +``` + +## Roles + +The `role` parameter must be one of: + +| Role | Description | +|------|-------------| +| `"user"` | Standard user with default permissions | +| `"admin"` | Administrator with elevated permissions | + +**Note:** Only `"user"` and `"admin"` are valid role values. An error will be thrown if you pass any other value. + +## Notes + +- **Email invitation**: The invited user receives an email with a link to join the app +- **Duplicate handling**: Inviting an existing user will re-send the invitation +- **Also available in auth**: `base44.auth.inviteUser()` provides the same functionality +- **Role validation**: Only `"user"` or `"admin"` are accepted + +```javascript +// These are equivalent: +await base44.users.inviteUser("newuser@example.com", "user"); +await base44.auth.inviteUser("newuser@example.com", "user"); +``` + +## Type Definitions + +```typescript +/** Users module for inviting users to the app. */ +interface UsersModule { + /** + * Invite a user to the application. + * @param user_email - User's email address. + * @param role - User's role ('user' or 'admin'). + * @returns Promise resolving when the invitation is sent. + * @throws Error if role is not 'user' or 'admin'. + */ + inviteUser(user_email: string, role: "user" | "admin"): Promise; +} +``` diff --git a/evals/fixtures/sdk-functions/eval.json b/evals/fixtures/sdk-functions/eval.json new file mode 100644 index 0000000..bff76fd --- /dev/null +++ b/evals/fixtures/sdk-functions/eval.json @@ -0,0 +1,119 @@ +{ + "$schema": "../../eval.schema.json", + "name": "sdk-functions", + "description": "Test SDK function invocation patterns", + "prompts": [ + { + "name": "sdk-functions-invoke", + "description": "Invoke a backend function from frontend", + "prompt": "Create a ProcessOrder component in src/components/ProcessOrder.jsx that invokes a backend function called 'process_order' using the Base44 SDK. Pass an orderId and action to the function. Display the result or any errors.", + "expectedSkills": [ + "base44-sdk" + ], + "checks": [ + { + "type": "file-exists", + "filePath": "src/components/ProcessOrder.jsx", + "description": "src/components/ProcessOrder.jsx exists" + }, + { + "type": "file-content", + "filePath": "src/components/ProcessOrder.jsx", + "pattern": "functions\\.invoke\\(", + "description": "Uses functions.invoke" + }, + { + "type": "file-content", + "filePath": "src/components/ProcessOrder.jsx", + "pattern": "process_order", + "description": "Matches expected pattern in ProcessOrder.jsx" + }, + { + "type": "file-content", + "filePath": "src/components/ProcessOrder.jsx", + "pattern": "import.*base44|from ['\"].*base44", + "description": "Imports base44 client" + }, + { + "type": "file-content-excluded", + "filePath": "src/components/ProcessOrder.jsx", + "pattern": "functions\\.call\\(", + "description": "Does not use hallucinated functions.call" + }, + { + "type": "file-content-excluded", + "filePath": "src/components/ProcessOrder.jsx", + "pattern": "httpsCallable", + "description": "Does not use hallucinated httpsCallable" + } + ] + }, + { + "name": "sdk-functions-backend", + "description": "Create a backend function with Deno.serve", + "prompt": "Create a backend function called 'calculate-shipping' that accepts an order_id and destination in the request body, looks up the order using service role access, and returns a shipping cost. Create both the function config (base44/functions/calculate-shipping/function.jsonc) and the implementation (base44/functions/calculate-shipping/index.ts).", + "expectedSkills": [ + "base44-cli" + ], + "checks": [ + { + "type": "file-exists", + "filePath": "base44/functions/calculate-shipping/function.jsonc", + "description": "base44/functions/calculate-shipping/function.jsonc exists" + }, + { + "type": "file-exists", + "filePath": "base44/functions/calculate-shipping/index.ts", + "description": "base44/functions/calculate-shipping/index.ts exists" + }, + { + "type": "valid-json", + "filePath": "base44/functions/calculate-shipping/function.jsonc", + "description": "base44/functions/calculate-shipping/function.jsonc is valid JSON" + }, + { + "type": "file-content", + "filePath": "base44/functions/calculate-shipping/index.ts", + "pattern": "Deno\\.serve", + "description": "Uses Deno.serve()" + }, + { + "type": "file-content", + "filePath": "base44/functions/calculate-shipping/index.ts", + "pattern": "createClientFromRequest", + "description": "Imports base44 client" + }, + { + "type": "file-content", + "filePath": "base44/functions/calculate-shipping/index.ts", + "pattern": "asServiceRole", + "description": "Uses asServiceRole for admin access" + }, + { + "type": "file-content", + "filePath": "base44/functions/calculate-shipping/index.ts", + "pattern": "await\\s+req\\.json\\(\\)", + "description": "Parses request body" + }, + { + "type": "file-content", + "filePath": "base44/functions/calculate-shipping/index.ts", + "pattern": "Response\\.json\\(", + "description": "Returns Response.json()" + }, + { + "type": "file-content", + "filePath": "base44/functions/calculate-shipping/index.ts", + "pattern": "npm:@base44/sdk|createClientFromRequest.*from.*@base44/sdk", + "description": "Imports base44 client" + }, + { + "type": "function-def", + "filePath": "base44/functions/calculate-shipping/function.jsonc", + "target": "file", + "description": "Function config validates (base44/functions/calculate-shipping/function.jsonc)" + } + ] + } + ] +} diff --git a/evals/fixtures/sdk-functions/project/.claude/settings.json b/evals/fixtures/sdk-functions/project/.claude/settings.json new file mode 100644 index 0000000..ab60505 --- /dev/null +++ b/evals/fixtures/sdk-functions/project/.claude/settings.json @@ -0,0 +1,6 @@ +{ + "permissions": { + "allow": ["Read", "Write", "Edit", "Bash", "Glob", "Grep"] + }, + "skills": ["./skills"] +} diff --git a/evals/fixtures/sdk-functions/project/AGENTS.md b/evals/fixtures/sdk-functions/project/AGENTS.md new file mode 100644 index 0000000..6c0f2be --- /dev/null +++ b/evals/fixtures/sdk-functions/project/AGENTS.md @@ -0,0 +1,25 @@ +IMPORTANT: Prefer retrieval-led reasoning over pre-training-led reasoning. Base44 is NOT Firebase, Supabase, or any other SDK you know. Every API name below is the ONLY correct form. Always verify against this index and the referenced docs. + +[Base44 SDK Docs Index]|root: ./skills/base44-sdk/references +|entities.md: CRUD - create(data), list(), filter(query), get(id), update(id,data), delete(id), subscribe(cb) +|auth.md: Auth - loginViaEmailPassword(), loginWithProvider('google'), me(), register(), logout() +|base44-agents.md: Agents - createConversation({agent_name}), addMessage(conv,{role,content}), subscribeToConversation(id,cb) +|functions.md: Functions - invoke(name, data) +|integrations.md: Core - InvokeLLM({prompt}), SendEmail({to,subject,body}), UploadFile({file}) +|client.md: Client - createClient({appId}), import from "@base44/sdk" +|QUICK_REFERENCE.md: All method signatures + +[Base44 CLI Docs Index]|root: ./skills/base44-cli/references +|entities-create.md: Entity schema - {name:"PascalCase", type:"object", properties:{}, required:[]} +|functions-create.md: Function - function.jsonc {name,entry} + index.ts with Deno.serve + createClientFromRequest +|create.md: Project creation - npx base44 create name -p . +|deploy.md: Deploy - npx base44 deploy -y +|agents-push.md: Agent config - {name:"snake_case", description, instructions, tool_configs:[{entity_name,allowed_operations}|{function_name,description}]} + +[Common Mistakes - NEVER use these hallucinated names] +|SDK: Do NOT use find() → use filter(), findOne() → get(), insert() → create(), remove() → delete(), onChange() → subscribe() +|SDK: Do NOT use signInWithGoogle() → use loginWithProvider('google'), functions.call() → functions.invoke() +|SDK: Do NOT use ai.generate() → use integrations.Core.InvokeLLM({prompt}) +|CLI: Entity names must be PascalCase alphanumeric. File names must be kebab-case.jsonc +|CLI: Agent names must be lowercase with underscores only. Function entry field is "entry" not "entrypoint" +|CLI: Backend functions import from "npm:@base44/sdk" (Deno requires npm: prefix) diff --git a/evals/fixtures/sdk-functions/project/CLAUDE.md b/evals/fixtures/sdk-functions/project/CLAUDE.md new file mode 100644 index 0000000..724ce4d --- /dev/null +++ b/evals/fixtures/sdk-functions/project/CLAUDE.md @@ -0,0 +1,7 @@ +# Base44 Development Guidelines + +When working with Base44 projects, first explore the existing project structure to understand what already exists. Look at existing code, config files, and patterns in use. + +Then consult the AGENTS.md index in the project root for correct API patterns. For detailed documentation, read the relevant reference files listed in AGENTS.md. + +IMPORTANT: Prefer retrieval-led reasoning over pre-training-led reasoning. Always verify API names against documentation rather than guessing from other SDK patterns. diff --git a/evals/fixtures/sdk-functions/project/base44/config.jsonc b/evals/fixtures/sdk-functions/project/base44/config.jsonc new file mode 100644 index 0000000..f4c5d58 --- /dev/null +++ b/evals/fixtures/sdk-functions/project/base44/config.jsonc @@ -0,0 +1,10 @@ +{ + "name": "Functions Test App", + "description": "A test application for SDK function invocation", + "entitiesDir": "./entities", + "functionsDir": "./functions", + "site": { + "buildCommand": "npm run build", + "outputDirectory": "dist" + } +} diff --git a/evals/fixtures/sdk-functions/project/package.json b/evals/fixtures/sdk-functions/project/package.json new file mode 100644 index 0000000..e4a46bc --- /dev/null +++ b/evals/fixtures/sdk-functions/project/package.json @@ -0,0 +1,20 @@ +{ + "name": "functions-test-app", + "version": "0.1.0", + "private": true, + "type": "module", + "scripts": { + "dev": "vite", + "build": "vite build" + }, + "dependencies": { + "@base44/sdk": "^1.0.0", + "react": "^18.2.0", + "react-dom": "^18.2.0" + }, + "devDependencies": { + "@vitejs/plugin-react": "^4.2.1", + "base44": "latest", + "vite": "^5.2.0" + } +} diff --git a/evals/fixtures/sdk-functions/project/src/App.jsx b/evals/fixtures/sdk-functions/project/src/App.jsx new file mode 100644 index 0000000..5553fc1 --- /dev/null +++ b/evals/fixtures/sdk-functions/project/src/App.jsx @@ -0,0 +1,11 @@ +import React from 'react'; + +function App() { + return ( +
+

Auth Test App

+
+ ); +} + +export default App; diff --git a/evals/fixtures/sdk-functions/project/src/api/base44Client.js b/evals/fixtures/sdk-functions/project/src/api/base44Client.js new file mode 100644 index 0000000..7887147 --- /dev/null +++ b/evals/fixtures/sdk-functions/project/src/api/base44Client.js @@ -0,0 +1,5 @@ +import { createClient } from "@base44/sdk"; + +const base44 = createClient({ appId: "test-app" }); + +export default base44; diff --git a/evals/fixtures/sdk-functions/skills/base44-cli/SKILL.md b/evals/fixtures/sdk-functions/skills/base44-cli/SKILL.md new file mode 100644 index 0000000..3c33f05 --- /dev/null +++ b/evals/fixtures/sdk-functions/skills/base44-cli/SKILL.md @@ -0,0 +1,384 @@ +--- +name: base44-cli +description: "The base44 CLI is used for EVERYTHING related to base44 projects: resource configuration (entities, backend functions, ai agents), initialization and actions (resource creation, deployment). This skill is the place for learning about how to configure resources. When you plan or implement a feature, you must learn this skill" +--- + +# Base44 CLI + +Create and manage Base44 apps (projects) using the Base44 CLI tool. + +## ⚡ IMMEDIATE ACTION REQUIRED - Read This First + +This skill activates on ANY mention of "base44" or when a `base44/` folder exists. **DO NOT read documentation files or search the web before acting.** + +**Your first action MUST be:** +1. Check if `base44/config.jsonc` exists in the current directory +2. If **NO** (new project scenario): + - This skill (base44-cli) handles the request + - Guide user through project initialization + - Do NOT activate base44-sdk yet +3. If **YES** (existing project scenario): + - Transfer to base44-sdk skill for implementation + - This skill only handles CLI commands (login, deploy, entities push) + +## Critical: Local Installation Only + +NEVER call `base44` directly. The CLI is installed locally as a dev dependency and must be accessed via a package manager: + +- `npx base44 ` (npm - recommended) +- `yarn base44 ` (yarn) +- `pnpm base44 ` (pnpm) + +WRONG: `base44 login` +RIGHT: `npx base44 login` + +## MANDATORY: Authentication Check at Session Start + +**CRITICAL**: At the very start of every AI session when this skill is activated, you MUST: + +1. **Check authentication status** by running: + ```bash + npx base44 whoami + ``` + +2. **If the user is logged in** (command succeeds and shows an email): + - Continue with the requested task + +3. **If the user is NOT logged in** (command fails or shows an error): + - **STOP immediately** + - **DO NOT proceed** with any CLI operations + - **Ask the user to login manually** by running: + ```bash + npx base44 login + ``` + - Wait for the user to confirm they have logged in before continuing + +**This check is mandatory and must happen before executing any other Base44 CLI commands.** + +## Overview + +The Base44 CLI provides command-line tools for authentication, creating projects, managing entities, and deploying Base44 applications. It is framework-agnostic and works with popular frontend frameworks like Vite, Next.js, and Create React App, Svelte, Vue, and more. + +## When to Use This Skill vs base44-sdk + +**Use base44-cli when:** +- Creating a **NEW** Base44 project from scratch +- Initializing a project in an empty directory +- Directory is missing `base44/config.jsonc` +- User mentions: "create a new project", "initialize project", "setup a project", "start a new Base44 app" +- Deploying, pushing entities, or authenticating via CLI +- Working with CLI commands (`npx base44 ...`) + +**Use base44-sdk when:** +- Building features in an **EXISTING** Base44 project +- `base44/config.jsonc` already exists +- Writing JavaScript/TypeScript code using Base44 SDK +- Implementing functionality, components, or features +- User mentions: "implement", "build a feature", "add functionality", "write code" + +**Skill Dependencies:** +- `base44-cli` is a **prerequisite** for `base44-sdk` in new projects +- If user wants to "create an app" and no Base44 project exists, use `base44-cli` first +- `base44-sdk` assumes a Base44 project is already initialized + +**State Check Logic:** +Before selecting a skill, check: +- IF (user mentions "create/build app" OR "make a project"): + - IF (directory is empty OR no `base44/config.jsonc` exists): + → Use **base44-cli** (project initialization needed) + - ELSE: + → Use **base44-sdk** (project exists, build features) + +## Project Structure + +A Base44 project combines a standard frontend project with a `base44/` configuration folder: + +``` +my-app/ +├── base44/ # Base44 configuration (created by CLI) +│ ├── config.jsonc # Project settings, site config +│ ├── entities/ # Entity schema definitions +│ │ ├── task.jsonc +│ │ └── board.jsonc +│ ├── functions/ # Backend functions (optional) +│ │ └── my-function/ +│ │ ├── function.jsonc +│ │ └── index.ts +│ └── agents/ # Agent configurations (optional) +│ └── support_agent.jsonc +├── src/ # Frontend source code +│ ├── api/ +│ │ └── base44Client.js # Base44 SDK client +│ ├── pages/ +│ ├── components/ +│ └── main.jsx +├── index.html # SPA entry point +├── package.json +└── vite.config.js # Or your framework's config +``` + +**Key files:** +- `base44/config.jsonc` - Project name, description, site build settings +- `base44/entities/*.jsonc` - Data model schemas (see Entity Schema section) +- `base44/agents/*.jsonc` - Agent configurations (optional) +- `src/api/base44Client.js` - Pre-configured SDK client for frontend use + +**config.jsonc example:** +```jsonc +{ + "name": "My App", // Required: project name + "description": "App description", // Optional: project description + "entitiesDir": "./entities", // Optional: default "entities" + "functionsDir": "./functions", // Optional: default "functions" + "agentsDir": "./agents", // Optional: default "agents" + "site": { // Optional: site deployment config + "installCommand": "npm install", // Optional: install dependencies + "buildCommand": "npm run build", // Optional: build command + "serveCommand": "npm run dev", // Optional: local dev server + "outputDirectory": "./dist" // Optional: build output directory + } +} +``` + +**Config properties:** + +| Property | Description | Default | +|----------|-------------|---------| +| `name` | Project name (required) | - | +| `description` | Project description | - | +| `entitiesDir` | Directory for entity schemas | `"entities"` | +| `functionsDir` | Directory for backend functions | `"functions"` | +| `agentsDir` | Directory for agent configs | `"agents"` | +| `site.installCommand` | Command to install dependencies | - | +| `site.buildCommand` | Command to build the project | - | +| `site.serveCommand` | Command to run dev server | - | +| `site.outputDirectory` | Build output directory for deployment | - | + +## Installation + +Install the Base44 CLI as a dev dependency in your project: + +```bash +npm install --save-dev base44 +``` + +**Important:** Never assume or hardcode the `base44` package version. Always install without a version specifier to get the latest version. + +Then run commands using `npx`: + +```bash +npx base44 +``` + +**Note:** All commands in this documentation use `npx base44`. You can also use `yarn base44`, or `pnpm base44` if preferred. + +## Available Commands + +### Authentication + +| Command | Description | Reference | +| --------------- | ----------------------------------------------- | ------------------------------------------- | +| `base44 login` | Authenticate with Base44 using device code flow | [auth-login.md](references/auth-login.md) | +| `base44 logout` | Logout from current device | [auth-logout.md](references/auth-logout.md) | +| `base44 whoami` | Display current authenticated user | [auth-whoami.md](references/auth-whoami.md) | + +### Project Management + +| Command | Description | Reference | +|---------|-------------|-----------| +| `base44 create` | Create a new Base44 project from a template | [create.md](references/create.md) ⚠️ **MUST READ** | +| `base44 link` | Link an existing local project to Base44 | [link.md](references/link.md) | +| `base44 dashboard` | Open the app dashboard in your browser | [dashboard.md](references/dashboard.md) | + +### Deployment + +| Command | Description | Reference | +|---------|-------------|-----------| +| `base44 deploy` | Deploy all resources (entities, functions, site) | [deploy.md](references/deploy.md) | + +### Entity Management + +| Action / Command | Description | Reference | +| ---------------------- | ------------------------------------------- | --------------------------------------------------- | +| Create Entities | Define entities in `base44/entities` folder | [entities-create.md](references/entities-create.md) | +| `base44 entities push` | Push local entities to Base44 | [entities-push.md](references/entities-push.md) | + +#### Entity Schema (Quick Reference) + +ALWAYS follow this exact structure when creating entity files: + +**File naming:** `base44/entities/{kebab-case-name}.jsonc` (e.g., `team-member.jsonc` for `TeamMember`) + +**Schema template:** +```jsonc +{ + "name": "EntityName", + "type": "object", + "properties": { + "field_name": { + "type": "string", + "description": "Field description" + } + }, + "required": ["field_name"] +} +``` + +**Field types:** `string`, `number`, `integer`, `boolean`, `array`, `object`, `binary` +**String formats:** `date`, `date-time`, `time`, `email`, `uri`, `hostname`, `ipv4`, `ipv6`, `uuid`, `file`, `regex`, `richtext` +**For enums:** Add `"enum": ["value1", "value2"]` and optionally `"default": "value1"` +**Entity names:** Must be alphanumeric only (pattern: `/^[a-zA-Z0-9]+$/`) + +For complete documentation, see [entities-create.md](references/entities-create.md). + +### Function Management + +| Action / Command | Description | Reference | +| ------------------------- | --------------------------------------------- | ------------------------------------------------------- | +| Create Functions | Define functions in `base44/functions` folder | [functions-create.md](references/functions-create.md) | +| `base44 functions deploy` | Deploy local functions to Base44 | [functions-deploy.md](references/functions-deploy.md) | + +### Agent Management + +Agents are conversational AI assistants that can interact with users, access your app's entities, and call backend functions. Use these commands to manage agent configurations. + +| Action / Command | Description | Reference | +| ----------------------- | --------------------------------------- | ----------------------------------------------- | +| Create Agents | Define agents in `base44/agents` folder | See Agent Schema below | +| `base44 agents pull` | Pull remote agents to local files | [agents-pull.md](references/agents-pull.md) | +| `base44 agents push` | Push local agents to Base44 | [agents-push.md](references/agents-push.md) | + +**Note:** Agent commands perform full synchronization - pushing replaces all remote agents with local ones, and pulling replaces all local agents with remote ones. + +#### Agent Schema (Quick Reference) + +**File naming:** `base44/agents/{agent_name}.jsonc` (e.g., `support_agent.jsonc`) + +**Schema template:** +```jsonc +{ + "name": "agent_name", + "description": "Brief description of what this agent does", + "instructions": "Detailed instructions for the agent's behavior", + "tool_configs": [ + // Entity tool - gives agent access to entity operations + { "entity_name": "tasks", "allowed_operations": ["read", "create", "update", "delete"] }, + // Backend function tool - gives agent access to a function + { "function_name": "send_email", "description": "Send an email notification" } + ], + "whatsapp_greeting": "Hello! How can I help you today?" +} +``` + +**Naming rules:** +- Agent names must match pattern: `/^[a-z0-9_]+$/` (lowercase alphanumeric with underscores, 1-100 chars) +- Valid: `support_agent`, `order_bot` +- Invalid: `Support-Agent`, `OrderBot` + +**Required fields:** `name`, `description`, `instructions` +**Optional fields:** `tool_configs` (defaults to `[]`), `whatsapp_greeting` + +**Tool config types:** +- **Entity tools**: `entity_name` + `allowed_operations` (array of: `read`, `create`, `update`, `delete`) +- **Backend function tools**: `function_name` + `description` + +### Site Deployment + +| Command | Description | Reference | +| -------------------- | ----------------------------------------- | ------------------------------------------- | +| `base44 site deploy` | Deploy built site files to Base44 hosting | [site-deploy.md](references/site-deploy.md) | + +**SPA only**: Base44 hosting supports Single Page Applications with a single `index.html` entry point. All routes are served from `index.html` (client-side routing). + +## Quick Start + +1. Install the CLI in your project: + ```bash + npm install --save-dev base44 + ``` + +2. Authenticate with Base44: + ```bash + npx base44 login + ``` + +3. Create a new project (ALWAYS provide name and `--path` flag): + ```bash + npx base44 create my-app -p . + ``` + +4. Build and deploy everything: + ```bash + npm run build + npx base44 deploy -y + ``` + +Or deploy individual resources: +- `npx base44 entities push` - Push entities only +- `npx base44 functions deploy` - Deploy functions only +- `npx base44 agents push` - Push agents only +- `npx base44 site deploy -y` - Deploy site only + +## Common Workflows + +### Creating a New Project + +**⚠️ MANDATORY: Before running `base44 create`, you MUST read [create.md](references/create.md) for:** +- **Template selection** - Choose the correct template (`backend-and-client` vs `backend-only`) +- **Correct workflow** - Different templates require different setup steps +- **Common pitfalls** - Avoid folder creation errors that cause failures + +Failure to follow the create.md instructions will result in broken project scaffolding. + +### Linking an Existing Project +```bash +# If you have base44/config.jsonc but no .app.jsonc +npx base44 link --create --name my-app +``` + +### Deploying All Changes +```bash +# Build your project first +npm run build + +# Deploy everything (entities, functions, and site) +npx base44 deploy -y +``` + +### Deploying Individual Resources +```bash +# Push only entities +npx base44 entities push + +# Deploy only functions +npx base44 functions deploy + +# Push only agents +npx base44 agents push + +# Deploy only site +npx base44 site deploy -y +``` + +### Opening the Dashboard +```bash +# Open app dashboard in browser +npx base44 dashboard +``` + +## Authentication + +Most commands require authentication. If you're not logged in, the CLI will automatically prompt you to login. Your session is stored locally and persists across CLI sessions. + +## Troubleshooting + +| Error | Solution | +| --------------------------- | ----------------------------------------------------------------------------------- | +| Not authenticated | Run `npx base44 login` first | +| No entities found | Ensure entities exist in `base44/entities/` directory | +| Entity not recognized | Ensure file uses kebab-case naming (e.g., `team-member.jsonc` not `TeamMember.jsonc`) | +| No functions found | Ensure functions exist in `base44/functions/` with valid `function.jsonc` configs | +| No agents found | Ensure agents exist in `base44/agents/` directory with valid `.jsonc` configs | +| Invalid agent name | Agent names must be lowercase alphanumeric with underscores only | +| No site configuration found | Check that `site.outputDirectory` is configured in project config | +| Site deployment fails | Ensure you ran `npm run build` first and the build succeeded | diff --git a/evals/fixtures/sdk-functions/skills/base44-cli/references/agents-pull.md b/evals/fixtures/sdk-functions/skills/base44-cli/references/agents-pull.md new file mode 100644 index 0000000..6a700f0 --- /dev/null +++ b/evals/fixtures/sdk-functions/skills/base44-cli/references/agents-pull.md @@ -0,0 +1,73 @@ +# base44 agents pull + +Pull AI agent configurations from Base44 to local files. Agents are conversational AI assistants that can interact with users, access your app's entities, and call backend functions. + +## Syntax + +```bash +npx base44 agents pull +``` + +## Authentication + +**Required**: Yes. If not authenticated, you'll be prompted to login first. + +## What It Does + +1. Fetches all agents from Base44 +2. Writes agent files to the `base44/agents/` directory +3. Deletes local agent files that don't exist remotely +4. Reports written and deleted agents + +## Prerequisites + +- Must be run from a Base44 project directory +- Project must be linked to a Base44 app + +## Output + +```bash +$ npx base44 agents pull + +Fetching agents from Base44... +✓ Agents fetched successfully + +Writing agent files... +✓ Agent files written successfully + +Written: support_agent, order_bot +Deleted: old_agent + +Pulled 2 agents to base44/agents +``` + +## Agent Synchronization + +The pull operation synchronizes remote agents to your local files: + +- **Written**: Agent files created or updated from remote +- **Deleted**: Local agent files removed (didn't exist remotely) + +**Warning**: This operation replaces all local agent configurations with remote versions. Any local changes not pushed to Base44 will be overwritten. + +## Error Handling + +If no agents exist on Base44: +```bash +$ npx base44 agents pull +No agents found on Base44 +``` + +## Use Cases + +- Sync agent configurations to a new development machine +- Get the latest agent configurations from your team +- Restore local agent files after accidental deletion +- Start working on an existing project with agents + +## Notes + +- This command syncs agent configurations, not conversation data +- Agent files are stored as `.jsonc` in the `base44/agents/` directory +- The directory location is configurable via `agentsDir` in `config.jsonc` +- Use `base44 agents push` to upload local changes to Base44 diff --git a/evals/fixtures/sdk-functions/skills/base44-cli/references/agents-push.md b/evals/fixtures/sdk-functions/skills/base44-cli/references/agents-push.md new file mode 100644 index 0000000..fa2795b --- /dev/null +++ b/evals/fixtures/sdk-functions/skills/base44-cli/references/agents-push.md @@ -0,0 +1,156 @@ +# base44 agents push + +Push local AI agent configurations to Base44. Agents are conversational AI assistants that can interact with users, access your app's entities, and call backend functions. + +## Syntax + +```bash +npx base44 agents push +``` + +## Authentication + +**Required**: Yes. If not authenticated, you'll be prompted to login first. + +## What It Does + +1. Reads all agent files from the `base44/agents/` directory +2. Validates agent configurations +3. Displays the count of agents to be pushed +4. Uploads agents to the Base44 backend +5. Reports the results: created, updated, and deleted agents + +## Prerequisites + +- Must be run from a Base44 project directory +- Project must have agent definitions in the `base44/agents/` folder + +## Output + +```bash +$ npx base44 agents push + +Found 2 agents to push +Pushing agents to Base44... + +Created: support_agent +Updated: order_bot +Deleted: old_agent + +✓ Agents pushed to Base44 +``` + +## Agent Synchronization + +The push operation synchronizes your local agents with Base44: + +- **Created**: New agents that didn't exist in Base44 +- **Updated**: Existing agents with modified configuration +- **Deleted**: Agents that were removed from your local configuration + +**Warning**: This is a full sync operation. Agents removed locally will be deleted from Base44. + +## Error Handling + +If no agents are found in your project: +```bash +$ npx base44 agents push +No local agents found - this will delete all remote agents +``` + +If an agent has an invalid name: +```bash +$ npx base44 agents push +Error: Agent name must be lowercase alphanumeric with underscores +``` + +## Agent Configuration Schema + +Each agent file should be a `.jsonc` file in `base44/agents/` with this structure: + +```jsonc +{ + "name": "agent_name", // Required: lowercase alphanumeric with underscores, 1-100 chars + "description": "Brief description of what this agent does", // Required: min 1 char + "instructions": "Detailed instructions for the agent's behavior", // Required: min 1 char + "tool_configs": [ // Optional: defaults to [] + // Entity tool - gives agent access to entity operations + { "entity_name": "Task", "allowed_operations": ["read", "create", "update", "delete"] }, + // Backend function tool - gives agent access to a function + { "function_name": "send_email", "description": "Send an email notification" } + ], + "whatsapp_greeting": "Hello! How can I help you today?" // Optional +} +``` + +**Naming rules:** +- **Agent names** must match pattern: `/^[a-z0-9_]+$/` (lowercase alphanumeric with underscores only, 1-100 characters) + - Valid: `support_agent`, `order_bot`, `task_helper` + - Invalid: `Support-Agent`, `OrderBot`, `task helper` +- **Agent file names** must use underscores (matching the agent name) + - Valid: `support_agent.jsonc`, `order_bot.jsonc` + - Invalid: `support-agent.jsonc` (hyphens not allowed) +- **Entity names in `tool_configs`** must use PascalCase (matching the entity's `name` field) + - Valid: `"entity_name": "Task"`, `"entity_name": "TeamMember"` + - Invalid: `"entity_name": "task"`, `"entity_name": "team_member"` + +**Required fields:** +- `name`: Required, must follow naming rules above +- `description`: Required, minimum 1 character +- `instructions`: Required, minimum 1 character +- `tool_configs`: Optional, defaults to empty array +- `whatsapp_greeting`: Optional + +### Common Mistake: Wrong tool_configs Format + +**WRONG** - Do NOT use `tools` with `type` and `entity`: +```jsonc +{ + "name": "my_agent", + "tools": [ // ❌ WRONG + { "type": "entity_query", "entity": "Task" } + ] +} +``` + +**CORRECT** - Use `tool_configs` with `entity_name` and `allowed_operations`: +```jsonc +{ + "name": "my_agent", + "tool_configs": [ // ✅ CORRECT + { "entity_name": "Task", "allowed_operations": ["read"] } + ] +} +``` + +### Best Practices for Agent Instructions + +When giving agents access to entities, be explicit in the instructions about using the tools: + +```jsonc +{ + "name": "support_agent", + "instructions": "You are a helpful support agent.\n\nIMPORTANT: You have access to customer data through entity tools. When users ask about their orders or account:\n1. ALWAYS use the Order entity tool to query their order history\n2. Use the Customer entity tool to look up account details\n3. Analyze the data and provide personalized responses\n\nAlways query the relevant entities first before answering questions about user data.", + "tool_configs": [ + { "entity_name": "Order", "allowed_operations": ["read"] }, + { "entity_name": "Customer", "allowed_operations": ["read"] } + ] +} +``` + +Without explicit instructions to use the entity tools, the agent may not proactively query user data when asked. + +## Use Cases + +- After defining new agents in your project +- When modifying existing agent configurations +- To sync agent changes before testing +- As part of your development workflow when agent behavior changes + +## Notes + +- This command syncs the agent configuration, not conversation data +- Changes are applied to your Base44 project immediately +- Make sure to test agent changes in a development environment first +- Agent definitions are located in the `base44/agents/` directory +- Use `base44 agents pull` to download agents from Base44 diff --git a/evals/fixtures/sdk-functions/skills/base44-cli/references/auth-login.md b/evals/fixtures/sdk-functions/skills/base44-cli/references/auth-login.md new file mode 100644 index 0000000..adebd27 --- /dev/null +++ b/evals/fixtures/sdk-functions/skills/base44-cli/references/auth-login.md @@ -0,0 +1,54 @@ +# base44 login + +Authenticate with Base44 using device code flow. + +## Syntax + +```bash +npx base44 login +``` + +## Authentication + +**Required**: No (this is the login command itself) + +## How It Works + +The login command uses OAuth 2.0 device code flow for authentication: + +1. Generates a device code for authentication +2. Displays a verification code and verification URI +3. Directs you to visit the URI and enter the code +4. Polls for authentication completion (up to device code expiration) +5. Retrieves access and refresh tokens upon successful authentication +6. Fetches and displays your user information +7. Saves authentication data locally with expiration timestamp + +## Interactive Flow + +```bash +$ npx base44 login + +Please visit: https://auth.base44.com/device +Enter code: ABCD-EFGH + +Waiting for authentication... +✓ Successfully authenticated! + +Logged in as: user@example.com +``` + +## Session Management + +- Authentication tokens are stored locally on your device +- Tokens include expiration timestamps +- The session persists across CLI sessions +- Other commands will automatically use your stored credentials +- Use `npx base44 logout` to clear your session +- Use `npx base44 whoami` to check your current authentication status + +## Notes + +- You only need to login once per device +- If your session expires, you'll be prompted to login again when running authenticated commands +- The CLI automatically prompts for login when you run commands that require authentication diff --git a/evals/fixtures/sdk-functions/skills/base44-cli/references/auth-logout.md b/evals/fixtures/sdk-functions/skills/base44-cli/references/auth-logout.md new file mode 100644 index 0000000..33c2ddf --- /dev/null +++ b/evals/fixtures/sdk-functions/skills/base44-cli/references/auth-logout.md @@ -0,0 +1,32 @@ +# base44 logout + +Logout from current device and clear stored authentication data. + +## Syntax + +```bash +npx base44 logout +``` + +## Authentication + +**Required**: No + +## What It Does + +- Deletes stored authentication data from your device +- Clears your local session +- Removes access and refresh tokens + +## Output + +```bash +$ npx base44 logout +Logged out successfully +``` + +## Notes + +- You can logout even if you're not currently logged in (no error) +- After logout, you'll need to run `npx base44 login` again to use authenticated commands +- This only affects the current device; your Base44 account remains active diff --git a/evals/fixtures/sdk-functions/skills/base44-cli/references/auth-whoami.md b/evals/fixtures/sdk-functions/skills/base44-cli/references/auth-whoami.md new file mode 100644 index 0000000..abe450c --- /dev/null +++ b/evals/fixtures/sdk-functions/skills/base44-cli/references/auth-whoami.md @@ -0,0 +1,37 @@ +# base44 whoami + +Display the currently authenticated user. + +## Syntax + +```bash +npx base44 whoami +``` + +## Authentication + +**Required**: Yes. If not authenticated, you'll be prompted to login first. + +## What It Does + +- Reads stored authentication data +- Displays the email of the currently logged-in user + +## Output + +```bash +$ npx base44 whoami +Logged in as: user@example.com +``` + +## Use Cases + +- Verify you're logged in before running other commands +- Check which account you're currently using +- Confirm authentication is working properly +- Useful in scripts or automation to verify credentials + +## Notes + +- If you're not logged in, the command will prompt you to authenticate first +- The email displayed matches your Base44 account email diff --git a/evals/fixtures/sdk-functions/skills/base44-cli/references/create.md b/evals/fixtures/sdk-functions/skills/base44-cli/references/create.md new file mode 100644 index 0000000..7580645 --- /dev/null +++ b/evals/fixtures/sdk-functions/skills/base44-cli/references/create.md @@ -0,0 +1,111 @@ +# base44 create + +Creates a new Base44 project from a template. This command is framework-agnostic and can either scaffold a complete project or add Base44 configuration to an existing project. + +## Critical: Non-Interactive Mode Required + +ALWAYS provide both the project name AND `--path` flag. Without both, the command opens an interactive TUI which agents cannot use properly. + +WRONG: `npx base44 create` +WRONG: `npx base44 create my-app` +RIGHT: `npx base44 create my-app -p ./my-app` + +## Syntax + +```bash +npx base44 create [name] --path [options] +``` + +## Arguments & Options + +| Argument/Option | Description | Required | +|--------|-------------|----------| +| `name` | Project name (positional argument) | Yes* | +| `-p, --path ` | Path where to create the project | Yes* | +| `-t, --template ` | Template ID (see templates below) | No | +| `--deploy` | Build and deploy the site (includes pushing entities) | No | +| `--no-skills` | Skip AI agent skills installation (skills are added by default) | No | + +*Required for non-interactive mode. Both `name` and `--path` must be provided together. + +## Template Selection (CRITICAL - Choose Appropriately) + +**You MUST select the most appropriate template based on user requirements:** + +| Template ID | When to Use | Example Scenarios | +|-------------|-------------|-------------------| +| `backend-and-client` | Creating a NEW full-stack web app from scratch | "Create a task app", "Build me a dashboard", "Make a SaaS app" | +| `backend-only` | Adding Base44 to an EXISTING project OR using a different framework (Next.js, Vue, Svelte, etc.) | "Add Base44 to my project", "I want to use Next.js", "I already have a frontend" | + +**Default Choice:** When the user asks to "create an app" or "build a project" without specifying a particular framework, use `backend-and-client` to provide a complete, production-ready application with Vite + React + Tailwind. + +## The `--path` Flag + +- **For `backend-and-client` template (new projects):** Use a new subfolder path + ```bash + npx base44 create my-app -p ./my-app -t backend-and-client + ``` +- **For `backend-only` template (existing projects):** Use `-p .` in the current directory + ```bash + npx base44 create my-app -p . + ``` + +## Workflow: Using `backend-only` with External Frameworks + +**CRITICAL: The project folder MUST exist BEFORE running `base44 create` with `backend-only`** + +The `backend-only` template only adds Base44 configuration files - it does NOT create a frontend. If you need a frontend with a specific framework: + +```bash +# Step 1: Initialize the frontend project FIRST +npm create vite@latest my-app -- --template react # or vue, svelte, etc. +# OR: npx create-next-app@latest my-app +# OR: any other framework's init command + +# Step 2: Navigate into the created folder +cd my-app + +# Step 3: Install Base44 CLI +npm install --save-dev base44 + +# Step 4: Add Base44 configuration +npx base44 create my-app -p . +``` + +**WARNING:** Do NOT: +- Create an empty folder manually, then try to run `npx create vite` inside it (will fail - folder exists) +- Run `base44 create` with `backend-only` expecting it to create a frontend (it won't) + +**DO:** +- Run the external framework's init command FIRST (it creates its own folder) +- Then run `base44 create` inside that folder with `-p .` + +## Examples + +```bash +# RECOMMENDED: Create full-stack project (for new apps) +npx base44 create my-app -p ./my-app -t backend-and-client + +# Create full-stack and deploy in one step +npx base44 create my-app -p ./my-app -t backend-and-client --deploy + +# Add Base44 to EXISTING project (must be inside the project folder) +npx base44 create my-app -p . + +# Add Base44 to existing project and deploy +npx base44 create my-app -p . --deploy + +# Create without adding AI agent skills +npx base44 create my-app -p . --no-skills +``` + +## What It Does + +1. Applies the selected template to the target path +2. Creates a `base44/` folder with configuration files +3. Registers the project with Base44 backend +4. Creates `base44/.app.jsonc` with the app ID +5. If `--deploy` is used: + - Pushes any entities defined in `base44/entities/` + - Runs install and build commands (for templates with frontend) + - Deploys the site to Base44 hosting diff --git a/evals/fixtures/sdk-functions/skills/base44-cli/references/dashboard.md b/evals/fixtures/sdk-functions/skills/base44-cli/references/dashboard.md new file mode 100644 index 0000000..95a534a --- /dev/null +++ b/evals/fixtures/sdk-functions/skills/base44-cli/references/dashboard.md @@ -0,0 +1,35 @@ +# base44 dashboard + +Opens the Base44 app dashboard in your default web browser. + +## Syntax + +```bash +npx base44 dashboard +``` + +## Options + +This command has no options. + +## What It Does + +1. Reads the project's app ID from `base44/.app.jsonc` +2. Opens the dashboard URL in your default browser + +## Example + +```bash +# Open dashboard for current project +npx base44 dashboard +``` + +## Requirements + +- Must be run from a linked Base44 project directory (contains `base44/.app.jsonc`) +- Must be authenticated (run `npx base44 login` first) + +## Notes + +- The dashboard provides a web interface to manage your app's entities, functions, users, and settings +- If you're not in a project directory, the command will fail with an error diff --git a/evals/fixtures/sdk-functions/skills/base44-cli/references/deploy.md b/evals/fixtures/sdk-functions/skills/base44-cli/references/deploy.md new file mode 100644 index 0000000..500526f --- /dev/null +++ b/evals/fixtures/sdk-functions/skills/base44-cli/references/deploy.md @@ -0,0 +1,86 @@ +# base44 deploy + +Deploys all project resources (entities, functions, and site) to Base44 in a single command. + +## Syntax + +```bash +npx base44 deploy [options] +``` + +## Options + +| Option | Description | +|--------|-------------| +| `-y, --yes` | Skip confirmation prompt | + +## What It Deploys + +The command automatically detects and deploys: + +1. **Entities** - All `.jsonc` files in `base44/entities/` +2. **Functions** - All functions in `base44/functions/` +3. **Agents** - All agent configurations in `base44/agents/` +4. **Site** - Built files from `site.outputDirectory` (if configured) + +## Examples + +```bash +# Interactive mode - shows what will be deployed and asks for confirmation +npx base44 deploy + +# Non-interactive - skip confirmation (for CI/CD or agent use) +npx base44 deploy -y +``` + +## Typical Workflow + +```bash +# 1. Make your changes (entities, functions, frontend code) + +# 2. Build the frontend (if you have one) +npm run build + +# 3. Deploy everything +npx base44 deploy -y +``` + +## What It Does + +1. Reads project configuration from `base44/config.jsonc` +2. Detects available resources (entities, functions, site) +3. Shows a summary of what will be deployed +4. Asks for confirmation (unless `-y` flag is used) +5. Deploys all resources in sequence: + - Pushes entity schemas + - Deploys functions + - Pushes agent configurations + - Uploads site files +6. Displays the dashboard URL and app URL (if site was deployed) + +## Requirements + +- Must be run from a linked Base44 project directory +- Must be authenticated (run `npx base44 login` first) +- For site deployment, must run `npm run build` first + +## Output + +After successful deployment: +- **Dashboard**: Link to your app's management dashboard +- **App URL**: Your deployed site's public URL (if site was included) + +## Notes + +- If no resources are found, the command exits with a message +- Use individual commands (`entities push`, `functions deploy`, `site deploy`) if you only want to deploy specific resources +- The site must be built before deployment - this command does not run `npm run build` for you + +## Related Commands + +| Command | Description | +|---------|-------------| +| `base44 entities push` | Push only entities | +| `base44 functions deploy` | Deploy only functions | +| `base44 agents push` | Push only agents | +| `base44 site deploy` | Deploy only the site | diff --git a/evals/fixtures/sdk-functions/skills/base44-cli/references/entities-create.md b/evals/fixtures/sdk-functions/skills/base44-cli/references/entities-create.md new file mode 100644 index 0000000..29b40aa --- /dev/null +++ b/evals/fixtures/sdk-functions/skills/base44-cli/references/entities-create.md @@ -0,0 +1,552 @@ +# Creating Entities + +Base44 entities are defined locally in your project and then pushed to the Base44 backend. + +## Critical: File Naming + +Entity files MUST use kebab-case naming: `{kebab-case-name}.jsonc` + +| Entity Name | File Name | +|-------------|-----------| +| `Task` | `task.jsonc` | +| `TeamMember` | `team-member.jsonc` | +| `ActivityLog` | `activity-log.jsonc` | + +WRONG: `TeamMember.jsonc`, `teamMember.jsonc` +RIGHT: `team-member.jsonc` + +## Table of Contents + +- [Creating Entities](#creating-entities) + - [Entity Directory](#entity-directory) + - [How to Create an Entity](#how-to-create-an-entity) + - [Entity Schema Structure](#entity-schema-structure) + - [Supported Field Types](#supported-field-types) + - [Field Properties](#field-properties) + - [Complete Example](#complete-example) + - [Naming Conventions](#naming-conventions) + - [Relationships Between Entities](#relationships-between-entities) + - [Row Level Security (RLS)](#row-level-security-rls) + - [Field Level Security (FLS)](#field-level-security-fls) + - [Pushing Entities](#pushing-entities) + +## Entity Directory + +All entity definitions must be placed in the `base44/entities/` folder in your project root. Each entity is defined in its own `.jsonc` file. + +Example structure: +``` +my-app/ + base44/ + entities/ + user.jsonc + product.jsonc + order.jsonc +``` + +## How to Create an Entity + +1. Create a new `.jsonc` file in the `base44/entities/` directory +2. Define your entity schema following the structure below +3. Push the changes to Base44 using the CLI + +## Entity Schema Structure + +Each entity file follows a JSON Schema-like structure: + +```jsonc +{ + "name": "EntityName", // PascalCase entity name + "type": "object", // Always "object" + "properties": { + // Define your fields here + }, + "required": ["field1"] // Array of required field names +} +``` + +### Common Mistake: Nested Schema Property + +**WRONG** - Do NOT wrap properties in a `schema` object: +```jsonc +{ + "name": "Task", + "description": "A task entity", + "schema": { // ❌ WRONG - don't use nested "schema" + "type": "object", + "properties": { ... } + } +} +``` + +**CORRECT** - Put `type` and `properties` at the top level: +```jsonc +{ + "name": "Task", + "description": "A task entity", + "type": "object", // ✅ CORRECT - top level + "properties": { ... } // ✅ CORRECT - top level +} +``` + +This is a common mistake that will cause "Invalid schema: Schema must have a 'type' field" errors when pushing entities. + +## Supported Field Types + +### String + +Basic text field: +```jsonc +{ + "title": { + "type": "string", + "description": "Task title" + } +} +``` + +With format: +```jsonc +{ + "due_date": { + "type": "string", + "format": "date", + "description": "Due date" + } +} +``` + +Available formats: `date`, `date-time`, `time`, `email`, `uri`, `hostname`, `ipv4`, `ipv6`, `uuid`, `file`, `regex`, `richtext` + +### String with Enum + +Constrained to specific values: +```jsonc +{ + "status": { + "type": "string", + "enum": ["todo", "in_progress", "done"], + "default": "todo", + "description": "Current status" + } +} +``` + +### Number + +```jsonc +{ + "position": { + "type": "number", + "description": "Position for ordering" + } +} +``` + +### Integer + +For whole numbers only: +```jsonc +{ + "quantity": { + "type": "integer", + "description": "Item quantity", + "minimum": 0, + "maximum": 1000 + } +} +``` + +### Binary + +For file/blob data: +```jsonc +{ + "attachment": { + "type": "binary", + "description": "File attachment" + } +} +``` + +### Boolean + +```jsonc +{ + "notify_on_change": { + "type": "boolean", + "default": true, + "description": "Enable notifications" + } +} +``` + +### Array of Strings + +```jsonc +{ + "labels": { + "type": "array", + "items": { "type": "string" }, + "description": "Task labels/tags" + } +} +``` + +### Array of Objects + +```jsonc +{ + "attachments": { + "type": "array", + "description": "File attachments", + "items": { + "type": "object", + "properties": { + "name": { "type": "string" }, + "url": { "type": "string" }, + "type": { "type": "string" } + } + } + } +} +``` + +## Field Properties + +| Property | Description | +| ------------- | ---------------------------------------------------------------------------------------- | +| `type` | Data type: `string`, `number`, `integer`, `boolean`, `array`, `object`, `binary` | +| `description` | Human-readable description of the field | +| `enum` | Array of allowed values (for strings) | +| `enumNames` | Human-readable labels for enum values (same order as `enum`) | +| `default` | Default value when not provided | +| `format` | Format hint: `date`, `date-time`, `time`, `email`, `uri`, `hostname`, `ipv4`, `ipv6`, `uuid`, `file`, `regex`, `richtext` | +| `items` | Schema for array items | +| `properties` | Nested properties for object types | +| `$ref` | Reference to another schema definition | +| `minLength` | Minimum string length | +| `maxLength` | Maximum string length | +| `pattern` | Regex pattern for string validation | +| `minimum` | Minimum value for numbers | +| `maximum` | Maximum value for numbers | +| `rls` | Field-level security rules (see Field Level Security section) | + +## Complete Example + +Here's a complete entity definition for a Task: + +```jsonc +{ + "name": "Task", + "type": "object", + "properties": { + "title": { + "type": "string", + "description": "Task title" + }, + "description": { + "type": "string", + "description": "Task description" + }, + "status": { + "type": "string", + "enum": ["todo", "in_progress", "done"], + "default": "todo", + "description": "Current status of the task" + }, + "board_id": { + "type": "string", + "description": "Board this task belongs to" + }, + "assignee_email": { + "type": "string", + "description": "Email of assigned user" + }, + "priority": { + "type": "string", + "enum": ["low", "medium", "high"], + "default": "medium", + "description": "Task priority" + }, + "due_date": { + "type": "string", + "format": "date", + "description": "Due date" + }, + "labels": { + "type": "array", + "items": { "type": "string" }, + "description": "Task labels/tags" + } + }, + "required": ["title"] +} +``` + +## Naming Conventions + +- **Entity name**: Use PascalCase with alphanumeric characters only (e.g., `Task`, `TeamMember`, `ActivityLog`) + - Must match pattern: `/^[a-zA-Z0-9]+$/` + - Valid: `Task`, `TeamMember`, `Order123` + - Invalid: `Team_Member`, `Team-Member`, `Team Member` +- **File name**: Use kebab-case matching the entity (e.g., `task.jsonc`, `team-member.jsonc`, `activity-log.jsonc`) +- **Field names**: Use snake_case (e.g., `board_id`, `user_email`, `due_date`) + +## Relationships Between Entities + +To create relationships between entities, use ID reference fields: + +```jsonc +{ + "board_id": { + "type": "string", + "description": "Board this task belongs to" + }, + "team_id": { + "type": "string", + "description": "Associated team ID" + } +} +``` + +## Row Level Security (RLS) + +Row Level Security (RLS) controls which records users can access based on their identity and attributes. RLS rules are defined per entity inside the `rls` field of the schema. + +**Important:** If no RLS is defined, all records are accessible to all users. + +### RLS Operations + +RLS supports five operations: + +| Operation | Description | +|-----------|-------------| +| `create` | Control who can add new records | +| `read` | Control who can view records | +| `update` | Control who can modify records | +| `delete` | Control who can remove records | +| `write` | Shorthand for `create`, `update`, and `delete` combined | + +### Permission Values + +Each operation accepts one of the following values: + +1. **`true`** - Allow all users (including anonymous/unauthenticated) +2. **`false`** - Block all users +3. **Condition object** - Allow users matching the condition + +### Template Variables + +Use template variables to reference the current user's attributes: + +| Template | Description | +|----------|-------------| +| `{{user.id}}` | The user's ID | +| `{{user.email}}` | The user's email | +| `{{user.role}}` | The user's role | +| `{{user.data.field_name}}` | Custom field from the user's `data` object | + +### Built-in Entity Attributes + +Every entity record has these built-in attributes available for RLS rules: + +| Attribute | Description | +|-----------|-------------| +| `id` | Unique record identifier | +| `created_date` | Timestamp when record was created | +| `updated_date` | Timestamp when record was last updated | +| `created_by` | Email of the user who created the record | + +### Rule Types + +There are two condition types you can use: + +**1. Entity-to-user comparison** - Compare record fields to the current user's values: +```jsonc +{ + "created_by": "{{user.email}}" +} +``` + +**2. User condition check** - Check user properties directly using `user_condition`: +```jsonc +{ + "user_condition": { "role": "admin" } +} +``` + +**Important limitations:** +- `user_condition` only supports **simple equality** (e.g., `{ "role": "admin" }`) +- For `data.*` field comparisons, you can use operators: `$in`, `$nin`, `$ne`, `$all` +- Logical operators `$or`, `$and`, `$nor` are available for combining conditions +- You cannot filter by entity field values directly (e.g., `{"status": "published"}`) +- Only user-related conditions are allowed + +### RLS Examples + +**Owner-only access:** +```jsonc +{ + "created_by": "{{user.email}}" +} +``` + +**Department-based access:** +```jsonc +{ + "data.department": "{{user.data.department}}" +} +``` + +**Admin-only access:** +```jsonc +{ + "user_condition": { "role": "admin" } +} +``` + +**Complete RLS configuration:** +```jsonc +{ + "name": "Task", + "type": "object", + "properties": { + "title": { + "type": "string", + "description": "Task title" + }, + "status": { + "type": "string", + "enum": ["todo", "in_progress", "done"], + "default": "todo" + } + }, + "required": ["title"], + "rls": { + "create": true, + "read": { "created_by": "{{user.email}}" }, + "update": { "created_by": "{{user.email}}" }, + "delete": { "created_by": "{{user.email}}" } + } +} +``` + +### Common RLS Patterns + +**Public create, admin-only management (e.g., contact forms, waitlists):** +```jsonc +{ + "rls": { + "create": true, + "read": { "user_condition": { "role": "admin" } }, + "update": { "user_condition": { "role": "admin" } }, + "delete": { "user_condition": { "role": "admin" } } + } +} +``` + +**Owner-only access:** +```jsonc +{ + "rls": { + "create": true, + "read": { "created_by": "{{user.email}}" }, + "update": { "created_by": "{{user.email}}" }, + "delete": { "created_by": "{{user.email}}" } + } +} +``` + +**Logged-in users only:** +```jsonc +{ + "rls": { + "create": { "user_condition": { "id": "{{user.id}}" } }, + "read": true, + "update": { "created_by": "{{user.email}}" }, + "delete": { "created_by": "{{user.email}}" } + } +} +``` + +### Limitations + +- **user_condition is equality only:** `user_condition` only supports exact match (e.g., `{ "role": "admin" }`) - no operators +- **No comparison operators on user_condition:** `$gt`, `$lt`, `$regex`, `$expr`, `$where` are NOT supported for user conditions +- **No entity field filtering:** Cannot filter by entity field values (e.g., `{"status": "published"}`) +- **No deeply nested templates:** Templates like `{{user.data.profile.department}}` may not work + +**Supported operators:** +- **Logical operators:** `$or`, `$and`, `$nor` for combining multiple conditions +- **Field operators (for `data.*` fields only):** `$in`, `$nin`, `$ne`, `$all` + +### Complex Access Patterns + +For complex access patterns that require multiple conditions (e.g., "owner OR admin"), you have two options: + +1. **Use the Base44 Dashboard UI** - The dashboard allows adding multiple rules per operation with OR logic +2. **Use separate entities** - Split data into multiple entities with different access rules +3. **Use backend functions** - Implement custom access logic in backend functions + +## Field Level Security (FLS) + +Field Level Security allows you to control access to individual fields within an entity. FLS rules are defined within each field's schema using the `rls` property. + +### FLS Operations + +FLS supports the same operations as entity-level RLS: + +| Operation | Description | +|-----------|-------------| +| `create` | Control who can set this field when creating records | +| `read` | Control who can view this field | +| `update` | Control who can modify this field | +| `delete` | Control who can clear this field | +| `write` | Shorthand for `create`, `update`, and `delete` combined | + +### FLS Example + +```jsonc +{ + "name": "Employee", + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "Employee name" + }, + "salary": { + "type": "number", + "description": "Employee salary", + "rls": { + "read": { "user_condition": { "role": "hr" } }, + "update": { "user_condition": { "role": "hr" } } + } + }, + "department": { + "type": "string", + "description": "Department name" + } + }, + "required": ["name"] +} +``` + +In this example, only users with the `hr` role can read or update the `salary` field. All users with access to the entity can read/update other fields. + +### FLS Notes + +- If no field-level RLS is defined, the field inherits the entity-level RLS rules +- FLS rules follow the same condition format as entity-level RLS +- Use FLS for sensitive fields like salary, SSN, or internal notes + +## Pushing Entities + +The `entities push` command will push all entities that exist in the `base44/entities` folder. + +```bash +npx base44 entities push +``` + +For more details on the push command, see [entities-push.md](entities-push.md). diff --git a/evals/fixtures/sdk-functions/skills/base44-cli/references/entities-push.md b/evals/fixtures/sdk-functions/skills/base44-cli/references/entities-push.md new file mode 100644 index 0000000..63b92d7 --- /dev/null +++ b/evals/fixtures/sdk-functions/skills/base44-cli/references/entities-push.md @@ -0,0 +1,71 @@ +# base44 entities push + +Push local entity definitions to Base44. + +## Syntax + +```bash +npx base44 entities push +``` + +## Authentication + +**Required**: Yes. If not authenticated, you'll be prompted to login first. + +## What It Does + +1. Pushes all entities that exist in the `base44/entities` folder +2. Validates that entities exist in the folder +3. Displays the count of entities to be pushed +4. Uploads entities to the Base44 backend +5. Reports the results: created, updated, and deleted entities + +## Prerequisites + +- Must be run from a Base44 project directory +- Project must have entity definitions in the `base44/entities` folder + +## Output + +```bash +$ npx base44 entities push + +Found 3 entities to push +Pushing entities to Base44... + +Created: User, Post +Updated: Comment +Deleted: OldEntity + +✓ Entities pushed successfully +``` + +## Entity Synchronization + +The push operation synchronizes your local entity schema with Base44: + +- **Created**: New entities that didn't exist in Base44 +- **Updated**: Existing entities with modified schema or configuration +- **Deleted**: Entities that were removed from your local configuration + +## Error Handling + +If no entities are found in your project: +```bash +$ npx base44 entities push +No entities found in project +``` + +## Use Cases + +- After defining new entities in your project +- When modifying existing entity schemas +- To sync entity changes before deploying +- As part of your development workflow when data models change + +## Notes + +- This command syncs the entity schema/structure, not the actual data +- Changes are applied to your Base44 project immediately +- Make sure to test entity changes in a development environment first +- Entity definitions are located in the `base44/entities/` directory diff --git a/evals/fixtures/sdk-functions/skills/base44-cli/references/functions-create.md b/evals/fixtures/sdk-functions/skills/base44-cli/references/functions-create.md new file mode 100644 index 0000000..c523cdc --- /dev/null +++ b/evals/fixtures/sdk-functions/skills/base44-cli/references/functions-create.md @@ -0,0 +1,229 @@ +# Creating Functions + +Base44 functions are serverless backend functions that run on Deno. They are defined locally in your project and deployed to the Base44 backend. + +## Function Directory + +All function definitions must be placed in the `base44/functions/` folder in your project. Each function lives in its own subdirectory with a configuration file and entry point. + +Example structure: +``` +my-app/ + base44/ + functions/ + process-order/ + function.jsonc + index.ts + send-notification/ + function.jsonc + index.ts +``` + +## How to Create a Function + +1. Create a new directory in `base44/functions/` with your function name (use kebab-case) +2. Create a `function.jsonc` configuration file in the directory +3. Create the entry point file (e.g., `index.ts`) +4. Deploy the function using the CLI + +## Function Configuration + +Each function requires a `function.jsonc` configuration file: + +```jsonc +{ + "name": "my-function", + "entry": "index.ts" +} +``` + +### Configuration Properties + +| Property | Description | Required | +|----------|-------------|----------| +| `name` | Function name (must match `/^[^.]+$/` - no dots allowed) | Yes | +| `entry` | Entry point file path relative to the function directory (min 1 char) | Yes | + +## Entry Point File + +Functions run on Deno and must export using `Deno.serve()`. Use `npm:` prefix for npm packages. + +```typescript +import { createClientFromRequest } from "npm:@base44/sdk"; + +Deno.serve(async (req) => { + // Get authenticated client from request + const base44 = createClientFromRequest(req); + + // Parse input + const { orderId, action } = await req.json(); + + // Your logic here + const order = await base44.entities.Orders.get(orderId); + + // Return response + return Response.json({ + success: true, + order: order + }); +}); +``` + +### Request Object + +The function receives a standard Deno `Request` object: +- `req.json()` - Parse JSON body +- `req.text()` - Get raw text body +- `req.headers` - Access request headers +- `req.method` - HTTP method + +### Response Object + +Return using `Response.json()` for JSON responses: + +```typescript +// Success response +return Response.json({ data: result }); + +// Error response with status code +return Response.json({ error: "Something went wrong" }, { status: 400 }); + +// Not found +return Response.json({ error: "Order not found" }, { status: 404 }); +``` + +## Complete Example + +### Directory Structure +``` +base44/ + functions/ + process-order/ + function.jsonc + index.ts +``` + +### function.jsonc +```jsonc +{ + "name": "process-order", + "entry": "index.ts" +} +``` + +### index.ts +```typescript +import { createClientFromRequest } from "npm:@base44/sdk"; + +Deno.serve(async (req) => { + try { + const base44 = createClientFromRequest(req); + const { orderId } = await req.json(); + + // Validate input + if (!orderId) { + return Response.json( + { error: "Order ID is required" }, + { status: 400 } + ); + } + + // Fetch and process the order + const order = await base44.entities.Orders.get(orderId); + if (!order) { + return Response.json( + { error: "Order not found" }, + { status: 404 } + ); + } + + return Response.json({ + success: true, + orderId: order.id, + processedAt: new Date().toISOString() + }); + + } catch (error) { + return Response.json( + { error: error.message }, + { status: 500 } + ); + } +}); +``` + +## Using Service Role Access + +For admin-level operations, use `asServiceRole`: + +```typescript +import { createClientFromRequest } from "npm:@base44/sdk"; + +Deno.serve(async (req) => { + const base44 = createClientFromRequest(req); + + // Check user is authenticated + const user = await base44.auth.me(); + if (!user) { + return Response.json({ error: "Unauthorized" }, { status: 401 }); + } + + // Use service role for admin operations + const allOrders = await base44.asServiceRole.entities.Orders.list(); + + return Response.json({ orders: allOrders }); +}); +``` + +## Using Secrets + +Access environment variables configured in the app dashboard: + +```typescript +Deno.serve(async (req) => { + // Access environment variables (configured in app settings) + const apiKey = Deno.env.get("STRIPE_API_KEY"); + + const response = await fetch("https://api.stripe.com/v1/charges", { + headers: { + "Authorization": `Bearer ${apiKey}` + } + }); + + return Response.json(await response.json()); +}); +``` + +## Naming Conventions + +- **Directory name**: Use kebab-case (e.g., `process-order`, `send-notification`) +- **Function name**: Match the directory name, must match pattern `/^[^.]+$/` (no dots allowed) + - Valid: `process-order`, `send_notification`, `myFunction` + - Invalid: `process.order`, `send.notification.v2` +- **Entry file**: Typically `index.ts` or `index.js` + +## Deploying Functions + +After creating your function, deploy it to Base44: + +```bash +npx base44 functions deploy +``` + +For more details on deploying, see [functions-deploy.md](functions-deploy.md). + +## Notes + +- Functions run on Deno runtime, not Node.js +- Use `npm:` prefix for npm packages (e.g., `npm:@base44/sdk`) +- Use `createClientFromRequest(req)` to get a client that inherits the caller's auth context +- Configure secrets via app dashboard for API keys +- Make sure to handle errors gracefully and return appropriate HTTP status codes + +## Common Mistakes + +| Wrong | Correct | Why | +|-------|---------|-----| +| `functions/myFunction.js` (single file) | `functions/my-function/index.ts` + `function.jsonc` | Functions require subdirectory with config | +| `import { ... } from "@base44/sdk"` | `import { ... } from "npm:@base44/sdk"` | Deno requires `npm:` prefix for npm packages | +| `MyFunction` or `myFunction` directory | `my-function` directory | Use kebab-case for directory names | diff --git a/evals/fixtures/sdk-functions/skills/base44-cli/references/functions-deploy.md b/evals/fixtures/sdk-functions/skills/base44-cli/references/functions-deploy.md new file mode 100644 index 0000000..f89d796 --- /dev/null +++ b/evals/fixtures/sdk-functions/skills/base44-cli/references/functions-deploy.md @@ -0,0 +1,78 @@ +# base44 functions deploy + +Deploy local function definitions to Base44. + +## Syntax + +```bash +npx base44 functions deploy +``` + +## Authentication + +**Required**: Yes. If not authenticated, you'll be prompted to login first. + +## What It Does + +1. Scans the `base44/functions/` directory for function definitions +2. Validates that functions exist and have valid configurations +3. Displays the count of functions to be deployed +4. Uploads function code and configuration to Base44 +5. Reports the results: deployed and deleted functions + +## Prerequisites + +- Must be run from a Base44 project directory +- Project must have function definitions in the `base44/functions/` folder +- Each function must have a valid `function.jsonc` config file + +## Output + +```bash +$ npx base44 functions deploy + +Found 2 functions to deploy +Deploying functions to Base44... + +Deployed: process-order, send-notification +Deleted: old-function + +✓ Functions deployed successfully +``` + +## Function Synchronization + +The deploy operation synchronizes your local functions with Base44: + +- **Deployed**: Functions that were created or updated +- **Deleted**: Functions that were removed from your local configuration + +## Error Handling + +If no functions are found in your project: +```bash +$ npx base44 functions deploy +No functions found. Create functions in the 'functions' directory. +``` + +If a function has configuration errors: +```bash +$ npx base44 functions deploy +Function deployment errors: +'my-function' function: Entry point cannot be empty +``` + +## Use Cases + +- After creating new functions in your project +- When modifying existing function code or configuration +- To sync function changes before testing +- As part of your development workflow when backend logic changes + +## Notes + +- This command deploys the function code and configuration +- Changes are applied to your Base44 project immediately +- Make sure to test functions in a development environment first +- Function definitions are located in the `base44/functions/` directory +- For how to create functions, see [functions-create.md](functions-create.md) diff --git a/evals/fixtures/sdk-functions/skills/base44-cli/references/link.md b/evals/fixtures/sdk-functions/skills/base44-cli/references/link.md new file mode 100644 index 0000000..0b009ce --- /dev/null +++ b/evals/fixtures/sdk-functions/skills/base44-cli/references/link.md @@ -0,0 +1,81 @@ +# base44 link + +Links an existing local Base44 project to a Base44 app in the cloud. Use this when you have a `base44/config.jsonc` but haven't connected it to a Base44 app yet. + +## Critical: When to Use Link vs Create + +| Scenario | Command | +|----------|---------| +| Starting fresh, no `base44/` folder | `npx base44 create` | +| Have `base44/config.jsonc` but no `.app.jsonc` | `npx base44 link` | +| Project already linked (has `.app.jsonc`) | Already done, use `deploy` | + +## Syntax + +```bash +npx base44 link [options] +``` + +## Options + +| Option | Description | Required | +|--------|-------------|----------| +| `-c, --create` | Create a new project (skip selection prompt) | No | +| `-n, --name ` | Project name (required when `--create` is used) | With `--create` | +| `-d, --description ` | Project description | No | +| `-p, --projectId ` | Project ID to link to an existing project (skip selection prompt) | No | + +## Non-Interactive Mode + +For CI/CD or agent use: + +**Create a new project:** +```bash +npx base44 link --create --name my-app +``` + +**Link to an existing project:** +```bash +npx base44 link --projectId +``` + +WRONG: `npx base44 link --create` (missing --name) +WRONG: `npx base44 link --create --projectId ` (cannot use both) +RIGHT: `npx base44 link --create --name my-app` +RIGHT: `npx base44 link --projectId ` + +## Examples + +```bash +# Interactive mode - prompts for project details +npx base44 link + +# Non-interactive - create and link in one step +npx base44 link --create --name my-app + +# With description +npx base44 link --create --name my-app --description "My awesome app" + +# Link to a specific existing project by ID +npx base44 link --projectId abc123 +``` + +## What It Does + +1. Finds the `base44/config.jsonc` in the current directory (or parent directories) +2. Verifies no `.app.jsonc` exists (project not already linked) +3. Either: + - Creates a new Base44 app in the cloud (with `--create`), OR + - Links to an existing project (with `--projectId` or interactive selection) +4. Writes the app ID to `base44/.app.jsonc` + +## Requirements + +- Must have `base44/config.jsonc` in the project +- Must NOT have `base44/.app.jsonc` (use `deploy` if already linked) +- Must be authenticated (run `npx base44 login` first) + +## Notes + +- After linking, you can deploy resources with `npx base44 deploy` +- The `.app.jsonc` file should be git-ignored (contains your app ID) diff --git a/evals/fixtures/sdk-functions/skills/base44-cli/references/rls-examples.md b/evals/fixtures/sdk-functions/skills/base44-cli/references/rls-examples.md new file mode 100644 index 0000000..7d3ef42 --- /dev/null +++ b/evals/fixtures/sdk-functions/skills/base44-cli/references/rls-examples.md @@ -0,0 +1,463 @@ +# RLS Examples + +Practical Row-Level Security patterns for common application types. + +**Important:** Base44 RLS supports: +- **Logical operators:** `$or`, `$and`, `$nor` for combining conditions +- **Field operators (for `data.*` fields):** `$in`, `$nin`, `$ne`, `$all` +- **user_condition:** Equality only (no operators) + +## Contents +- [Simple Patterns (JSON Schema)](#simple-patterns-json-schema) +- [Using Operators](#using-operators) +- [Field-Level Security Examples](#field-level-security-examples) +- [Complex Patterns (Dashboard UI or Backend)](#complex-patterns-dashboard-ui-or-backend) +- [Best Practices](#best-practices) + +--- + +## Simple Patterns (JSON Schema) + +These patterns work with the JSON schema RLS format. + +### Todo App - Owner-only access + +Users see and manage only their own tasks. + +```jsonc +{ + "name": "Task", + "type": "object", + "properties": { + "title": { "type": "string" }, + "description": { "type": "string" }, + "completed": { "type": "boolean" }, + "priority": { "type": "string", "enum": ["low", "medium", "high"] }, + "due_date": { "type": "string", "format": "date" } + }, + "rls": { + "create": true, + "read": { "created_by": "{{user.email}}" }, + "update": { "created_by": "{{user.email}}" }, + "delete": { "created_by": "{{user.email}}" } + } +} +``` + +### Contact Form - Public create, admin-only read + +Anyone can submit, only admins can view submissions. + +```jsonc +{ + "name": "ContactSubmission", + "type": "object", + "properties": { + "name": { "type": "string" }, + "email": { "type": "string", "format": "email" }, + "message": { "type": "string" } + }, + "rls": { + "create": true, + "read": { "user_condition": { "role": "admin" } }, + "update": { "user_condition": { "role": "admin" } }, + "delete": { "user_condition": { "role": "admin" } } + } +} +``` + +### User Profile - Self-management + +Users can only access their own profile. + +```jsonc +{ + "name": "UserProfile", + "type": "object", + "properties": { + "name": { "type": "string" }, + "avatar_url": { "type": "string" }, + "bio": { "type": "string" }, + "preferences": { "type": "object" } + }, + "rls": { + "create": true, + "read": { "created_by": "{{user.email}}" }, + "update": { "created_by": "{{user.email}}" }, + "delete": { "created_by": "{{user.email}}" } + } +} +``` + +### Department Data - Same department access + +Users can only see records from their department. + +```jsonc +{ + "name": "DepartmentAnnouncement", + "type": "object", + "properties": { + "title": { "type": "string" }, + "content": { "type": "string" }, + "department": { "type": "string" } + }, + "rls": { + "create": { "user_condition": { "role": "manager" } }, + "read": { "data.department": "{{user.data.department}}" }, + "update": { "user_condition": { "role": "manager" } }, + "delete": { "user_condition": { "role": "admin" } } + } +} +``` + +### Subscription - Admin-managed, user-readable via email field + +```jsonc +{ + "name": "Subscription", + "type": "object", + "properties": { + "user_email": { "type": "string" }, + "tier": { "type": "string", "enum": ["free", "basic", "pro", "enterprise"] }, + "credits": { "type": "number" }, + "renewal_date": { "type": "string", "format": "date" } + }, + "rls": { + "create": { "user_condition": { "role": "admin" } }, + "read": { "data.user_email": "{{user.email}}" }, + "update": { "user_condition": { "role": "admin" } }, + "delete": { "user_condition": { "role": "admin" } } + } +} +``` + +**Note:** This pattern only allows users to read their own subscription. Admins need to use the Dashboard UI to configure additional read access for themselves. + +### Private Data - Owner-only + +```jsonc +{ + "name": "PrivateNotes", + "type": "object", + "properties": { + "title": { "type": "string" }, + "content": { "type": "string" }, + "tags": { "type": "array", "items": { "type": "string" } } + }, + "rls": { + "create": true, + "read": { "created_by": "{{user.email}}" }, + "update": { "created_by": "{{user.email}}" }, + "delete": { "created_by": "{{user.email}}" } + } +} +``` + +### Public Read, Authenticated Write + +Anyone can read, only logged-in users can create/edit their own records. + +```jsonc +{ + "name": "BlogPost", + "type": "object", + "properties": { + "title": { "type": "string" }, + "content": { "type": "string" }, + "author_email": { "type": "string" } + }, + "rls": { + "create": true, + "read": true, + "update": { "created_by": "{{user.email}}" }, + "delete": { "created_by": "{{user.email}}" } + } +} +``` + +--- + +## Using Operators + +### Logical Operators + +Combine multiple conditions using `$or`, `$and`, or `$nor`: + +**Owner OR Admin access:** +```jsonc +{ + "name": "Document", + "type": "object", + "properties": { + "title": { "type": "string" }, + "content": { "type": "string" } + }, + "rls": { + "create": true, + "read": { + "$or": [ + { "created_by": "{{user.email}}" }, + { "user_condition": { "role": "admin" } } + ] + }, + "update": { + "$or": [ + { "created_by": "{{user.email}}" }, + { "user_condition": { "role": "admin" } } + ] + }, + "delete": { "user_condition": { "role": "admin" } } + } +} +``` + +**Multiple roles with $or:** +```jsonc +{ + "rls": { + "read": { + "$or": [ + { "user_condition": { "role": "admin" } }, + { "user_condition": { "role": "manager" } }, + { "user_condition": { "role": "hr" } } + ] + } + } +} +``` + +### Field Operators for data.* Fields + +Use `$in`, `$nin`, `$ne`, `$all` for comparing entity data fields: + +**Access based on tags ($in):** +```jsonc +{ + "rls": { + "read": { + "data.category": { "$in": ["public", "shared"] } + } + } +} +``` + +**Exclude specific statuses ($nin):** +```jsonc +{ + "rls": { + "read": { + "data.status": { "$nin": ["archived", "deleted"] } + } + } +} +``` + +**Not equal ($ne):** +```jsonc +{ + "rls": { + "read": { + "data.visibility": { "$ne": "private" } + } + } +} +``` + +**All tags must match ($all):** +```jsonc +{ + "rls": { + "read": { + "data.required_tags": { "$all": ["approved", "reviewed"] } + } + } +} +``` + +### Combining Logical and Field Operators + +```jsonc +{ + "rls": { + "read": { + "$and": [ + { "data.status": { "$ne": "draft" } }, + { + "$or": [ + { "created_by": "{{user.email}}" }, + { "data.visibility": "public" } + ] + } + ] + } + } +} +``` + +--- + +## Field-Level Security Examples + +Control access to specific fields within an entity. + +### Sensitive Salary Field + +```jsonc +{ + "name": "Employee", + "type": "object", + "properties": { + "name": { "type": "string" }, + "email": { "type": "string", "format": "email" }, + "salary": { + "type": "number", + "description": "Annual salary", + "rls": { + "read": { "user_condition": { "role": "hr" } }, + "write": { "user_condition": { "role": "hr" } } + } + }, + "performance_notes": { + "type": "string", + "description": "Manager notes", + "rls": { + "read": { + "$or": [ + { "user_condition": { "role": "manager" } }, + { "user_condition": { "role": "hr" } } + ] + }, + "write": { "user_condition": { "role": "manager" } } + } + } + } +} +``` + +### Admin-Only Internal Fields + +```jsonc +{ + "name": "Order", + "type": "object", + "properties": { + "order_number": { "type": "string" }, + "total": { "type": "number" }, + "internal_notes": { + "type": "string", + "description": "Internal processing notes", + "rls": { + "read": { "user_condition": { "role": "admin" } }, + "write": { "user_condition": { "role": "admin" } } + } + }, + "profit_margin": { + "type": "number", + "description": "Profit margin percentage", + "rls": { + "read": { "user_condition": { "role": "admin" } }, + "write": false + } + } + } +} +``` + +--- + +## Complex Patterns (Dashboard UI or Backend) + +Some patterns may still require the Dashboard UI or backend functions. + +### Bidirectional Relationships (e.g., Friendships, Matches) + +**Requirement:** Either party in a relationship should have access. + +**Now possible with $or:** +```jsonc +{ + "rls": { + "read": { + "$or": [ + { "data.user_a_email": "{{user.email}}" }, + { "data.user_b_email": "{{user.email}}" } + ] + } + } +} +``` + +**Alternative solutions:** +1. **Entity redesign:** Store two records per relationship (one for each party) +2. **Backend function:** Query with custom logic + +### Complex Business Logic + +**Requirement:** Access depends on multiple entity fields with complex conditions. + +**JSON Schema limitation:** While operators help, very complex business logic may still be hard to express. + +**Solution options:** +1. **Backend function:** Implement custom access logic +2. **Combine simpler rules:** Break complex rules into simpler entity-level and field-level rules + +--- + +## Best Practices + +### Security Strategy + +Use a combination of entity-level RLS and field-level security: + +| Data Type | Approach | Example | +|-----------|----------|---------| +| User-editable | Entity RLS: Owner-only | UserProfile with `created_by` check | +| Sensitive fields | Field-level RLS | Salary field with HR role check | +| Multi-role access | `$or` with user_condition | Admin OR Manager access | +| Conditional access | Field operators | `$in`, `$ne` on data fields | +| Public content | Entity RLS: `read: true` | PublicPost | +| Private content | Entity RLS: Owner-only | PrivateNote | + +### When to Use Each Approach + +| Requirement | Approach | +|-------------|----------| +| Single condition (owner, admin, department) | JSON Schema RLS | +| Multiple OR/AND conditions | JSON Schema RLS with `$or`/`$and` | +| Field value checks with `$in`/`$ne`/etc. | JSON Schema RLS for `data.*` fields | +| Field-level access control | JSON Schema FLS (field-level `rls`) | +| Complex comparison operators (`$gt`, `$lt`) | Backend functions | +| Very complex business logic | Backend functions | + +### Common Role Patterns + +| Role | Typical Access | +|------|----------------| +| `admin` | Full access to all records | +| `moderator` | Read/update access, limited delete | +| `manager` | Department-scoped access | +| `user` | Own records only | + +### Supported Operators Summary + +| Operator | Supported | Notes | +|----------|-----------|-------| +| `$or` | Yes | Combine multiple conditions | +| `$and` | Yes | All conditions must match | +| `$nor` | Yes | None of the conditions match | +| `$in` | Yes | For `data.*` fields only | +| `$nin` | Yes | For `data.*` fields only | +| `$ne` | Yes | For `data.*` fields only | +| `$all` | Yes | For `data.*` fields only | +| `$gt`, `$lt`, `$gte`, `$lte` | No | Use backend functions | +| `$regex` | No | Use backend functions | + +### Limitations Summary + +| Not Supported | Alternative | +|---------------|-------------| +| Operators on `user_condition` | Use equality only for user checks | +| Comparison operators (`$gt`, `$lt`) | Backend functions | +| Regex matching (`$regex`) | Backend functions | +| Cross-entity relationships | Backend functions | diff --git a/evals/fixtures/sdk-functions/skills/base44-cli/references/site-deploy.md b/evals/fixtures/sdk-functions/skills/base44-cli/references/site-deploy.md new file mode 100644 index 0000000..929d302 --- /dev/null +++ b/evals/fixtures/sdk-functions/skills/base44-cli/references/site-deploy.md @@ -0,0 +1,118 @@ +# base44 site deploy + +Deploy built site files to Base44 hosting. + +## Table of Contents + +- [Syntax](#syntax) +- [Authentication](#authentication) +- [Prerequisites](#prerequisites) +- [How It Works](#how-it-works) +- [Interactive Flow](#interactive-flow) +- [Typical Workflow](#typical-workflow) +- [Configuration](#configuration) +- [Error Handling](#error-handling) +- [Use Cases](#use-cases) +- [Notes](#notes) + +## Syntax + +```bash +npx base44 site deploy [options] +``` + +## Options + +| Option | Description | +| ------------ | ------------------------- | +| `-y, --yes` | Skip confirmation prompt | + +Use `-y` flag for non-interactive/automated deployments: + +```bash +npx base44 site deploy -y +``` + +## Authentication + +**Required**: Yes. If not authenticated, you'll be prompted to login first. + +## Prerequisites + +- Must be run from a Base44 project directory +- Project must have `site.outputDirectory` configured in project config +- Site must be built before deploying (run your build command first) +- **SPA only**: Base44 hosting supports Single Page Applications with a single `index.html` entry point. All routes are served from `index.html` (client-side routing). + +## How It Works + +1. Reads project configuration +2. Validates that site configuration exists +3. Prompts for deployment confirmation showing the output directory +4. Creates an archive of site files from the output directory +5. Deploys to Base44 hosting +6. Returns the app URL + +## Interactive Flow + +```bash +$ npx base44 site deploy + +Deploy site from ./dist? (yes/no) yes + +Creating archive... +Uploading to Base44... +Deploying... + +✓ Deployment successful! + +Visit your site at: https://my-app.base44.app +``` + +## Typical Workflow + +```bash +# 1. Build your site using your framework's build command +npm run build + +# 2. Deploy to Base44 +npx base44 site deploy +``` + +## Configuration + +The `site.outputDirectory` in your project configuration should point to where your framework outputs built files: + +- Vite: typically `./dist` +- Next.js: typically `./.next` or `./out` +- Create React App: typically `./build` +- Custom: whatever your build tool outputs to + +## Error Handling + +If site configuration is missing: +```bash +$ npx base44 site deploy +Error: No site configuration found in project +``` + +If you cancel the deployment: +```bash +Deploy site from ./dist? (yes/no) no +Deployment cancelled +``` + +## Use Cases + +- Deploy your site after making changes +- Push new versions of your application +- Deploy after updating content or functionality +- Part of your CI/CD pipeline + +## Notes + +- Always build your site before deploying +- The command deploys whatever is in your output directory +- Make sure your build completed successfully before deploying +- Previous deployments are preserved (versioned) in Base44 +- Deployment is immediate and updates your live site diff --git a/evals/fixtures/sdk-functions/skills/base44-sdk/SKILL.md b/evals/fixtures/sdk-functions/skills/base44-sdk/SKILL.md new file mode 100644 index 0000000..01ddd7f --- /dev/null +++ b/evals/fixtures/sdk-functions/skills/base44-sdk/SKILL.md @@ -0,0 +1,296 @@ +--- +name: base44-sdk +description: "The base44 SDK is the library to communicate with base44 services. In projects, you use it to communicate with remote resources (entities, backend functions, ai agents) and to write backend functions. This skill is the place for learning about available modules and types. When you plan or implement a feature, you must learn this skill" +--- + +# Base44 Coder + +Build apps on the Base44 platform using the Base44 JavaScript SDK. + +## ⚡ IMMEDIATE ACTION REQUIRED - Read This First + +This skill activates on ANY mention of "base44" or when a `base44/` folder exists. **DO NOT read documentation files or search the web before acting.** + +**Your first action MUST be:** +1. Check if `base44/config.jsonc` exists in the current directory +2. If **YES** (existing project scenario): + - This skill (base44-sdk) handles the request + - Implement features using Base44 SDK + - Do NOT use base44-cli unless user explicitly requests CLI commands +3. If **NO** (new project scenario): + - Transfer to base44-cli skill for project initialization + - This skill cannot help until project is initialized + +## When to Use This Skill vs base44-cli + +**Use base44-sdk when:** +- Building features in an **EXISTING** Base44 project +- `base44/config.jsonc` already exists in the project +- Base44 SDK imports are present (`@base44/sdk`) +- Writing JavaScript/TypeScript code using Base44 SDK modules +- Implementing functionality, components, or features +- User mentions: "implement", "build a feature", "add functionality", "write code for" +- User says "create a [type] app" **and** a Base44 project already exists + +**DO NOT USE base44-sdk for:** +- ❌ Initializing new Base44 projects (use `base44-cli` instead) +- ❌ Empty directories without Base44 configuration +- ❌ When user says "create a new Base44 project/app/site" and no project exists +- ❌ CLI commands like `npx base44 create`, `npx base44 deploy`, `npx base44 login` (use `base44-cli`) + +**Skill Dependencies:** +- `base44-sdk` assumes a Base44 project is **already initialized** +- `base44-cli` is a **prerequisite** for `base44-sdk` in new projects +- If user wants to "create an app" and no Base44 project exists, use `base44-cli` first + +**State Check Logic:** +Before selecting this skill, verify: +- IF (user mentions "create/build app" OR "make a project"): + - IF (directory is empty OR no `base44/config.jsonc` exists): + → Use **base44-cli** (project initialization needed) + - ELSE: + → Use **base44-sdk** (project exists, build features) + +## Quick Start + +```javascript +// In Base44-generated apps, base44 client is pre-configured and available + +// CRUD operations +const task = await base44.entities.Task.create({ title: "New task", status: "pending" }); +const tasks = await base44.entities.Task.list(); +await base44.entities.Task.update(task.id, { status: "done" }); + +// Get current user +const user = await base44.auth.me(); +``` + +```javascript +// External apps +import { createClient } from "@base44/sdk"; + +// IMPORTANT: Use 'appId' (NOT 'clientId' or 'id') +const base44 = createClient({ appId: "your-app-id" }); +await base44.auth.loginViaEmailPassword("user@example.com", "password"); +``` + +## ⚠️ CRITICAL: Do Not Hallucinate APIs + +**Before writing ANY Base44 code, verify method names against this table or [QUICK_REFERENCE.md](references/QUICK_REFERENCE.md).** + +Base44 SDK has unique method names. Do NOT assume patterns from Firebase, Supabase, or other SDKs. + +### Authentication - WRONG vs CORRECT + +| ❌ WRONG (hallucinated) | ✅ CORRECT | +|------------------------|-----------| +| `signInWithGoogle()` | `loginWithProvider('google')` | +| `signInWithProvider('google')` | `loginWithProvider('google')` | +| `auth.google()` | `loginWithProvider('google')` | +| `signInWithEmailAndPassword(email, pw)` | `loginViaEmailPassword(email, pw)` | +| `signIn(email, pw)` | `loginViaEmailPassword(email, pw)` | +| `createUser()` / `signUp()` | `register({email, password})` | +| `onAuthStateChanged()` | `me()` (no listener, call when needed) | +| `currentUser` | `await auth.me()` | + +### Functions - WRONG vs CORRECT + +| ❌ WRONG (hallucinated) | ✅ CORRECT | +|------------------------|-----------| +| `functions.call('name', data)` | `functions.invoke('name', data)` | +| `functions.run('name', data)` | `functions.invoke('name', data)` | +| `callFunction('name', data)` | `functions.invoke('name', data)` | +| `httpsCallable('name')(data)` | `functions.invoke('name', data)` | + +### Integrations - WRONG vs CORRECT + +| ❌ WRONG (hallucinated) | ✅ CORRECT | +|------------------------|-----------| +| `ai.generate(prompt)` | `integrations.Core.InvokeLLM({prompt})` | +| `openai.chat(prompt)` | `integrations.Core.InvokeLLM({prompt})` | +| `llm(prompt)` | `integrations.Core.InvokeLLM({prompt})` | +| `sendEmail(to, subject, body)` | `integrations.Core.SendEmail({to, subject, body})` | +| `email.send()` | `integrations.Core.SendEmail({to, subject, body})` | +| `uploadFile(file)` | `integrations.Core.UploadFile({file})` | +| `storage.upload(file)` | `integrations.Core.UploadFile({file})` | + +### Entities - WRONG vs CORRECT + +| ❌ WRONG (hallucinated) | ✅ CORRECT | +|------------------------|-----------| +| `entities.Task.find({...})` | `entities.Task.filter({...})` | +| `entities.Task.findOne(id)` | `entities.Task.get(id)` | +| `entities.Task.insert(data)` | `entities.Task.create(data)` | +| `entities.Task.remove(id)` | `entities.Task.delete(id)` | +| `entities.Task.onChange(cb)` | `entities.Task.subscribe(cb)` | + +## SDK Modules + +| Module | Purpose | Reference | +|--------|---------|-----------| +| `entities` | CRUD operations on data models | [entities.md](references/entities.md) | +| `auth` | Login, register, user management | [auth.md](references/auth.md) | +| `agents` | AI conversations and messages | [base44-agents.md](references/base44-agents.md) | +| `functions` | Backend function invocation | [functions.md](references/functions.md) | +| `integrations` | AI, email, file uploads, custom APIs | [integrations.md](references/integrations.md) | +| `connectors` | OAuth tokens (service role only) | [connectors.md](references/connectors.md) | +| `analytics` | Track custom events and user activity | [analytics.md](references/analytics.md) | +| `appLogs` | Log user activity in app | [app-logs.md](references/app-logs.md) | +| `users` | Invite users to the app | [users.md](references/users.md) | + +For client setup and authentication modes, see [client.md](references/client.md). + +**TypeScript Support:** Each reference file includes a "Type Definitions" section with TypeScript interfaces and types for the module's methods, parameters, and return values. + +## Installation + +Install the Base44 SDK: + +```bash +npm install @base44/sdk +``` + +**Important:** Never assume or hardcode the `@base44/sdk` package version. Always install without a version specifier to get the latest version. + +## Creating a Client (External Apps) + +When creating a client in external apps, **ALWAYS use `appId` as the parameter name**: + +```javascript +import { createClient } from "@base44/sdk"; + +// ✅ CORRECT +const base44 = createClient({ appId: "your-app-id" }); + +// ❌ WRONG - Do NOT use these: +// const base44 = createClient({ clientId: "your-app-id" }); // WRONG +// const base44 = createClient({ id: "your-app-id" }); // WRONG +``` + +**Required parameter:** `appId` (string) - Your Base44 application ID + +**Optional parameters:** +- `token` (string) - Pre-authenticated user token +- `options` (object) - Configuration options + - `options.onError` (function) - Global error handler + +**Example with error handler:** +```javascript +const base44 = createClient({ + appId: "your-app-id", + options: { + onError: (error) => { + console.error("Base44 error:", error); + } + } +}); +``` + +## Module Selection + +**Working with app data?** +- Create/read/update/delete records → `entities` +- Import data from file → `entities.importEntities()` +- Realtime updates → `entities.EntityName.subscribe()` + +**User management?** +- Login/register/logout → `auth` +- Get current user → `auth.me()` +- Update user profile → `auth.updateMe()` +- Invite users → `users.inviteUser()` + +**AI features?** +- Chat with AI agents → `agents` (requires logged-in user) +- Create new conversation → `agents.createConversation()` +- Manage conversations → `agents.getConversations()` +- Generate text/JSON with AI → `integrations.Core.InvokeLLM()` +- Generate images → `integrations.Core.GenerateImage()` + +**Custom backend logic?** +- Run server-side code → `functions.invoke()` +- Need admin access → `base44.asServiceRole.functions.invoke()` + +**External services?** +- Send emails → `integrations.Core.SendEmail()` +- Upload files → `integrations.Core.UploadFile()` +- Custom APIs → `integrations.custom.call()` +- OAuth tokens (Google, Slack) → `connectors` (backend only) + +**Tracking and analytics?** +- Track custom events → `analytics.track()` +- Log page views/activity → `appLogs.logUserInApp()` + +## Common Patterns + +### Filter and Sort Data + +```javascript +const pendingTasks = await base44.entities.Task.filter( + { status: "pending", assignedTo: userId }, // query + "-created_date", // sort (descending) + 10, // limit + 0 // skip +); +``` + +### Protected Routes (check auth) + +```javascript +const user = await base44.auth.me(); +if (!user) { + // Navigate to your custom login page + navigate('/login', { state: { returnTo: window.location.pathname } }); + return; +} +``` + +### Backend Function Call + +```javascript +// Frontend +const result = await base44.functions.invoke("processOrder", { + orderId: "123", + action: "ship" +}); + +// Backend function (Deno) +import { createClientFromRequest } from "npm:@base44/sdk"; + +Deno.serve(async (req) => { + const base44 = createClientFromRequest(req); + const { orderId, action } = await req.json(); + // Process with service role for admin access + const order = await base44.asServiceRole.entities.Orders.get(orderId); + return Response.json({ success: true }); +}); +``` + +### Service Role Access + +Use `asServiceRole` in backend functions for admin-level operations: + +```javascript +// User mode - respects permissions +const myTasks = await base44.entities.Task.list(); + +// Service role - full access (backend only) +const allTasks = await base44.asServiceRole.entities.Task.list(); +const token = await base44.asServiceRole.connectors.getAccessToken("slack"); +``` + +## Frontend vs Backend + +| Capability | Frontend | Backend | +|------------|----------|---------| +| `entities` (user's data) | Yes | Yes | +| `auth` | Yes | Yes | +| `agents` | Yes | Yes | +| `functions.invoke()` | Yes | Yes | +| `integrations` | Yes | Yes | +| `analytics` | Yes | Yes | +| `appLogs` | Yes | Yes | +| `users` | Yes | Yes | +| `asServiceRole.*` | No | Yes | +| `connectors` | No | Yes | + +Backend functions use `Deno.serve()` and `createClientFromRequest(req)` to get a properly authenticated client. diff --git a/evals/fixtures/sdk-functions/skills/base44-sdk/references/QUICK_REFERENCE.md b/evals/fixtures/sdk-functions/skills/base44-sdk/references/QUICK_REFERENCE.md new file mode 100644 index 0000000..d357384 --- /dev/null +++ b/evals/fixtures/sdk-functions/skills/base44-sdk/references/QUICK_REFERENCE.md @@ -0,0 +1,155 @@ +# Base44 SDK Quick Reference + +Compact method signatures for all SDK modules. **Verify against this before writing code.** + +--- + +## Auth (`base44.auth.*`) + +``` +loginViaEmailPassword(email, password, turnstileToken?) → Promise<{access_token, user}> +loginWithProvider('google' | 'microsoft' | 'facebook', fromUrl?) → void +me() → Promise +updateMe(data) → Promise +isAuthenticated() → Promise +logout(redirectUrl?) → void +redirectToLogin(nextUrl) → void # ⚠️ Avoid - prefer custom login UI +register({email, password, turnstile_token?, referral_code?}) → Promise +verifyOtp({email, otpCode}) → Promise +resendOtp(email) → Promise +inviteUser(userEmail, role) → Promise +resetPasswordRequest(email) → Promise +resetPassword({resetToken, newPassword}) → Promise +changePassword({userId, currentPassword, newPassword}) → Promise +setToken(token, saveToStorage?) → void +``` + +--- + +## Entities (`base44.entities.EntityName.*`) + +``` +create(data) → Promise +bulkCreate(dataArray) → Promise +list(sort?, limit?, skip?, fields?) → Promise +filter(query, sort?, limit?, skip?, fields?) → Promise +get(id) → Promise +update(id, data) → Promise +delete(id) → Promise +deleteMany(query) → Promise +importEntities(file) → Promise // frontend only +subscribe(callback) → () => void // returns unsubscribe fn +``` + +**Sort:** Use `-fieldName` for descending (e.g., `-created_date`) + +--- + +## Functions (`base44.functions.*`) + +``` +invoke(functionName, data) → Promise +``` + +**Backend:** Use `base44.asServiceRole.functions.invoke()` for admin access. + +--- + +## Integrations (`base44.integrations.Core.*`) + +``` +InvokeLLM({prompt, add_context_from_internet?, response_json_schema?, file_urls?}) → Promise +GenerateImage({prompt}) → Promise<{url}> +SendEmail({to, subject, body, from_name?}) → Promise +UploadFile({file}) → Promise<{file_url}> +UploadPrivateFile({file}) → Promise<{file_uri}> +CreateFileSignedUrl({file_uri, expires_in?}) → Promise<{signed_url}> +ExtractDataFromUploadedFile({file_url, json_schema}) → Promise +``` + +### Custom Integrations (`base44.integrations.custom.*`) + +``` +call(slug, operationId, {payload?, pathParams?, queryParams?, headers?}?) → Promise<{success, status_code, data}> +``` + +**operationId format:** `"method:/path"` (e.g., `"get:/contacts"`, `"post:/users/{id}"`) + +--- + +## Analytics (`base44.analytics.*`) + +``` +track({eventName, properties?}) → void +``` + +--- + +## App Logs (`base44.appLogs.*`) + +``` +logUserInApp(pageName) → Promise +``` + +--- + +## Users (`base44.users.*`) + +``` +inviteUser(userEmail, role) → Promise // role: 'user' | 'admin' +``` + +--- + +## Connectors (`base44.asServiceRole.connectors.*`) + +**Backend only, service role required.** + +``` +getAccessToken(integrationType) → Promise +``` + +**Types:** `'googlecalendar'`, `'googledrive'`, `'slack'`, `'notion'`, `'salesforce'`, `'hubspot'`, `'linkedin'`, `'tiktok'`, `'github'` + +--- + +## Service Role Access + +**Backend functions only.** Prefix any module with `asServiceRole` for admin access: + +```javascript +base44.asServiceRole.entities.Task.list() +base44.asServiceRole.functions.invoke('name', data) +base44.asServiceRole.connectors.getAccessToken('slack') +``` + +--- + +## Backend Function Template + +```javascript +import { createClientFromRequest } from "@base44/sdk"; + +Deno.serve(async (req) => { + const base44 = createClientFromRequest(req); + const data = await req.json(); + + // User context + const user = await base44.auth.me(); + + // Service role for admin operations + const allRecords = await base44.asServiceRole.entities.Task.list(); + + return Response.json({ success: true }); +}); +``` + +--- + +## Client Initialization (External Apps) + +```javascript +import { createClient } from "@base44/sdk"; + +const base44 = createClient({ appId: "your-app-id" }); // MUST use 'appId' +``` diff --git a/evals/fixtures/sdk-functions/skills/base44-sdk/references/analytics.md b/evals/fixtures/sdk-functions/skills/base44-sdk/references/analytics.md new file mode 100644 index 0000000..d1b2c69 --- /dev/null +++ b/evals/fixtures/sdk-functions/skills/base44-sdk/references/analytics.md @@ -0,0 +1,113 @@ +# Analytics Module + +Track custom events and user activity via `base44.analytics`. + +## Contents +- [Methods](#methods) +- [Examples](#examples) +- [Automatic Tracking](#automatic-tracking) +- [Best Practices](#best-practices) + +## Methods + +| Method | Signature | Description | +|--------|-----------|-------------| +| `track(params)` | `void` | Track a custom event | + +## Examples + +### Track Custom Event + +```javascript +// Track a simple event +base44.analytics.track({ + eventName: "button_clicked" +}); + +// Track event with properties +base44.analytics.track({ + eventName: "purchase_completed", + properties: { + product_id: "prod-123", + amount: 99.99, + currency: "USD" + } +}); +``` + +### Track User Actions + +```javascript +// Track page view +base44.analytics.track({ + eventName: "page_view", + properties: { + page: "/dashboard", + referrer: document.referrer + } +}); + +// Track feature usage +base44.analytics.track({ + eventName: "feature_used", + properties: { + feature: "export_data", + format: "csv" + } +}); +``` + +## Automatic Tracking + +The analytics module automatically tracks: +- **Initialization events**: When the app loads +- **Heartbeat events**: Periodic activity signals +- **Session duration**: Time spent in the app + +These internal events help measure user engagement without manual instrumentation. + +## Best Practices + +1. **Use descriptive event names**: `order_completed` instead of `click` +2. **Include relevant properties**: Add context that helps analyze the event +3. **Be consistent**: Use the same event names and property keys across your app +4. **Don't track sensitive data**: Avoid PII in event properties + +```javascript +// Good: Descriptive with relevant properties +base44.analytics.track({ + eventName: "subscription_started", + properties: { + plan: "pro", + billing_cycle: "annual" + } +}); + +// Avoid: Vague event name, no context +base44.analytics.track({ + eventName: "click" +}); +``` + +## Type Definitions + +```typescript +/** Properties that can be attached to a tracked event. */ +type TrackEventProperties = { + [key: string]: string | number | boolean | null | undefined; +}; + +/** Parameters for the track() method. */ +interface TrackEventParams { + /** The name of the event to track. */ + eventName: string; + /** Optional properties to attach to the event. */ + properties?: TrackEventProperties; +} + +/** The analytics module interface. */ +interface AnalyticsModule { + /** Track a custom event with optional properties. */ + track(params: TrackEventParams): void; +} +``` diff --git a/evals/fixtures/sdk-functions/skills/base44-sdk/references/app-logs.md b/evals/fixtures/sdk-functions/skills/base44-sdk/references/app-logs.md new file mode 100644 index 0000000..0439805 --- /dev/null +++ b/evals/fixtures/sdk-functions/skills/base44-sdk/references/app-logs.md @@ -0,0 +1,78 @@ +# App Logs Module + +Log user activity in your app via `base44.appLogs`. + +## Contents +- [Methods](#methods) +- [Examples](#examples) +- [Use Cases](#use-cases) + +## Methods + +| Method | Signature | Description | +|--------|-----------|-------------| +| `logUserInApp(pageName)` | `Promise` | Log user activity on a page | + +## Examples + +### Log User Activity + +```javascript +// Log when user visits a page +await base44.appLogs.logUserInApp("dashboard"); + +// Log specific page visits +await base44.appLogs.logUserInApp("settings"); +await base44.appLogs.logUserInApp("profile"); + +// Log feature usage +await base44.appLogs.logUserInApp("export-button-click"); +``` + +The page name doesn't have to be an actual page - it can be any string you want to track. + +## Use Cases + +### Track Page Views in React + +```javascript +// Log page views on route change +useEffect(() => { + base44.appLogs.logUserInApp(window.location.pathname); +}, [location.pathname]); +``` + +### Track Feature Usage + +```javascript +// Log when user uses specific features +function handleExport() { + base44.appLogs.logUserInApp("export-data"); + // ... export logic +} + +function handleSettingsChange() { + base44.appLogs.logUserInApp("settings-updated"); + // ... save settings +} +``` + +## Notes + +- Logs appear in the Analytics page of your app dashboard +- App logs track page-level and feature-level activity +- Use `analytics.track()` for custom events with properties, `appLogs.logUserInApp()` for simple page/feature tracking + +## Type Definitions + +```typescript +/** App Logs module for tracking and analyzing app usage. */ +interface AppLogsModule { + /** + * Log user activity in the app. + * @param pageName - Name of the page or section being visited. + * @returns Promise that resolves when the log is recorded. + */ + logUserInApp(pageName: string): Promise; +} +``` diff --git a/evals/fixtures/sdk-functions/skills/base44-sdk/references/auth.md b/evals/fixtures/sdk-functions/skills/base44-sdk/references/auth.md new file mode 100644 index 0000000..5195b3a --- /dev/null +++ b/evals/fixtures/sdk-functions/skills/base44-sdk/references/auth.md @@ -0,0 +1,761 @@ +# Auth Module + +User authentication, registration, and session management via `base44.auth`. + +## Contents +- [TypeScript Types](#typescript-types) +- [Methods](#methods) +- [Examples](#examples) +- [Error Handling](#error-handling) +- [Auth Providers](#auth-providers) +- [Environment Availability](#environment-availability) +- [App Visibility](#app-visibility) +- [Limitations](#limitations) + +--- + +## TypeScript Types + +### User Interface +```typescript +interface User { + id: string; + created_date: string; + updated_date: string; + email: string; + full_name: string | null; + disabled: boolean | null; + is_verified: boolean; + app_id: string; + is_service: boolean; + role: string; + [key: string]: any; // Custom schema fields +} +``` + +### LoginResponse Interface +```typescript +interface LoginResponse { + access_token: string; // JWT token + user: User; // Complete user object +} +``` + +### Parameter Interfaces + +#### RegisterParams +```typescript +interface RegisterParams { + email: string; // Required + password: string; // Required + turnstile_token?: string | null; // Optional: Cloudflare Turnstile for bot protection + referral_code?: string | null; // Optional: Referral code +} +``` + +#### VerifyOtpParams +```typescript +interface VerifyOtpParams { + email: string; // User's email + otpCode: string; // OTP code from email +} +``` + +#### ResetPasswordParams +```typescript +interface ResetPasswordParams { + resetToken: string; // Token from password reset email + newPassword: string; // New password to set +} +``` + +#### ChangePasswordParams +```typescript +interface ChangePasswordParams { + userId: string; // User ID + currentPassword: string; // Current password for verification + newPassword: string; // New password to set +} +``` + +### Provider Type +```typescript +type Provider = 'google' | 'microsoft' | 'facebook'; +``` + +--- + +## Methods + +### Module Interface +```typescript +interface AuthModule { + // User Info + me(): Promise; + updateMe(data: Partial>): Promise; + isAuthenticated(): Promise; + + // Login/Logout + loginViaEmailPassword(email: string, password: string, turnstileToken?: string): Promise; + loginWithProvider(provider: Provider, fromUrl?: string): void; + logout(redirectUrl?: string): void; + redirectToLogin(nextUrl: string): void; + + // Token Management + setToken(token: string, saveToStorage?: boolean): void; + + // Registration + register(params: RegisterParams): Promise; + verifyOtp(params: VerifyOtpParams): Promise; + resendOtp(email: string): Promise; + + // User Management + inviteUser(userEmail: string, role: string): Promise; + + // Password Management + resetPasswordRequest(email: string): Promise; + resetPassword(params: ResetPasswordParams): Promise; + changePassword(params: ChangePasswordParams): Promise; +} +``` + +### Method Reference Table + +| Method | Parameters | Return Type | Description | +|--------|-----------|-------------|-------------| +| `register()` | `params: RegisterParams` | `Promise` | Create new user account | +| `loginViaEmailPassword()` | `email: string, password: string, turnstileToken?: string` | `Promise` | Authenticate with email/password | +| `loginWithProvider()` | `provider: Provider, fromUrl?: string` | `void` | Initiate OAuth login flow | +| `me()` | None | `Promise` | Get current authenticated user | +| `updateMe()` | `data: Partial` | `Promise` | Update current user's profile | +| `logout()` | `redirectUrl?: string` | `void` | Clear session, optionally redirect | +| `redirectToLogin()` | `nextUrl: string` | `void` | ⚠️ **Avoid** - Prefer custom login UI with `loginViaEmailPassword()` or `loginWithProvider()` | +| `isAuthenticated()` | None | `Promise` | Check if user is logged in | +| `setToken()` | `token: string, saveToStorage?: boolean` | `void` | Manually set auth token | +| `inviteUser()` | `userEmail: string, role: string` | `Promise` | Send invitation email | +| `verifyOtp()` | `params: VerifyOtpParams` | `Promise` | Verify OTP code | +| `resendOtp()` | `email: string` | `Promise` | Resend OTP code | +| `resetPasswordRequest()` | `email: string` | `Promise` | Request password reset | +| `resetPassword()` | `params: ResetPasswordParams` | `Promise` | Reset password with token | +| `changePassword()` | `params: ChangePasswordParams` | `Promise` | Change user password | + +--- + +## Examples + +### Register New User (Complete Flow) + +Registration requires email verification before login. Complete flow: + +1. **Register** - Create the user account +2. **Verification email sent** - User receives an OTP code +3. **Verify OTP** - User enters code to verify email +4. **Login** - User can now log in + +```javascript +try { + // Step 1: Register the user + await base44.auth.register({ + email: "user@example.com", + password: "securePassword123", + referral_code: "OPTIONAL_CODE", // optional + turnstile_token: "CAPTCHA_TOKEN" // optional, for bot protection + }); + console.log('Registration successful. Check email for OTP code.'); + + // Step 2: User receives email with OTP code (e.g., "123456") + + // Step 3: Verify the OTP code + await base44.auth.verifyOtp({ + email: "user@example.com", + otpCode: "123456" // code from verification email + }); + console.log('Email verified successfully.'); + + // Step 4: Now the user can log in + const loginResponse = await base44.auth.loginViaEmailPassword( + "user@example.com", + "securePassword123" + ); + console.log('Login successful:', loginResponse.user); + +} catch (error) { + console.error('Registration flow failed:', error.message); + // Handle specific errors (see Error Handling section) +} +``` + +> **Important**: Users cannot log in until they complete OTP verification. Attempting to call `loginViaEmailPassword` before verification will fail. + +### Login with Email/Password + +```javascript +try { + const response = await base44.auth.loginViaEmailPassword( + "user@example.com", + "password123", + turnstileToken // optional: for bot protection + ); + + console.log('Login successful'); + console.log('User:', response.user); + console.log('Token:', response.access_token); + + // JWT is automatically stored for subsequent requests + +} catch (error) { + console.error('Login failed:', error.message); + if (error.status === 401) { + console.error('Invalid credentials'); + } else if (error.status === 403) { + console.error('Email not verified. Please check your email for OTP.'); + } +} +``` + +### Login with OAuth Provider + +```javascript +// Redirect to Google OAuth +base44.auth.loginWithProvider('google'); + +// Redirect to Google OAuth and return to current page after +base44.auth.loginWithProvider('google', window.location.href); + +// Supported providers: 'google', 'microsoft', 'facebook' +base44.auth.loginWithProvider('microsoft'); +``` + +### Get Current User + +```javascript +try { + const user = await base44.auth.me(); + + if (user) { + console.log('User ID:', user.id); + console.log('Email:', user.email); + console.log('Name:', user.full_name); + console.log('Role:', user.role); + console.log('Verified:', user.is_verified); + } else { + console.log('User not authenticated'); + } + +} catch (error) { + console.error('Failed to fetch user:', error.message); + if (error.status === 401) { + // Token expired or invalid - navigate to your custom login page + navigate('/login'); + } +} +``` + +### Update User Profile + +```javascript +try { + const updatedUser = await base44.auth.updateMe({ + full_name: "John Doe", + // Custom schema fields can be updated here + phone: "+1234567890", + preferences: { theme: "dark" } + }); + + console.log('Profile updated:', updatedUser); + +} catch (error) { + console.error('Profile update failed:', error.message); + if (error.status === 400) { + console.error('Invalid data provided'); + } else if (error.status === 401) { + console.error('Authentication required'); + navigate('/login'); + } +} +``` + +### Check Authentication Status + +```javascript +try { + const isLoggedIn = await base44.auth.isAuthenticated(); + + if (isLoggedIn) { + // Show authenticated UI + console.log('User is authenticated'); + } else { + // Show login button + console.log('User is not authenticated'); + } + +} catch (error) { + console.error('Failed to check authentication:', error.message); + // On error, treat as not authenticated +} +``` + +### Logout + +```javascript +// Simple logout +base44.auth.logout(); + +// Logout and redirect to goodbye page +base44.auth.logout("/goodbye"); + +// Logout and redirect to homepage +base44.auth.logout("/"); +``` + +### Protected Route Pattern + +```javascript +// Using a navigation function (e.g., React Router's useNavigate, Next.js router) +async function requireAuth(navigate) { + try { + const user = await base44.auth.me(); + + if (!user) { + // Navigate to your custom login page + navigate('/login', { state: { returnTo: window.location.pathname } }); + return null; + } + + return user; + + } catch (error) { + console.error('Authentication check failed:', error.message); + navigate('/login', { state: { returnTo: window.location.pathname } }); + return null; + } +} + +// Usage in your app +async function loadProtectedPage(navigate) { + const user = await requireAuth(navigate); + if (!user) { + // Will navigate to login + return; + } + + // Continue with authenticated logic + console.log('Welcome,', user.full_name); +} +``` + +### Set Authentication Token + +```javascript +// SECURITY WARNING: Never hardcode tokens or expose them in client code +// Tokens should only be received from secure authentication flows + +// Set token and save to localStorage (default) +base44.auth.setToken(receivedToken); + +// Set token without saving to localStorage (temporary session) +base44.auth.setToken(receivedToken, false); + +// Verify token was set +try { + const isAuthenticated = await base44.auth.isAuthenticated(); + if (!isAuthenticated) { + console.error('Token validation failed'); + } +} catch (error) { + console.error('Failed to set token:', error.message); +} +``` + +### Invite User to Application + +```javascript +try { + // Note: Typically requires admin privileges + const response = await base44.auth.inviteUser( + "newuser@example.com", + "user" // or "admin" + ); + + console.log('Invitation sent successfully'); + +} catch (error) { + console.error('Failed to invite user:', error.message); + if (error.status === 403) { + console.error('Insufficient permissions to invite users'); + } else if (error.status === 400) { + console.error('Invalid email or role'); + } +} +``` + +### OTP Verification + +```javascript +try { + // Verify OTP code sent to user's email + await base44.auth.verifyOtp({ + email: "user@example.com", + otpCode: "123456" + }); + + console.log('OTP verified successfully'); + +} catch (error) { + console.error('OTP verification failed:', error.message); + if (error.status === 400) { + console.error('Invalid or expired OTP code'); + } else if (error.status === 429) { + console.error('Too many attempts. Please try again later.'); + } +} + +// Resend OTP if needed +try { + await base44.auth.resendOtp("user@example.com"); + console.log('OTP resent successfully'); + +} catch (error) { + console.error('Failed to resend OTP:', error.message); + if (error.status === 429) { + console.error('Too many requests. Please wait before trying again.'); + } +} +``` + +### Password Reset Flow + +```javascript +// Step 1: Request password reset +try { + await base44.auth.resetPasswordRequest("user@example.com"); + console.log('Password reset email sent. Check your inbox.'); + +} catch (error) { + console.error('Password reset request failed:', error.message); + if (error.status === 429) { + console.error('Too many requests. Please try again later.'); + } + // Note: For security, don't reveal if email exists +} + +// Step 2: Reset password with token from email +try { + await base44.auth.resetPassword({ + resetToken: "token-from-email", + newPassword: "newSecurePassword123" + }); + + console.log('Password reset successfully. You can now log in.'); + +} catch (error) { + console.error('Password reset failed:', error.message); + if (error.status === 400) { + console.error('Invalid or expired reset token'); + } else if (error.status === 422) { + console.error('Password does not meet requirements'); + } +} +``` + +### Change Password + +```javascript +try { + const currentUser = await base44.auth.me(); + + if (!currentUser) { + throw new Error('User must be authenticated to change password'); + } + + await base44.auth.changePassword({ + userId: currentUser.id, + currentPassword: "oldPassword123", + newPassword: "newSecurePassword456" + }); + + console.log('Password changed successfully'); + +} catch (error) { + console.error('Password change failed:', error.message); + if (error.status === 401) { + console.error('Current password is incorrect'); + } else if (error.status === 422) { + console.error('New password does not meet requirements'); + } else if (error.status === 403) { + console.error('Not authorized to change this password'); + } +} +``` + +--- + +## Error Handling + +### Common Error Scenarios + +The auth module can throw various errors. Here are common scenarios and how to handle them: + +#### Authentication Errors (401/403) +```javascript +try { + const user = await base44.auth.me(); +} catch (error) { + if (error.status === 401) { + // Token expired or invalid - navigate to your custom login page + navigate('/login'); + } else if (error.status === 403) { + // Email not verified or insufficient permissions + console.error('Access denied:', error.message); + } +} +``` + +#### Validation Errors (400/422) +```javascript +try { + await base44.auth.register({ + email: "invalid-email", + password: "weak" + }); +} catch (error) { + if (error.status === 400) { + console.error('Invalid input:', error.message); + // Handle validation errors + } else if (error.status === 422) { + console.error('Data validation failed:', error.details); + } +} +``` + +#### Rate Limiting (429) +```javascript +try { + await base44.auth.resendOtp("user@example.com"); +} catch (error) { + if (error.status === 429) { + console.error('Too many requests. Please wait before trying again.'); + // Show countdown or disable button temporarily + } +} +``` + +#### Generic Error Handler +```javascript +function handleAuthError(error) { + switch (error.status) { + case 400: + return 'Invalid input. Please check your data.'; + case 401: + return 'Authentication required. Redirecting to login...'; + case 403: + return 'Access denied. You may need to verify your email.'; + case 404: + return 'Resource not found.'; + case 422: + return 'Validation failed. Please check your input.'; + case 429: + return 'Too many requests. Please try again later.'; + case 500: + return 'Server error. Please try again.'; + default: + return 'An unexpected error occurred.'; + } +} + +// Usage +try { + await base44.auth.loginViaEmailPassword(email, password); +} catch (error) { + console.error(handleAuthError(error)); +} +``` + +--- + +## Auth Providers + +Configure authentication providers in your app dashboard: + +### Available Providers + +**Built-in (All Plans):** +- **Email/Password** - Default, always enabled +- **Google** - OAuth authentication +- **Microsoft** - OAuth authentication +- **Facebook** - OAuth authentication + +**SSO Providers (Elite Plan):** +- **Okta** +- **Azure AD** +- **GitHub** + +### Using OAuth Providers + +```javascript +// Initiate OAuth login flow +base44.auth.loginWithProvider('google'); + +// Return to specific page after authentication +base44.auth.loginWithProvider('microsoft', '/dashboard'); + +// Supported values: 'google', 'microsoft', 'facebook' +``` + +--- + +## Environment Availability + +| Environment | Availability | Notes | +|------------|--------------|-------| +| **Frontend** | ✅ Yes | All methods available | +| **Backend Functions** | ✅ Yes | Use `createClientFromRequest(req)` for authenticated client | +| **Service Role** | ❌ No | Auth methods not available in service role context | + +### Frontend Usage +```javascript +// Standard browser usage +const user = await base44.auth.me(); +``` + +### Backend Functions Usage +```javascript +import { createClientFromRequest } from "@base44/sdk"; + +Deno.serve(async (req) => { + const base44 = createClientFromRequest(req); + + // Get user from request context + const user = await base44.auth.me(); + + // User operations here... + return Response.json({ user }); +}); +``` + +--- + +## App Visibility + +Control who can access your app in the app settings: + +### Public Apps +- No login required for basic access +- Users can view public content without authentication +- Authenticated users get additional features/data + +### Private Apps +- Login required to access any content +- Unauthenticated users are automatically redirected to login +- All content is protected by default + +--- + +## Limitations + +### Authentication UI Options +- **Recommended:** Build custom login/signup UI using `loginViaEmailPassword()` and `loginWithProvider()` for full control over user experience and branding +- **Alternative:** `redirectToLogin()` uses Base44's hosted authentication pages with limited customization + +### Hosted Login (via redirectToLogin) +- `redirectToLogin()` shows both login and signup options on the same page +- No separate `redirectToSignup()` method +- Users can switch between login/signup on the hosted page +- ⚠️ **Note:** Prefer building custom login UI for better user experience + +### Password Requirements +- Minimum length and complexity requirements enforced +- Requirements not exposed via API +- Validation errors returned when requirements not met + +### Rate Limiting +- OTP requests are rate-limited to prevent abuse +- Password reset requests are rate-limited +- Login attempts may be rate-limited with Turnstile protection + +### Token Management +- JWTs are automatically stored in localStorage by default +- Token expiration and refresh not exposed in API +- Call `me()` or `isAuthenticated()` to verify token validity + +--- + +## Best Practices + +### 1. Always Handle Errors +```javascript +try { + await base44.auth.loginViaEmailPassword(email, password); +} catch (error) { + // Always handle authentication errors + console.error('Login failed:', error.message); +} +``` + +### 2. Verify Authentication Before Protected Actions +```javascript +const user = await requireAuth(); +if (!user) return; // Will redirect to login +// Proceed with authenticated action +``` + +### 3. Use Type Safety with TypeScript +```typescript +import type { User, LoginResponse } from '@base44/sdk'; + +const user: User = await base44.auth.me(); +const response: LoginResponse = await base44.auth.loginViaEmailPassword(email, password); +``` + +### 4. Don't Hardcode Credentials +```javascript +// ❌ BAD +base44.auth.setToken("hardcoded-token-here"); + +// ✅ GOOD +const token = await secureAuthFlow(); +base44.auth.setToken(token); +``` + +### 5. Provide User Feedback +```javascript +try { + await base44.auth.register(params); + showSuccessMessage('Registration successful! Check your email for verification code.'); +} catch (error) { + showErrorMessage('Registration failed: ' + error.message); +} +``` + +### 6. Handle Token Expiration Gracefully +```javascript +try { + const user = await base44.auth.me(); +} catch (error) { + if (error.status === 401) { + // Clear local state and navigate to login + localStorage.clear(); + navigate('/login'); + } +} +``` + +### 7. Build Custom Login UI (Recommended) +```javascript +// ✅ RECOMMENDED - Custom login form with direct methods +const handleLogin = async (email, password) => { + try { + const { user } = await base44.auth.loginViaEmailPassword(email, password); + navigate('/dashboard'); + } catch (error) { + setError(error.message); + } +}; + +const handleGoogleLogin = () => { + base44.auth.loginWithProvider('google', '/dashboard'); +}; + +// ❌ AVOID - Redirecting to hosted login page +// base44.auth.redirectToLogin(window.location.href); +``` diff --git a/evals/fixtures/sdk-functions/skills/base44-sdk/references/base44-agents.md b/evals/fixtures/sdk-functions/skills/base44-sdk/references/base44-agents.md new file mode 100644 index 0000000..ee55dee --- /dev/null +++ b/evals/fixtures/sdk-functions/skills/base44-sdk/references/base44-agents.md @@ -0,0 +1,364 @@ +# Agents Module + +AI agent conversations and messages via `base44.agents`. + +> **Note:** This module requires a logged-in user. All agent methods work in the context of the authenticated user. + +## Contents +- [Concepts](#concepts) +- [Methods](#methods) +- [Examples](#examples) (Create, Get Conversations, List, Subscribe, Send Message, WhatsApp) +- [Message Structure](#message-structure) +- [Conversation Structure](#conversation-structure) +- [Common Patterns](#common-patterns) + +## Concepts + +- **Conversation**: A dialogue between user and an AI agent. Has unique ID, agent name, user reference, and metadata. +- **Message**: Single message in a conversation. Has role (`user`, `assistant`, `system`), content, timestamps, and optional metadata. + +## Methods + +| Method | Signature | Description | +|--------|-----------|-------------| +| `createConversation(params)` | `Promise` | Create a new conversation with an agent | +| `getConversations()` | `Promise` | Get all user's conversations | +| `getConversation(id)` | `Promise` | Get conversation with messages | +| `listConversations(filterParams)` | `Promise` | Filter/sort/paginate conversations | +| `subscribeToConversation(id, onUpdate?)` | `() => void` | Real-time updates via WebSocket (returns unsubscribe function) | +| `addMessage(conversation, message)` | `Promise` | Send a message | +| `getWhatsAppConnectURL(agentName)` | `string` | Get WhatsApp connection URL for agent | + +## Examples + +### Create Conversation + +```javascript +const conversation = await base44.agents.createConversation({ + agent_name: "support-agent", + metadata: { + order_id: "ORD-123", + category: "billing" + } +}); + +console.log(conversation.id); +``` + +### Get All Conversations + +```javascript +const conversations = await base44.agents.getConversations(); + +conversations.forEach(conv => { + console.log(conv.id, conv.agent_name, conv.created_date); +}); +``` + +### Get Single Conversation (with messages) + +```javascript +const conversation = await base44.agents.getConversation("conv-id-123"); + +console.log(conversation.messages); +``` + +### List with Filters + +```javascript +// Using filterParams object with q, sort, limit, skip, fields +const recent = await base44.agents.listConversations({ + q: { agent_name: "support-agent" }, + sort: "-created_date", + limit: 10, + skip: 0 +}); + +// Filter by metadata +const highPriority = await base44.agents.listConversations({ + q: { + agent_name: "support-agent", + "metadata.priority": "high" + }, + sort: "-updated_date", + limit: 20 +}); +``` + +### Subscribe to Updates (Real-time) + +```javascript +const unsubscribe = base44.agents.subscribeToConversation( + "conv-id-123", + (updatedConversation) => { + // Called when new messages arrive + console.log("New messages:", updatedConversation.messages); + } +); + +// Later: unsubscribe +unsubscribe(); +``` + +### Send a Message + +```javascript +const conversation = await base44.agents.getConversation("conv-id-123"); + +await base44.agents.addMessage(conversation, { + role: "user", + content: "What's the weather like today?" +}); +``` + +### Get WhatsApp Connection URL + +```javascript +const whatsappUrl = base44.agents.getWhatsAppConnectURL("support-agent"); +// Returns URL for users to connect with agent via WhatsApp +console.log(whatsappUrl); +``` + +## Message Structure + +```javascript +{ + role: "user" | "assistant" | "system", + content: "Message text or structured object", + created_date: "2024-01-15T10:30:00Z", + updated_date: "2024-01-15T10:30:00Z", + + // Optional fields + reasoning: { + content: "Agent's reasoning process", + timing: 1500 + }, + tool_calls: [{ + name: "search", + arguments: { query: "weather" }, + result: { ... }, + status: "success" + }], + file_urls: ["https://..."], + usage: { + prompt_tokens: 150, + completion_tokens: 50 + }, + metadata: { ... }, + custom_context: { ... } +} +``` + +## Conversation Structure + +```javascript +{ + id: "conv-id-123", + app_id: "app-id", + agent_name: "support-agent", + created_by_id: "user-id", + created_date: "2024-01-15T10:00:00Z", + updated_date: "2024-01-15T10:30:00Z", + messages: [ ... ], + metadata: { ... } +} +``` + +## Common Patterns + +### Chat Interface + +```javascript +// Load conversation +const conv = await base44.agents.getConversation(conversationId); +setMessages(conv.messages); + +// Subscribe to updates +const unsubscribe = base44.agents.subscribeToConversation(conversationId, (updated) => { + setMessages(updated.messages); +}); + +// Send message +async function sendMessage(text) { + await base44.agents.addMessage(conv, { role: "user", content: text }); +} + +// Cleanup on unmount +return () => unsubscribe(); +``` + +## Type Definitions + +### AgentConversation + +```typescript +/** An agent conversation containing messages exchanged with an AI agent. */ +interface AgentConversation { + /** Unique identifier for the conversation. */ + id: string; + /** Application ID. */ + app_id: string; + /** Name of the agent in this conversation. */ + agent_name: string; + /** ID of the user who created the conversation. */ + created_by_id: string; + /** When the conversation was created. */ + created_date: string; + /** When the conversation was last updated. */ + updated_date: string; + /** Array of messages in the conversation. */ + messages: AgentMessage[]; + /** Optional metadata associated with the conversation. */ + metadata?: Record; +} +``` + +### AgentMessage + +```typescript +/** A message in an agent conversation. */ +interface AgentMessage { + /** Unique identifier for the message. */ + id: string; + /** Role of the message sender. */ + role: "user" | "assistant" | "system"; + /** When the message was created. */ + created_date: string; + /** When the message was last updated. */ + updated_date: string; + /** Message content. */ + content?: string | Record; + /** Optional reasoning information for the message. */ + reasoning?: AgentMessageReasoning | null; + /** URLs to files attached to the message. */ + file_urls?: string[]; + /** Tool calls made by the agent. */ + tool_calls?: AgentMessageToolCall[]; + /** Token usage statistics. */ + usage?: AgentMessageUsage; + /** Whether the message is hidden from the user. */ + hidden?: boolean; + /** Custom context provided with the message. */ + custom_context?: AgentMessageCustomContext[]; + /** Model used to generate the message. */ + model?: string; + /** Checkpoint ID for the message. */ + checkpoint_id?: string; + /** Metadata about when and by whom the message was created. */ + metadata?: AgentMessageMetadata; +} +``` + +### Supporting Types + +```typescript +/** Reasoning information for an agent message. */ +interface AgentMessageReasoning { + /** When reasoning started. */ + start_date: string; + /** When reasoning ended. */ + end_date?: string; + /** Reasoning content. */ + content: string; +} + +/** A tool call made by the agent. */ +interface AgentMessageToolCall { + /** Tool call ID. */ + id: string; + /** Name of the tool called. */ + name: string; + /** Arguments passed to the tool as JSON string. */ + arguments_string: string; + /** Status of the tool call. */ + status: "running" | "success" | "error" | "stopped"; + /** Results from the tool call. */ + results?: string; +} + +/** Token usage statistics for an agent message. */ +interface AgentMessageUsage { + /** Number of tokens in the prompt. */ + prompt_tokens?: number; + /** Number of tokens in the completion. */ + completion_tokens?: number; +} + +/** Custom context provided with an agent message. */ +interface AgentMessageCustomContext { + /** Context message. */ + message: string; + /** Associated data for the context. */ + data: Record; + /** Type of context. */ + type: string; +} + +/** Metadata about when and by whom a message was created. */ +interface AgentMessageMetadata { + /** When the message was created. */ + created_date: string; + /** Email of the user who created the message. */ + created_by_email: string; + /** Full name of the user who created the message. */ + created_by_full_name: string; +} +``` + +### CreateConversationParams + +```typescript +/** Parameters for creating a new conversation. */ +interface CreateConversationParams { + /** The name of the agent to create a conversation with. */ + agent_name: string; + /** Optional metadata to attach to the conversation. */ + metadata?: Record; +} +``` + +### ModelFilterParams + +```typescript +/** Parameters for filtering, sorting, and paginating conversations. */ +interface ModelFilterParams { + /** Query object with field-value pairs for filtering. */ + q?: Record; + /** Sort parameter (e.g., "-created_date" for descending). */ + sort?: string | null; + /** Maximum number of results to return. */ + limit?: number | null; + /** Number of results to skip for pagination. */ + skip?: number | null; + /** Array of field names to include in the response. */ + fields?: string[] | null; +} +``` + +### AgentsModule + +```typescript +/** Agents module for managing AI agent conversations. */ +interface AgentsModule { + /** Gets all conversations from all agents in the app. */ + getConversations(): Promise; + + /** Gets a specific conversation by ID. */ + getConversation(conversationId: string): Promise; + + /** Lists conversations with filtering, sorting, and pagination. */ + listConversations(filterParams: ModelFilterParams): Promise; + + /** Creates a new conversation with an agent. */ + createConversation(conversation: CreateConversationParams): Promise; + + /** Adds a message to a conversation. */ + addMessage(conversation: AgentConversation, message: Partial): Promise; + + /** Subscribes to real-time updates for a conversation. Returns unsubscribe function. */ + subscribeToConversation(conversationId: string, onUpdate?: (conversation: AgentConversation) => void): () => void; + + /** Gets WhatsApp connection URL for an agent. */ + getWhatsAppConnectURL(agentName: string): string; +} +``` diff --git a/evals/fixtures/sdk-functions/skills/base44-sdk/references/client.md b/evals/fixtures/sdk-functions/skills/base44-sdk/references/client.md new file mode 100644 index 0000000..554bf2b --- /dev/null +++ b/evals/fixtures/sdk-functions/skills/base44-sdk/references/client.md @@ -0,0 +1,257 @@ +# Client Setup + +How to create and configure the Base44 client. + +## Contents +- [In Base44-Generated Apps](#in-base44-generated-apps) +- [In External Apps](#in-external-apps) +- [In Backend Functions](#in-backend-functions) +- [Authentication Modes](#authentication-modes) (Anonymous, User, Service Role) +- [Available Modules](#available-modules) +- [Client Methods](#client-methods) +- [Client Configuration Options](#client-configuration-options) + +## In Base44-Generated Apps + +The client is pre-configured and available as `base44`. Just use it: + +```javascript +const tasks = await base44.entities.Task.list(); +``` + +## In External Apps + +Install the SDK and create a client: + +```bash +npm install @base44/sdk +``` + +```javascript +import { createClient } from "@base44/sdk"; + +// IMPORTANT: The parameter name is 'appId' (NOT 'clientId', NOT 'id') +// IMPORTANT: onError must be nested inside 'options' object +const base44 = createClient({ + appId: "your-app-id", // Required: Use 'appId' parameter + token: "optional-user-token", // Optional: for pre-authenticated requests + options: { // Optional: configuration options + onError: (error) => { // Optional: error handler (must be in options) + console.error("Base44 error:", error); + } + } +}); +``` + +**Common Mistakes:** +- ❌ `createClient({ clientId: "..." })` - WRONG parameter name +- ❌ `createClient({ id: "..." })` - WRONG parameter name +- ❌ `createClient({ appId: "...", onError: ... })` - WRONG: onError must be in options +- ✅ `createClient({ appId: "..." })` - CORRECT parameter name +- ✅ `createClient({ appId: "...", options: { onError: ... } })` - CORRECT: onError in options + +## In Backend Functions + +Use `createClientFromRequest` to get a client with the caller's auth context: + +```javascript +import { createClientFromRequest } from "@base44/sdk"; + +Deno.serve(async (req) => { + const base44 = createClientFromRequest(req); + + // Client inherits authentication from the request + const user = await base44.auth.me(); + + return Response.json({ user }); +}); +``` + +## Authentication Modes + +| Mode | How to Get | Permissions | +|------|-----------|-------------| +| **Anonymous** | `createClient({ appId })` without token | Public data only | +| **User** | After `loginViaEmailPassword()` or via `createClientFromRequest` | User's own data | +| **Service Role** | `base44.asServiceRole.*` in backend | Full admin access | + +## Anonymous Mode + +No authentication. Can only access public resources. + +```javascript +const base44 = createClient({ appId: "your-app-id" }); + +// Only works if Task entity allows anonymous read +const publicTasks = await base44.entities.Task.list(); +``` + +## User Mode + +After user logs in, the client automatically includes their token. + +```javascript +const base44 = createClient({ appId: "your-app-id" }); + +// Login sets the token +await base44.auth.loginViaEmailPassword("user@example.com", "password"); + +// Subsequent requests are authenticated +const user = await base44.auth.me(); +const myTasks = await base44.entities.Task.list(); // filtered by permissions +``` + +## Service Role Mode + +Admin-level access. **Backend only.** + +```javascript +// Inside a backend function +Deno.serve(async (req) => { + const base44 = createClientFromRequest(req); + + // User mode - respects permissions + const myTasks = await base44.entities.Task.list(); + + // Service role - bypasses permissions + const allTasks = await base44.asServiceRole.entities.Task.list(); + const allUsers = await base44.asServiceRole.entities.User.list(); + const oauthToken = await base44.asServiceRole.connectors.getAccessToken("slack"); + + return Response.json({ myTasks, allTasks }); +}); +``` + +## Available Modules + +The client exposes these modules: + +```javascript +base44.entities // CRUD operations +base44.auth // Authentication +base44.agents // AI conversations +base44.functions // Backend function invocation +base44.integrations // Third-party services +base44.analytics // Event tracking +base44.appLogs // App usage logging +base44.users // User invitations + +// Service role only (backend) +base44.asServiceRole.entities +base44.asServiceRole.agents +base44.asServiceRole.functions +base44.asServiceRole.integrations +base44.asServiceRole.appLogs +base44.asServiceRole.connectors +``` + +## Client Methods + +The client provides these methods: + +```javascript +// Set authentication token for all subsequent requests +base44.setToken(newToken); + +// Cleanup WebSocket connections (call when done with client) +base44.cleanup(); +``` + +### setToken + +Updates the authentication token for all subsequent API requests and WebSocket connections. + +```javascript +// After receiving a token (e.g., from external auth) +base44.setToken("new-jwt-token"); +``` + +### cleanup + +Disconnects WebSocket connections. Call when you're done with the client or when the component unmounts. + +```javascript +// Cleanup on component unmount (React example) +useEffect(() => { + return () => base44.cleanup(); +}, []); +``` + +## Client Configuration Options + +```javascript +createClient({ + appId: "your-app-id", // Required: MUST use 'appId' (not 'clientId' or 'id') + token: "jwt-token", // Optional: pre-set auth token + options: { // Optional: configuration options + onError: (error) => {} // Optional: global error handler (must be in options) + } +}); +``` + +**⚠️ Critical:** +- The parameter name is `appId`, not `clientId` or `id`. Using the wrong parameter name will cause errors. +- The `onError` handler must be nested inside the `options` object, not at the top level. + +## Type Definitions + +### CreateClientConfig + +```typescript +/** Configuration for creating a Base44 client. */ +interface CreateClientConfig { + /** The Base44 app ID (required). */ + appId: string; + /** User authentication token. Used to authenticate as a specific user. */ + token?: string; + /** Service role authentication token (backend only). */ + serviceToken?: string; + /** Additional client options. */ + options?: CreateClientOptions; +} + +/** Options for creating a Base44 client. */ +interface CreateClientOptions { + /** Optional error handler called whenever an API error occurs. */ + onError?: (error: Error) => void; +} +``` + +### Base44Client + +```typescript +/** The Base44 client instance. */ +interface Base44Client { + /** Entities module for CRUD operations on your data models. */ + entities: EntitiesModule; + /** Integrations module for calling pre-built integration endpoints. */ + integrations: IntegrationsModule; + /** Auth module for user authentication and management. */ + auth: AuthModule; + /** Functions module for invoking custom backend functions. */ + functions: FunctionsModule; + /** Agents module for managing AI agent conversations. */ + agents: AgentsModule; + /** App logs module for tracking app usage. */ + appLogs: AppLogsModule; + /** Analytics module for tracking custom events. */ + analytics: AnalyticsModule; + + /** Cleanup function to disconnect WebSocket connections. */ + cleanup(): void; + + /** Sets a new authentication token for all subsequent requests. */ + setToken(newToken: string): void; + + /** Provides access to modules with elevated service role permissions (backend only). */ + readonly asServiceRole: { + entities: EntitiesModule; + integrations: IntegrationsModule; + connectors: ConnectorsModule; + functions: FunctionsModule; + agents: AgentsModule; + appLogs: AppLogsModule; + cleanup(): void; + }; +} +``` diff --git a/evals/fixtures/sdk-functions/skills/base44-sdk/references/connectors.md b/evals/fixtures/sdk-functions/skills/base44-sdk/references/connectors.md new file mode 100644 index 0000000..f3fbcb4 --- /dev/null +++ b/evals/fixtures/sdk-functions/skills/base44-sdk/references/connectors.md @@ -0,0 +1,113 @@ +# Connectors Module + +OAuth token management for external services. **Service role only** (backend functions). + +## Method + +```javascript +base44.asServiceRole.connectors.getAccessToken(integrationType): Promise +``` + +Returns a raw OAuth access token for the specified service. + +## Supported Services + +| Integration Type | Service | +|-----------------|---------| +| `"googlecalendar"` | Google Calendar | +| `"googledrive"` | Google Drive | +| `"slack"` | Slack | +| `"notion"` | Notion | +| `"salesforce"` | Salesforce | +| `"hubspot"` | HubSpot | +| `"linkedin"` | LinkedIn | +| `"tiktok"` | TikTok | +| `"github"` | GitHub | + +## Example Usage + +```javascript +// Backend function only +Deno.serve(async (req) => { + const base44 = createClientFromRequest(req); + + // Get OAuth token for Slack + const slackToken = await base44.asServiceRole.connectors.getAccessToken("slack"); + + // Use token directly with Slack API + const response = await fetch("https://slack.com/api/chat.postMessage", { + method: "POST", + headers: { + "Authorization": `Bearer ${slackToken}`, + "Content-Type": "application/json" + }, + body: JSON.stringify({ + channel: "#general", + text: "Hello from Base44!" + }) + }); + + return Response.json(await response.json()); +}); +``` + +## Google Calendar Example + +```javascript +Deno.serve(async (req) => { + const base44 = createClientFromRequest(req); + + const token = await base44.asServiceRole.connectors.getAccessToken("googlecalendar"); + + // List upcoming events + const response = await fetch( + "https://www.googleapis.com/calendar/v3/calendars/primary/events?" + + new URLSearchParams({ + maxResults: "10", + orderBy: "startTime", + singleEvents: "true", + timeMin: new Date().toISOString() + }), + { + headers: { "Authorization": `Bearer ${token}` } + } + ); + + const events = await response.json(); + return Response.json(events); +}); +``` + +## Setup Requirements + +1. **Builder plan** or higher +2. **Backend functions** enabled +3. **Connector configured** in Base44 dashboard (OAuth flow completed) + +## Important Notes + +- **One account per connector per app**: All users share the same connected account +- **Backend only**: `connectors` module not available in frontend code +- **Service role required**: Must use `base44.asServiceRole.connectors` +- **You handle the API calls**: Base44 only provides the token; you make the actual API requests +- **Token refresh**: Base44 handles token refresh automatically + +## Type Definitions + +```typescript +/** + * The type of external integration/connector. + * Examples: 'googlecalendar', 'slack', 'github', 'notion', etc. + */ +type ConnectorIntegrationType = string; + +/** Connectors module for managing OAuth tokens for external services. */ +interface ConnectorsModule { + /** + * Retrieves an OAuth access token for a specific external integration type. + * @param integrationType - The type of integration (e.g., 'googlecalendar', 'slack'). + * @returns Promise resolving to the access token string. + */ + getAccessToken(integrationType: ConnectorIntegrationType): Promise; +} +``` diff --git a/evals/fixtures/sdk-functions/skills/base44-sdk/references/entities.md b/evals/fixtures/sdk-functions/skills/base44-sdk/references/entities.md new file mode 100644 index 0000000..a7bdf35 --- /dev/null +++ b/evals/fixtures/sdk-functions/skills/base44-sdk/references/entities.md @@ -0,0 +1,253 @@ +# Entities Module + +CRUD operations on data models. Access via `base44.entities.EntityName.method()`. + +## Contents +- [Methods](#methods) +- [Examples](#examples) (Create, Bulk Create, List, Filter, Get, Update, Delete, Subscribe) +- [User Entity](#user-entity) +- [Service Role Access](#service-role-access) +- [Permissions](#permissions) + +## Methods + +| Method | Signature | Description | +|--------|-----------|-------------| +| `create(data)` | `Promise` | Create one record | +| `bulkCreate(dataArray)` | `Promise` | Create multiple records | +| `list(sort?, limit?, skip?, fields?)` | `Promise` | Get all records (paginated) | +| `filter(query, sort?, limit?, skip?, fields?)` | `Promise` | Get records matching conditions | +| `get(id)` | `Promise` | Get single record by ID | +| `update(id, data)` | `Promise` | Update record (partial update) | +| `delete(id)` | `Promise` | Delete record by ID | +| `deleteMany(query)` | `Promise` | Delete all matching records | +| `importEntities(file)` | `Promise` | Import from CSV (frontend only) | +| `subscribe(callback)` | `() => void` | Subscribe to realtime updates (returns unsubscribe function) | + +## Examples + +### Create + +```javascript +const task = await base44.entities.Task.create({ + title: "Complete documentation", + status: "pending", + dueDate: "2024-12-31" +}); +``` + +### Bulk Create + +```javascript +const tasks = await base44.entities.Task.bulkCreate([ + { title: "Task 1", status: "pending" }, + { title: "Task 2", status: "pending" } +]); +``` + +### List with Pagination + +```javascript +// Get first 10 records, sorted by created_date descending +const tasks = await base44.entities.Task.list( + "-created_date", // sort (string: prefix with - for descending) + 10, // limit + 0 // skip +); + +// Get next page +const page2 = await base44.entities.Task.list("-created_date", 10, 10); +``` + +### Filter + +```javascript +// Simple filter +const pending = await base44.entities.Task.filter({ status: "pending" }); + +// Multiple conditions +const myPending = await base44.entities.Task.filter({ + status: "pending", + assignedTo: userId +}); + +// With sort, limit, skip +const recent = await base44.entities.Task.filter( + { status: "pending" }, + "-created_date", // sort (string: prefix with - for descending) + 5, + 0 +); + +// Select specific fields +const titles = await base44.entities.Task.filter( + { status: "pending" }, + null, + null, + null, + ["id", "title"] +); +``` + +### Get by ID + +```javascript +const task = await base44.entities.Task.get("task-id-123"); +``` + +### Update + +```javascript +// Partial update - only specified fields change +await base44.entities.Task.update("task-id-123", { + status: "completed", + completedAt: new Date().toISOString() +}); +``` + +### Delete + +```javascript +// Single record +await base44.entities.Task.delete("task-id-123"); + +// Multiple records matching query +await base44.entities.Task.deleteMany({ status: "archived" }); +``` + +### Subscribe to Realtime Updates + +```javascript +// Subscribe to all changes on Task entity +const unsubscribe = base44.entities.Task.subscribe((event) => { + console.log(`Task ${event.id} was ${event.type}:`, event.data); + // event.type is "create", "update", or "delete" +}); + +// Later: unsubscribe to stop receiving updates +unsubscribe(); +``` + +**Event structure:** +```javascript +{ + type: "create" | "update" | "delete", + data: { ... }, // the entity data + id: "entity-id", // the affected entity's ID + timestamp: "2024-01-15T10:30:00Z" +} +``` + +## User Entity + +Every app has a built-in `User` entity with special rules: + +- Regular users can only read/update **their own** record +- Cannot create users via `entities.create()` - use `auth.register()` instead +- Service role has full access to all user records + +```javascript +// Get current user's record +const me = await base44.entities.User.get(currentUserId); + +// Service role: get any user +const anyUser = await base44.asServiceRole.entities.User.get(userId); +``` + +## Service Role Access + +For admin-level operations (bypass user permissions): + +```javascript +// Backend only +const allTasks = await base44.asServiceRole.entities.Task.list(); +const allUsers = await base44.asServiceRole.entities.User.list(); +``` + +## Permissions (RLS & FLS) + +Data access is controlled by **Row Level Security (RLS)** and **Field Level Security (FLS)** rules defined in entity schemas. + +1. **Authentication level**: anonymous, authenticated, or service role +2. **RLS rules**: Control which records (rows) users can create/read/update/delete +3. **FLS rules**: Control which fields users can read/write within accessible records + +Operations succeed or fail based on these rules - no partial results. + +RLS and FLS are configured in entity schema files (`base44/entities/*.jsonc`). See [entities-create.md](../../base44-cli/references/entities-create.md#row-level-security-rls) for configuration details. + +**Note:** `asServiceRole` sets the user's role to `"admin"` but does NOT bypass RLS. Your RLS rules must include admin access (e.g., `{ "user_condition": { "role": "admin" } }`) for service role operations to succeed. + +## Type Definitions + +### RealtimeEvent + +```typescript +/** Event types for realtime entity updates. */ +type RealtimeEventType = "create" | "update" | "delete"; + +/** Payload received when a realtime event occurs. */ +interface RealtimeEvent { + /** The type of change that occurred. */ + type: RealtimeEventType; + /** The entity data. */ + data: any; + /** The unique identifier of the affected entity. */ + id: string; + /** ISO 8601 timestamp of when the event occurred. */ + timestamp: string; +} + +/** Callback function invoked when a realtime event occurs. */ +type RealtimeCallback = (event: RealtimeEvent) => void; + +/** Function returned from subscribe, call it to unsubscribe. */ +type Subscription = () => void; +``` + +### EntityHandler + +```typescript +/** Entity handler providing CRUD operations for a specific entity type. */ +interface EntityHandler { + /** Lists records with optional pagination and sorting. */ + list(sort?: string, limit?: number, skip?: number, fields?: string[]): Promise; + + /** Filters records based on a query. */ + filter(query: Record, sort?: string, limit?: number, skip?: number, fields?: string[]): Promise; + + /** Gets a single record by ID. */ + get(id: string): Promise; + + /** Creates a new record. */ + create(data: Record): Promise; + + /** Updates an existing record. */ + update(id: string, data: Record): Promise; + + /** Deletes a single record by ID. */ + delete(id: string): Promise; + + /** Deletes multiple records matching a query. */ + deleteMany(query: Record): Promise; + + /** Creates multiple records in a single request. */ + bulkCreate(data: Record[]): Promise; + + /** Imports records from a file (frontend only). */ + importEntities(file: File): Promise; + + /** Subscribes to realtime updates. Returns unsubscribe function. */ + subscribe(callback: RealtimeCallback): Subscription; +} +``` + +### EntitiesModule + +```typescript +/** Entities module for managing app data. */ +interface EntitiesModule { + /** Access any entity by name dynamically. */ + [entityName: string]: EntityHandler; +} +``` diff --git a/evals/fixtures/sdk-functions/skills/base44-sdk/references/functions.md b/evals/fixtures/sdk-functions/skills/base44-sdk/references/functions.md new file mode 100644 index 0000000..8072465 --- /dev/null +++ b/evals/fixtures/sdk-functions/skills/base44-sdk/references/functions.md @@ -0,0 +1,216 @@ +# Functions Module + +Invoke custom backend functions via `base44.functions`. + +## Contents +- [Method](#method) +- [Invoking Functions](#invoking-functions) (Frontend, File Upload, Service Role, REST API) +- [Writing Backend Functions](#writing-backend-functions) (Basic, Service Role, Secrets, Errors) +- [Setup Requirements](#setup-requirements) +- [Authentication Modes](#authentication-modes) + +## Method + +```javascript +base44.functions.invoke(functionName, data): Promise +``` + +- `functionName`: Name of the backend function +- `data`: Object of parameters (sent as JSON, or multipart if contains File objects) +- Returns: Whatever the function returns + +## Invoking Functions + +### From Frontend + +```javascript +const result = await base44.functions.invoke("processOrder", { + orderId: "order-123", + action: "ship" +}); + +console.log(result); +``` + +### With File Upload + +```javascript +const fileInput = document.querySelector('input[type="file"]'); +const file = fileInput.files[0]; + +// Automatically uses multipart/form-data when File objects present +const result = await base44.functions.invoke("uploadDocument", { + file: file, + category: "invoices" +}); +``` + +### With Service Role (Backend) + +```javascript +// Inside another backend function +const result = await base44.asServiceRole.functions.invoke("adminTask", { + userId: "user-123" +}); +``` + +### Via REST API (curl) + +Functions can be called via HTTP POST to your app domain: + +```bash +curl -X POST "https:///functions/" \ + -H "Content-Type: application/json" \ + -d '{"key": "value"}' +``` + +## Writing Backend Functions + +Backend functions run on Deno. Must export using `Deno.serve()`. + +### Required Directory Structure + +Each function must be in its own subdirectory under `base44/functions/` with a configuration file: + +``` +base44/ + functions/ + process-order/ # kebab-case directory name + function.jsonc # required configuration + index.ts # entry point +``` + +**function.jsonc:** +```jsonc +{ + "name": "process-order", + "entry": "index.ts" +} +``` + +For complete setup and deployment instructions, see [functions-create.md](../../base44-cli/references/functions-create.md) in base44-cli. + +### Basic Structure + +```javascript +// base44/functions/process-order/index.ts +import { createClientFromRequest } from "npm:@base44/sdk"; + +Deno.serve(async (req) => { + // Get authenticated client from request + const base44 = createClientFromRequest(req); + + // Parse input + const { orderId, action } = await req.json(); + + // Your logic here + const order = await base44.entities.Orders.get(orderId); + + // Return response + return Response.json({ + success: true, + order: order + }); +}); +``` + +### With Service Role Access + +```javascript +import { createClientFromRequest } from "npm:@base44/sdk"; + +Deno.serve(async (req) => { + const base44 = createClientFromRequest(req); + + // Check user is authenticated + const user = await base44.auth.me(); + if (!user) { + return Response.json({ error: "Unauthorized" }, { status: 401 }); + } + + // Use service role for admin operations + const allOrders = await base44.asServiceRole.entities.Orders.list(); + + return Response.json({ orders: allOrders }); +}); +``` + +### Using Secrets + +```javascript +Deno.serve(async (req) => { + // Access environment variables (configured in app settings) + const apiKey = Deno.env.get("STRIPE_API_KEY"); + + const response = await fetch("https://api.stripe.com/v1/charges", { + headers: { + "Authorization": `Bearer ${apiKey}` + } + }); + + return Response.json(await response.json()); +}); +``` + +### Error Handling + +```javascript +import { createClientFromRequest } from "npm:@base44/sdk"; + +Deno.serve(async (req) => { + try { + const base44 = createClientFromRequest(req); + const { orderId } = await req.json(); + + const order = await base44.entities.Orders.get(orderId); + if (!order) { + return Response.json( + { error: "Order not found" }, + { status: 404 } + ); + } + + return Response.json({ order }); + + } catch (error) { + return Response.json( + { error: error.message }, + { status: 500 } + ); + } +}); +``` + +## Setup Requirements + +1. Enable Backend Functions in app settings (requires appropriate plan) +2. Create function files in `/functions` folder +3. Configure secrets via app dashboard for API keys + +## Authentication Modes + +| Mode | Context | Permissions | +|------|---------|-------------| +| User | `base44.functions.invoke()` | Runs under calling user's permissions | +| Service Role | `base44.asServiceRole.functions.invoke()` | Admin-level access | + +Inside the function, use `createClientFromRequest(req)` to get a client that inherits the caller's auth context. + +## Type Definitions + +```typescript +/** Functions module for invoking custom backend functions. */ +interface FunctionsModule { + /** + * Invokes a custom backend function by name. + * + * If any parameter is a File object, the request will automatically be + * sent as multipart/form-data. Otherwise, it will be sent as JSON. + * + * @param functionName - The name of the function to invoke. + * @param data - An object containing named parameters for the function. + * @returns Promise resolving to the function's response. + */ + invoke(functionName: string, data: Record): Promise; +} +``` diff --git a/evals/fixtures/sdk-functions/skills/base44-sdk/references/integrations.md b/evals/fixtures/sdk-functions/skills/base44-sdk/references/integrations.md new file mode 100644 index 0000000..f356a18 --- /dev/null +++ b/evals/fixtures/sdk-functions/skills/base44-sdk/references/integrations.md @@ -0,0 +1,377 @@ +# Integrations Module + +Access third-party services via `base44.integrations`. + +## Types of Integrations + +1. **Core/Built-in**: AI, email, file uploads (available by default) +2. **Catalog integrations**: Pre-built connectors from Base44 catalog +3. **Custom integrations**: Your own OpenAPI-based integrations + +## Accessing Integrations + +```javascript +// Core integrations +base44.integrations.Core.FunctionName(params) + +// Custom integrations +base44.integrations.custom.call(slug, operationId, params) +``` + +## Core Integrations + +### InvokeLLM (AI Text Generation) + +Generate text or structured JSON data using AI models. + +```javascript +// Basic prompt - returns string +const response = await base44.integrations.Core.InvokeLLM({ + prompt: "Summarize this text: ..." +}); + +// With internet context (uses Google Search, Maps, News) +const response = await base44.integrations.Core.InvokeLLM({ + prompt: "What's the current weather in NYC?", + add_context_from_internet: true +}); + +// Structured JSON response - returns object +const response = await base44.integrations.Core.InvokeLLM({ + prompt: "Analyze the sentiment of: 'Great product but slow shipping'", + response_json_schema: { + type: "object", + properties: { + sentiment: { type: "string", enum: ["positive", "negative", "mixed"] }, + score: { type: "number", description: "Score from 1-10" }, + key_points: { type: "array", items: { type: "string" } } + } + } +}); +// Returns: { sentiment: "mixed", score: 7, key_points: ["great product", "slow shipping"] } + +// With file attachments (uploaded via UploadFile) +const response = await base44.integrations.Core.InvokeLLM({ + prompt: "Describe what's in this image", + file_urls: ["https://...uploaded_image.png"] +}); +``` + +**Parameters:** +- `prompt` (string, required): The prompt text to send to the model +- `add_context_from_internet` (boolean, optional): If true, uses Google Search/Maps/News for real-time context +- `response_json_schema` (object, optional): JSON schema for structured output +- `file_urls` (string[], optional): URLs of uploaded files for context + +### GenerateImage + +Create AI-generated images from text prompts. + +```javascript +const { url } = await base44.integrations.Core.GenerateImage({ + prompt: "A serene mountain landscape with a lake" +}); +console.log(url); // https://...generated_image.png +``` + +### SendEmail + +Send emails to registered users. Every app gets this integration (no plan upgrade required). + +```javascript +await base44.integrations.Core.SendEmail({ + to: "user@example.com", + subject: "Welcome!", + body: "

Hello

Welcome to our app.

", + from_name: "My App" // optional, defaults to app name +}); +``` + +**Parameters:** +- `to` (string, required): Recipient email address +- `subject` (string, required): Email subject line +- `body` (string, required): Plain text or HTML email body +- `from_name` (string, optional): Sender name displayed to recipient + +**Limitations:** +- 1 credit per email (2 credits with custom domain) + +### UploadFile (Public) + +Upload files to public storage. + +```javascript +const fileInput = document.querySelector('input[type="file"]'); +const { file_url } = await base44.integrations.Core.UploadFile({ + file: fileInput.files[0] +}); +console.log(file_url); // https://...uploaded_file.pdf +``` + +### UploadPrivateFile + +Upload files to private storage that requires a signed URL to access. + +```javascript +const { file_uri } = await base44.integrations.Core.UploadPrivateFile({ + file: fileInput.files[0] +}); +console.log(file_uri); // "private/user123/document.pdf" + +// Create a signed URL to access the file +const { signed_url } = await base44.integrations.Core.CreateFileSignedUrl({ + file_uri, + expires_in: 3600 // URL expires in 1 hour (default: 300 seconds) +}); +console.log(signed_url); // Temporary URL to access the private file +``` + +### CreateFileSignedUrl + +Generate temporary access links for private files. + +```javascript +const { signed_url } = await base44.integrations.Core.CreateFileSignedUrl({ + file_uri: "private/user123/document.pdf", + expires_in: 7200 // 2 hours +}); +``` + +**Parameters:** +- `file_uri` (string, required): URI from UploadPrivateFile +- `expires_in` (number, optional): Expiration time in seconds (default: 300) + +### ExtractDataFromUploadedFile + +Extract structured data from uploaded files using AI. + +```javascript +// First upload the file +const { file_url } = await base44.integrations.Core.UploadFile({ file }); + +// Then extract structured data +const result = await base44.integrations.Core.ExtractDataFromUploadedFile({ + file_url, + json_schema: { + type: "object", + properties: { + invoice_number: { type: "string" }, + total_amount: { type: "number" }, + date: { type: "string" }, + vendor_name: { type: "string" } + } + } +}); +console.log(result); // { invoice_number: "INV-12345", total_amount: 1250.00, ... } +``` + +## Custom Integrations + +Custom integrations allow workspace administrators to connect any external API by importing an OpenAPI specification. Use `base44.integrations.custom.call()` to invoke them. + +### Syntax + +```javascript +const response = await base44.integrations.custom.call( + slug, // Integration identifier (set by admin) + operationId, // Endpoint in "method:path" format + params // Optional: payload, pathParams, queryParams +); +``` + +### Examples + +```javascript +// GET request with query params +const response = await base44.integrations.custom.call( + "my-crm", + "get:/contacts", + { queryParams: { limit: 10, status: "active" } } +); + +// POST request with body +const response = await base44.integrations.custom.call( + "my-crm", + "post:/contacts", + { payload: { name: "John Doe", email: "john@example.com" } } +); + +// Request with path parameters +const response = await base44.integrations.custom.call( + "github", + "post:/repos/{owner}/{repo}/issues", + { + pathParams: { owner: "myorg", repo: "myrepo" }, + payload: { title: "Bug report", body: "Something is broken" } + } +); +``` + +### Response Structure + +```javascript +{ + success: true, // Whether external API returned 2xx + status_code: 200, // HTTP status code + data: { ... } // Response from external API +} +``` + +## Requirements + +- **Core integrations**: Available on all plans +- **Catalog/Custom integrations**: Require Builder plan or higher + +## Type Definitions + +### Core Integration Parameters + +```typescript +/** Parameters for the InvokeLLM function. */ +interface InvokeLLMParams { + /** The prompt text to send to the model. */ + prompt: string; + /** If true, uses Google Search/Maps/News for real-time context. */ + add_context_from_internet?: boolean; + /** JSON schema for structured output. If provided, returns object instead of string. */ + response_json_schema?: object; + /** File URLs (from UploadFile) to provide as context. */ + file_urls?: string[]; +} + +/** Parameters for the GenerateImage function. */ +interface GenerateImageParams { + /** Description of the image to generate. */ + prompt: string; +} + +/** Result from GenerateImage. */ +interface GenerateImageResult { + /** URL of the generated image. */ + url: string; +} + +/** Parameters for the UploadFile function. */ +interface UploadFileParams { + /** The file object to upload. */ + file: File; +} + +/** Result from UploadFile. */ +interface UploadFileResult { + /** URL of the uploaded file. */ + file_url: string; +} + +/** Parameters for the SendEmail function. */ +interface SendEmailParams { + /** Recipient email address. */ + to: string; + /** Email subject line. */ + subject: string; + /** Plain text or HTML email body. */ + body: string; + /** Sender name (defaults to app name). */ + from_name?: string; +} + +/** Parameters for ExtractDataFromUploadedFile. */ +interface ExtractDataFromUploadedFileParams { + /** URL of the uploaded file. */ + file_url: string; + /** JSON schema defining fields to extract. */ + json_schema: object; +} + +/** Parameters for UploadPrivateFile. */ +interface UploadPrivateFileParams { + /** The file object to upload. */ + file: File; +} + +/** Result from UploadPrivateFile. */ +interface UploadPrivateFileResult { + /** URI of the private file (used for signed URLs). */ + file_uri: string; +} + +/** Parameters for CreateFileSignedUrl. */ +interface CreateFileSignedUrlParams { + /** URI from UploadPrivateFile. */ + file_uri: string; + /** Expiration time in seconds (default: 300). */ + expires_in?: number; +} + +/** Result from CreateFileSignedUrl. */ +interface CreateFileSignedUrlResult { + /** Temporary signed URL to access the file. */ + signed_url: string; +} +``` + +### CoreIntegrations + +```typescript +/** Core package containing built-in Base44 integration functions. */ +interface CoreIntegrations { + InvokeLLM(params: InvokeLLMParams): Promise; + GenerateImage(params: GenerateImageParams): Promise; + UploadFile(params: UploadFileParams): Promise; + SendEmail(params: SendEmailParams): Promise; + ExtractDataFromUploadedFile(params: ExtractDataFromUploadedFileParams): Promise; + UploadPrivateFile(params: UploadPrivateFileParams): Promise; + CreateFileSignedUrl(params: CreateFileSignedUrlParams): Promise; +} +``` + +### Custom Integrations + +```typescript +/** Parameters for calling a custom integration endpoint. */ +interface CustomIntegrationCallParams { + /** Request body payload. */ + payload?: Record; + /** Path parameters to substitute in the URL. */ + pathParams?: Record; + /** Query string parameters. */ + queryParams?: Record; + /** Additional headers for this request. */ + headers?: Record; +} + +/** Response from a custom integration call. */ +interface CustomIntegrationCallResponse { + /** Whether the external API returned a 2xx status code. */ + success: boolean; + /** The HTTP status code from the external API. */ + status_code: number; + /** The response data from the external API. */ + data: any; +} + +/** Module for calling custom workspace-level API integrations. */ +interface CustomIntegrationsModule { + /** + * Call a custom integration endpoint. + * @param slug - The integration's unique identifier. + * @param operationId - The operation ID from the OpenAPI spec. + * @param params - Optional payload, pathParams, queryParams, headers. + */ + call(slug: string, operationId: string, params?: CustomIntegrationCallParams): Promise; +} +``` + +### IntegrationsModule + +```typescript +/** Integrations module for calling integration endpoints. */ +type IntegrationsModule = { + /** Core package with built-in integrations. */ + Core: CoreIntegrations; + /** Custom integrations module. */ + custom: CustomIntegrationsModule; + /** Additional integration packages (dynamic). */ + [packageName: string]: any; +}; +``` diff --git a/evals/fixtures/sdk-functions/skills/base44-sdk/references/users.md b/evals/fixtures/sdk-functions/skills/base44-sdk/references/users.md new file mode 100644 index 0000000..4205b06 --- /dev/null +++ b/evals/fixtures/sdk-functions/skills/base44-sdk/references/users.md @@ -0,0 +1,82 @@ +# Users Module + +Invite users to the app via `base44.users`. + +## Contents +- [Methods](#methods) +- [Examples](#examples) (Invite User) +- [Roles](#roles) +- [Notes](#notes) + +## Methods + +| Method | Signature | Description | +|--------|-----------|-------------| +| `inviteUser(user_email, role)` | `Promise` | Invite a user to the app | + +## Examples + +### Invite User + +```javascript +// Invite a user with "user" role +await base44.users.inviteUser("newuser@example.com", "user"); + +// Invite an admin +await base44.users.inviteUser("admin@example.com", "admin"); +``` + +### Invite Multiple Users + +```javascript +const usersToInvite = [ + { email: "user1@example.com", role: "user" }, + { email: "user2@example.com", role: "user" }, + { email: "manager@example.com", role: "admin" } +]; + +for (const user of usersToInvite) { + await base44.users.inviteUser(user.email, user.role); + console.log(`Invited ${user.email} as ${user.role}`); +} +``` + +## Roles + +The `role` parameter must be one of: + +| Role | Description | +|------|-------------| +| `"user"` | Standard user with default permissions | +| `"admin"` | Administrator with elevated permissions | + +**Note:** Only `"user"` and `"admin"` are valid role values. An error will be thrown if you pass any other value. + +## Notes + +- **Email invitation**: The invited user receives an email with a link to join the app +- **Duplicate handling**: Inviting an existing user will re-send the invitation +- **Also available in auth**: `base44.auth.inviteUser()` provides the same functionality +- **Role validation**: Only `"user"` or `"admin"` are accepted + +```javascript +// These are equivalent: +await base44.users.inviteUser("newuser@example.com", "user"); +await base44.auth.inviteUser("newuser@example.com", "user"); +``` + +## Type Definitions + +```typescript +/** Users module for inviting users to the app. */ +interface UsersModule { + /** + * Invite a user to the application. + * @param user_email - User's email address. + * @param role - User's role ('user' or 'admin'). + * @returns Promise resolving when the invitation is sent. + * @throws Error if role is not 'user' or 'admin'. + */ + inviteUser(user_email: string, role: "user" | "admin"): Promise; +} +``` diff --git a/evals/fixtures/sdk-integrations/eval.json b/evals/fixtures/sdk-integrations/eval.json new file mode 100644 index 0000000..bb5722e --- /dev/null +++ b/evals/fixtures/sdk-integrations/eval.json @@ -0,0 +1,127 @@ +{ + "$schema": "../../eval.schema.json", + "name": "sdk-integrations", + "description": "Test SDK integration patterns: LLM, email, upload, image generation", + "prompts": [ + { + "name": "sdk-integrations-llm", + "description": "Use LLM integration", + "prompt": "Create an AISummary component in src/components/AISummary.jsx that takes a text prop and generates a summary using the Base44 SDK's LLM integration. Use the correct integration method to invoke the LLM and display the result.", + "expectedSkills": [ + "base44-sdk" + ], + "checks": [ + { + "type": "file-exists", + "filePath": "src/components/AISummary.jsx", + "description": "src/components/AISummary.jsx exists" + }, + { + "type": "file-content", + "filePath": "src/components/AISummary.jsx", + "pattern": "integrations\\.Core\\.InvokeLLM", + "description": "Matches expected pattern in AISummary.jsx" + }, + { + "type": "file-content", + "filePath": "src/components/AISummary.jsx", + "pattern": "import.*base44|from ['\"].*base44", + "description": "Imports base44 client" + }, + { + "type": "file-content-excluded", + "filePath": "src/components/AISummary.jsx", + "pattern": "ai\\.generate|openai\\.chat|llm\\(", + "description": "Does not use hallucinated ai.generate" + } + ] + }, + { + "name": "sdk-integrations-email", + "description": "Send email via integration", + "prompt": "Create a SendNotification component in src/components/SendNotification.jsx that sends an email notification using the Base44 SDK. Include form fields for recipient (to), subject, and body. Use the correct integration method to send the email.", + "expectedSkills": [ + "base44-sdk" + ], + "checks": [ + { + "type": "file-exists", + "filePath": "src/components/SendNotification.jsx", + "description": "src/components/SendNotification.jsx exists" + }, + { + "type": "file-content", + "filePath": "src/components/SendNotification.jsx", + "pattern": "integrations\\.Core\\.SendEmail", + "description": "Matches expected pattern in SendNotification.jsx" + }, + { + "type": "file-content-excluded", + "filePath": "src/components/SendNotification.jsx", + "pattern": "sendEmail\\(|email\\.send\\(", + "description": "Does not use hallucinated sendEmail" + } + ] + }, + { + "name": "sdk-integrations-upload", + "description": "Upload file via integration", + "prompt": "Create a FileUploader component in src/components/FileUploader.jsx that lets users upload files using the Base44 SDK. Include a file input, handle the upload using the correct integration method, and display the uploaded file URL.", + "expectedSkills": [ + "base44-sdk" + ], + "checks": [ + { + "type": "file-exists", + "filePath": "src/components/FileUploader.jsx", + "description": "src/components/FileUploader.jsx exists" + }, + { + "type": "file-content", + "filePath": "src/components/FileUploader.jsx", + "pattern": "integrations\\.Core\\.UploadFile", + "description": "Matches expected pattern in FileUploader.jsx" + }, + { + "type": "file-content-excluded", + "filePath": "src/components/FileUploader.jsx", + "pattern": "storage\\.upload|uploadFile\\(", + "description": "Does not use hallucinated storage.upload" + } + ] + }, + { + "name": "sdk-integrations-image", + "description": "Generate image via integration", + "prompt": "Create an ImageGenerator component in src/components/ImageGenerator.jsx that generates images from text prompts using the Base44 SDK. Include a text input for the prompt, use the correct integration method to generate the image, and display the result.", + "expectedSkills": [ + "base44-sdk" + ], + "checks": [ + { + "type": "file-exists", + "filePath": "src/components/ImageGenerator.jsx", + "description": "src/components/ImageGenerator.jsx exists" + }, + { + "type": "file-content", + "filePath": "src/components/ImageGenerator.jsx", + "pattern": "integrations\\.Core\\.GenerateImage", + "description": "Matches expected pattern in ImageGenerator.jsx" + }, + { + "type": "file-content", + "filePath": "src/components/ImageGenerator.jsx", + "pattern": "import.*base44|from ['\"].*base44", + "description": "Imports base44 client" + }, + { + "type": "file-content-excluded", + "filePath": "src/components/ImageGenerator.jsx", + "pattern": "ai\\.generateImage|dalle|openai", + "description": "Does not use hallucinated ai.generateImage" + } + ] + } + ] +} diff --git a/evals/fixtures/sdk-integrations/project/.claude/settings.json b/evals/fixtures/sdk-integrations/project/.claude/settings.json new file mode 100644 index 0000000..ab60505 --- /dev/null +++ b/evals/fixtures/sdk-integrations/project/.claude/settings.json @@ -0,0 +1,6 @@ +{ + "permissions": { + "allow": ["Read", "Write", "Edit", "Bash", "Glob", "Grep"] + }, + "skills": ["./skills"] +} diff --git a/evals/fixtures/sdk-integrations/project/AGENTS.md b/evals/fixtures/sdk-integrations/project/AGENTS.md new file mode 100644 index 0000000..6c0f2be --- /dev/null +++ b/evals/fixtures/sdk-integrations/project/AGENTS.md @@ -0,0 +1,25 @@ +IMPORTANT: Prefer retrieval-led reasoning over pre-training-led reasoning. Base44 is NOT Firebase, Supabase, or any other SDK you know. Every API name below is the ONLY correct form. Always verify against this index and the referenced docs. + +[Base44 SDK Docs Index]|root: ./skills/base44-sdk/references +|entities.md: CRUD - create(data), list(), filter(query), get(id), update(id,data), delete(id), subscribe(cb) +|auth.md: Auth - loginViaEmailPassword(), loginWithProvider('google'), me(), register(), logout() +|base44-agents.md: Agents - createConversation({agent_name}), addMessage(conv,{role,content}), subscribeToConversation(id,cb) +|functions.md: Functions - invoke(name, data) +|integrations.md: Core - InvokeLLM({prompt}), SendEmail({to,subject,body}), UploadFile({file}) +|client.md: Client - createClient({appId}), import from "@base44/sdk" +|QUICK_REFERENCE.md: All method signatures + +[Base44 CLI Docs Index]|root: ./skills/base44-cli/references +|entities-create.md: Entity schema - {name:"PascalCase", type:"object", properties:{}, required:[]} +|functions-create.md: Function - function.jsonc {name,entry} + index.ts with Deno.serve + createClientFromRequest +|create.md: Project creation - npx base44 create name -p . +|deploy.md: Deploy - npx base44 deploy -y +|agents-push.md: Agent config - {name:"snake_case", description, instructions, tool_configs:[{entity_name,allowed_operations}|{function_name,description}]} + +[Common Mistakes - NEVER use these hallucinated names] +|SDK: Do NOT use find() → use filter(), findOne() → get(), insert() → create(), remove() → delete(), onChange() → subscribe() +|SDK: Do NOT use signInWithGoogle() → use loginWithProvider('google'), functions.call() → functions.invoke() +|SDK: Do NOT use ai.generate() → use integrations.Core.InvokeLLM({prompt}) +|CLI: Entity names must be PascalCase alphanumeric. File names must be kebab-case.jsonc +|CLI: Agent names must be lowercase with underscores only. Function entry field is "entry" not "entrypoint" +|CLI: Backend functions import from "npm:@base44/sdk" (Deno requires npm: prefix) diff --git a/evals/fixtures/sdk-integrations/project/CLAUDE.md b/evals/fixtures/sdk-integrations/project/CLAUDE.md new file mode 100644 index 0000000..724ce4d --- /dev/null +++ b/evals/fixtures/sdk-integrations/project/CLAUDE.md @@ -0,0 +1,7 @@ +# Base44 Development Guidelines + +When working with Base44 projects, first explore the existing project structure to understand what already exists. Look at existing code, config files, and patterns in use. + +Then consult the AGENTS.md index in the project root for correct API patterns. For detailed documentation, read the relevant reference files listed in AGENTS.md. + +IMPORTANT: Prefer retrieval-led reasoning over pre-training-led reasoning. Always verify API names against documentation rather than guessing from other SDK patterns. diff --git a/evals/fixtures/sdk-integrations/project/base44/config.jsonc b/evals/fixtures/sdk-integrations/project/base44/config.jsonc new file mode 100644 index 0000000..a132b8d --- /dev/null +++ b/evals/fixtures/sdk-integrations/project/base44/config.jsonc @@ -0,0 +1,10 @@ +{ + "name": "Integrations Test App", + "description": "A test application for SDK integrations", + "entitiesDir": "./entities", + "functionsDir": "./functions", + "site": { + "buildCommand": "npm run build", + "outputDirectory": "dist" + } +} diff --git a/evals/fixtures/sdk-integrations/project/package.json b/evals/fixtures/sdk-integrations/project/package.json new file mode 100644 index 0000000..37969ca --- /dev/null +++ b/evals/fixtures/sdk-integrations/project/package.json @@ -0,0 +1,20 @@ +{ + "name": "integrations-test-app", + "version": "0.1.0", + "private": true, + "type": "module", + "scripts": { + "dev": "vite", + "build": "vite build" + }, + "dependencies": { + "@base44/sdk": "^1.0.0", + "react": "^18.2.0", + "react-dom": "^18.2.0" + }, + "devDependencies": { + "@vitejs/plugin-react": "^4.2.1", + "base44": "latest", + "vite": "^5.2.0" + } +} diff --git a/evals/fixtures/sdk-integrations/project/src/App.jsx b/evals/fixtures/sdk-integrations/project/src/App.jsx new file mode 100644 index 0000000..5553fc1 --- /dev/null +++ b/evals/fixtures/sdk-integrations/project/src/App.jsx @@ -0,0 +1,11 @@ +import React from 'react'; + +function App() { + return ( +
+

Auth Test App

+
+ ); +} + +export default App; diff --git a/evals/fixtures/sdk-integrations/project/src/api/base44Client.js b/evals/fixtures/sdk-integrations/project/src/api/base44Client.js new file mode 100644 index 0000000..7887147 --- /dev/null +++ b/evals/fixtures/sdk-integrations/project/src/api/base44Client.js @@ -0,0 +1,5 @@ +import { createClient } from "@base44/sdk"; + +const base44 = createClient({ appId: "test-app" }); + +export default base44; diff --git a/evals/fixtures/sdk-integrations/skills/base44-cli/SKILL.md b/evals/fixtures/sdk-integrations/skills/base44-cli/SKILL.md new file mode 100644 index 0000000..3c33f05 --- /dev/null +++ b/evals/fixtures/sdk-integrations/skills/base44-cli/SKILL.md @@ -0,0 +1,384 @@ +--- +name: base44-cli +description: "The base44 CLI is used for EVERYTHING related to base44 projects: resource configuration (entities, backend functions, ai agents), initialization and actions (resource creation, deployment). This skill is the place for learning about how to configure resources. When you plan or implement a feature, you must learn this skill" +--- + +# Base44 CLI + +Create and manage Base44 apps (projects) using the Base44 CLI tool. + +## ⚡ IMMEDIATE ACTION REQUIRED - Read This First + +This skill activates on ANY mention of "base44" or when a `base44/` folder exists. **DO NOT read documentation files or search the web before acting.** + +**Your first action MUST be:** +1. Check if `base44/config.jsonc` exists in the current directory +2. If **NO** (new project scenario): + - This skill (base44-cli) handles the request + - Guide user through project initialization + - Do NOT activate base44-sdk yet +3. If **YES** (existing project scenario): + - Transfer to base44-sdk skill for implementation + - This skill only handles CLI commands (login, deploy, entities push) + +## Critical: Local Installation Only + +NEVER call `base44` directly. The CLI is installed locally as a dev dependency and must be accessed via a package manager: + +- `npx base44 ` (npm - recommended) +- `yarn base44 ` (yarn) +- `pnpm base44 ` (pnpm) + +WRONG: `base44 login` +RIGHT: `npx base44 login` + +## MANDATORY: Authentication Check at Session Start + +**CRITICAL**: At the very start of every AI session when this skill is activated, you MUST: + +1. **Check authentication status** by running: + ```bash + npx base44 whoami + ``` + +2. **If the user is logged in** (command succeeds and shows an email): + - Continue with the requested task + +3. **If the user is NOT logged in** (command fails or shows an error): + - **STOP immediately** + - **DO NOT proceed** with any CLI operations + - **Ask the user to login manually** by running: + ```bash + npx base44 login + ``` + - Wait for the user to confirm they have logged in before continuing + +**This check is mandatory and must happen before executing any other Base44 CLI commands.** + +## Overview + +The Base44 CLI provides command-line tools for authentication, creating projects, managing entities, and deploying Base44 applications. It is framework-agnostic and works with popular frontend frameworks like Vite, Next.js, and Create React App, Svelte, Vue, and more. + +## When to Use This Skill vs base44-sdk + +**Use base44-cli when:** +- Creating a **NEW** Base44 project from scratch +- Initializing a project in an empty directory +- Directory is missing `base44/config.jsonc` +- User mentions: "create a new project", "initialize project", "setup a project", "start a new Base44 app" +- Deploying, pushing entities, or authenticating via CLI +- Working with CLI commands (`npx base44 ...`) + +**Use base44-sdk when:** +- Building features in an **EXISTING** Base44 project +- `base44/config.jsonc` already exists +- Writing JavaScript/TypeScript code using Base44 SDK +- Implementing functionality, components, or features +- User mentions: "implement", "build a feature", "add functionality", "write code" + +**Skill Dependencies:** +- `base44-cli` is a **prerequisite** for `base44-sdk` in new projects +- If user wants to "create an app" and no Base44 project exists, use `base44-cli` first +- `base44-sdk` assumes a Base44 project is already initialized + +**State Check Logic:** +Before selecting a skill, check: +- IF (user mentions "create/build app" OR "make a project"): + - IF (directory is empty OR no `base44/config.jsonc` exists): + → Use **base44-cli** (project initialization needed) + - ELSE: + → Use **base44-sdk** (project exists, build features) + +## Project Structure + +A Base44 project combines a standard frontend project with a `base44/` configuration folder: + +``` +my-app/ +├── base44/ # Base44 configuration (created by CLI) +│ ├── config.jsonc # Project settings, site config +│ ├── entities/ # Entity schema definitions +│ │ ├── task.jsonc +│ │ └── board.jsonc +│ ├── functions/ # Backend functions (optional) +│ │ └── my-function/ +│ │ ├── function.jsonc +│ │ └── index.ts +│ └── agents/ # Agent configurations (optional) +│ └── support_agent.jsonc +├── src/ # Frontend source code +│ ├── api/ +│ │ └── base44Client.js # Base44 SDK client +│ ├── pages/ +│ ├── components/ +│ └── main.jsx +├── index.html # SPA entry point +├── package.json +└── vite.config.js # Or your framework's config +``` + +**Key files:** +- `base44/config.jsonc` - Project name, description, site build settings +- `base44/entities/*.jsonc` - Data model schemas (see Entity Schema section) +- `base44/agents/*.jsonc` - Agent configurations (optional) +- `src/api/base44Client.js` - Pre-configured SDK client for frontend use + +**config.jsonc example:** +```jsonc +{ + "name": "My App", // Required: project name + "description": "App description", // Optional: project description + "entitiesDir": "./entities", // Optional: default "entities" + "functionsDir": "./functions", // Optional: default "functions" + "agentsDir": "./agents", // Optional: default "agents" + "site": { // Optional: site deployment config + "installCommand": "npm install", // Optional: install dependencies + "buildCommand": "npm run build", // Optional: build command + "serveCommand": "npm run dev", // Optional: local dev server + "outputDirectory": "./dist" // Optional: build output directory + } +} +``` + +**Config properties:** + +| Property | Description | Default | +|----------|-------------|---------| +| `name` | Project name (required) | - | +| `description` | Project description | - | +| `entitiesDir` | Directory for entity schemas | `"entities"` | +| `functionsDir` | Directory for backend functions | `"functions"` | +| `agentsDir` | Directory for agent configs | `"agents"` | +| `site.installCommand` | Command to install dependencies | - | +| `site.buildCommand` | Command to build the project | - | +| `site.serveCommand` | Command to run dev server | - | +| `site.outputDirectory` | Build output directory for deployment | - | + +## Installation + +Install the Base44 CLI as a dev dependency in your project: + +```bash +npm install --save-dev base44 +``` + +**Important:** Never assume or hardcode the `base44` package version. Always install without a version specifier to get the latest version. + +Then run commands using `npx`: + +```bash +npx base44 +``` + +**Note:** All commands in this documentation use `npx base44`. You can also use `yarn base44`, or `pnpm base44` if preferred. + +## Available Commands + +### Authentication + +| Command | Description | Reference | +| --------------- | ----------------------------------------------- | ------------------------------------------- | +| `base44 login` | Authenticate with Base44 using device code flow | [auth-login.md](references/auth-login.md) | +| `base44 logout` | Logout from current device | [auth-logout.md](references/auth-logout.md) | +| `base44 whoami` | Display current authenticated user | [auth-whoami.md](references/auth-whoami.md) | + +### Project Management + +| Command | Description | Reference | +|---------|-------------|-----------| +| `base44 create` | Create a new Base44 project from a template | [create.md](references/create.md) ⚠️ **MUST READ** | +| `base44 link` | Link an existing local project to Base44 | [link.md](references/link.md) | +| `base44 dashboard` | Open the app dashboard in your browser | [dashboard.md](references/dashboard.md) | + +### Deployment + +| Command | Description | Reference | +|---------|-------------|-----------| +| `base44 deploy` | Deploy all resources (entities, functions, site) | [deploy.md](references/deploy.md) | + +### Entity Management + +| Action / Command | Description | Reference | +| ---------------------- | ------------------------------------------- | --------------------------------------------------- | +| Create Entities | Define entities in `base44/entities` folder | [entities-create.md](references/entities-create.md) | +| `base44 entities push` | Push local entities to Base44 | [entities-push.md](references/entities-push.md) | + +#### Entity Schema (Quick Reference) + +ALWAYS follow this exact structure when creating entity files: + +**File naming:** `base44/entities/{kebab-case-name}.jsonc` (e.g., `team-member.jsonc` for `TeamMember`) + +**Schema template:** +```jsonc +{ + "name": "EntityName", + "type": "object", + "properties": { + "field_name": { + "type": "string", + "description": "Field description" + } + }, + "required": ["field_name"] +} +``` + +**Field types:** `string`, `number`, `integer`, `boolean`, `array`, `object`, `binary` +**String formats:** `date`, `date-time`, `time`, `email`, `uri`, `hostname`, `ipv4`, `ipv6`, `uuid`, `file`, `regex`, `richtext` +**For enums:** Add `"enum": ["value1", "value2"]` and optionally `"default": "value1"` +**Entity names:** Must be alphanumeric only (pattern: `/^[a-zA-Z0-9]+$/`) + +For complete documentation, see [entities-create.md](references/entities-create.md). + +### Function Management + +| Action / Command | Description | Reference | +| ------------------------- | --------------------------------------------- | ------------------------------------------------------- | +| Create Functions | Define functions in `base44/functions` folder | [functions-create.md](references/functions-create.md) | +| `base44 functions deploy` | Deploy local functions to Base44 | [functions-deploy.md](references/functions-deploy.md) | + +### Agent Management + +Agents are conversational AI assistants that can interact with users, access your app's entities, and call backend functions. Use these commands to manage agent configurations. + +| Action / Command | Description | Reference | +| ----------------------- | --------------------------------------- | ----------------------------------------------- | +| Create Agents | Define agents in `base44/agents` folder | See Agent Schema below | +| `base44 agents pull` | Pull remote agents to local files | [agents-pull.md](references/agents-pull.md) | +| `base44 agents push` | Push local agents to Base44 | [agents-push.md](references/agents-push.md) | + +**Note:** Agent commands perform full synchronization - pushing replaces all remote agents with local ones, and pulling replaces all local agents with remote ones. + +#### Agent Schema (Quick Reference) + +**File naming:** `base44/agents/{agent_name}.jsonc` (e.g., `support_agent.jsonc`) + +**Schema template:** +```jsonc +{ + "name": "agent_name", + "description": "Brief description of what this agent does", + "instructions": "Detailed instructions for the agent's behavior", + "tool_configs": [ + // Entity tool - gives agent access to entity operations + { "entity_name": "tasks", "allowed_operations": ["read", "create", "update", "delete"] }, + // Backend function tool - gives agent access to a function + { "function_name": "send_email", "description": "Send an email notification" } + ], + "whatsapp_greeting": "Hello! How can I help you today?" +} +``` + +**Naming rules:** +- Agent names must match pattern: `/^[a-z0-9_]+$/` (lowercase alphanumeric with underscores, 1-100 chars) +- Valid: `support_agent`, `order_bot` +- Invalid: `Support-Agent`, `OrderBot` + +**Required fields:** `name`, `description`, `instructions` +**Optional fields:** `tool_configs` (defaults to `[]`), `whatsapp_greeting` + +**Tool config types:** +- **Entity tools**: `entity_name` + `allowed_operations` (array of: `read`, `create`, `update`, `delete`) +- **Backend function tools**: `function_name` + `description` + +### Site Deployment + +| Command | Description | Reference | +| -------------------- | ----------------------------------------- | ------------------------------------------- | +| `base44 site deploy` | Deploy built site files to Base44 hosting | [site-deploy.md](references/site-deploy.md) | + +**SPA only**: Base44 hosting supports Single Page Applications with a single `index.html` entry point. All routes are served from `index.html` (client-side routing). + +## Quick Start + +1. Install the CLI in your project: + ```bash + npm install --save-dev base44 + ``` + +2. Authenticate with Base44: + ```bash + npx base44 login + ``` + +3. Create a new project (ALWAYS provide name and `--path` flag): + ```bash + npx base44 create my-app -p . + ``` + +4. Build and deploy everything: + ```bash + npm run build + npx base44 deploy -y + ``` + +Or deploy individual resources: +- `npx base44 entities push` - Push entities only +- `npx base44 functions deploy` - Deploy functions only +- `npx base44 agents push` - Push agents only +- `npx base44 site deploy -y` - Deploy site only + +## Common Workflows + +### Creating a New Project + +**⚠️ MANDATORY: Before running `base44 create`, you MUST read [create.md](references/create.md) for:** +- **Template selection** - Choose the correct template (`backend-and-client` vs `backend-only`) +- **Correct workflow** - Different templates require different setup steps +- **Common pitfalls** - Avoid folder creation errors that cause failures + +Failure to follow the create.md instructions will result in broken project scaffolding. + +### Linking an Existing Project +```bash +# If you have base44/config.jsonc but no .app.jsonc +npx base44 link --create --name my-app +``` + +### Deploying All Changes +```bash +# Build your project first +npm run build + +# Deploy everything (entities, functions, and site) +npx base44 deploy -y +``` + +### Deploying Individual Resources +```bash +# Push only entities +npx base44 entities push + +# Deploy only functions +npx base44 functions deploy + +# Push only agents +npx base44 agents push + +# Deploy only site +npx base44 site deploy -y +``` + +### Opening the Dashboard +```bash +# Open app dashboard in browser +npx base44 dashboard +``` + +## Authentication + +Most commands require authentication. If you're not logged in, the CLI will automatically prompt you to login. Your session is stored locally and persists across CLI sessions. + +## Troubleshooting + +| Error | Solution | +| --------------------------- | ----------------------------------------------------------------------------------- | +| Not authenticated | Run `npx base44 login` first | +| No entities found | Ensure entities exist in `base44/entities/` directory | +| Entity not recognized | Ensure file uses kebab-case naming (e.g., `team-member.jsonc` not `TeamMember.jsonc`) | +| No functions found | Ensure functions exist in `base44/functions/` with valid `function.jsonc` configs | +| No agents found | Ensure agents exist in `base44/agents/` directory with valid `.jsonc` configs | +| Invalid agent name | Agent names must be lowercase alphanumeric with underscores only | +| No site configuration found | Check that `site.outputDirectory` is configured in project config | +| Site deployment fails | Ensure you ran `npm run build` first and the build succeeded | diff --git a/evals/fixtures/sdk-integrations/skills/base44-cli/references/agents-pull.md b/evals/fixtures/sdk-integrations/skills/base44-cli/references/agents-pull.md new file mode 100644 index 0000000..6a700f0 --- /dev/null +++ b/evals/fixtures/sdk-integrations/skills/base44-cli/references/agents-pull.md @@ -0,0 +1,73 @@ +# base44 agents pull + +Pull AI agent configurations from Base44 to local files. Agents are conversational AI assistants that can interact with users, access your app's entities, and call backend functions. + +## Syntax + +```bash +npx base44 agents pull +``` + +## Authentication + +**Required**: Yes. If not authenticated, you'll be prompted to login first. + +## What It Does + +1. Fetches all agents from Base44 +2. Writes agent files to the `base44/agents/` directory +3. Deletes local agent files that don't exist remotely +4. Reports written and deleted agents + +## Prerequisites + +- Must be run from a Base44 project directory +- Project must be linked to a Base44 app + +## Output + +```bash +$ npx base44 agents pull + +Fetching agents from Base44... +✓ Agents fetched successfully + +Writing agent files... +✓ Agent files written successfully + +Written: support_agent, order_bot +Deleted: old_agent + +Pulled 2 agents to base44/agents +``` + +## Agent Synchronization + +The pull operation synchronizes remote agents to your local files: + +- **Written**: Agent files created or updated from remote +- **Deleted**: Local agent files removed (didn't exist remotely) + +**Warning**: This operation replaces all local agent configurations with remote versions. Any local changes not pushed to Base44 will be overwritten. + +## Error Handling + +If no agents exist on Base44: +```bash +$ npx base44 agents pull +No agents found on Base44 +``` + +## Use Cases + +- Sync agent configurations to a new development machine +- Get the latest agent configurations from your team +- Restore local agent files after accidental deletion +- Start working on an existing project with agents + +## Notes + +- This command syncs agent configurations, not conversation data +- Agent files are stored as `.jsonc` in the `base44/agents/` directory +- The directory location is configurable via `agentsDir` in `config.jsonc` +- Use `base44 agents push` to upload local changes to Base44 diff --git a/evals/fixtures/sdk-integrations/skills/base44-cli/references/agents-push.md b/evals/fixtures/sdk-integrations/skills/base44-cli/references/agents-push.md new file mode 100644 index 0000000..fa2795b --- /dev/null +++ b/evals/fixtures/sdk-integrations/skills/base44-cli/references/agents-push.md @@ -0,0 +1,156 @@ +# base44 agents push + +Push local AI agent configurations to Base44. Agents are conversational AI assistants that can interact with users, access your app's entities, and call backend functions. + +## Syntax + +```bash +npx base44 agents push +``` + +## Authentication + +**Required**: Yes. If not authenticated, you'll be prompted to login first. + +## What It Does + +1. Reads all agent files from the `base44/agents/` directory +2. Validates agent configurations +3. Displays the count of agents to be pushed +4. Uploads agents to the Base44 backend +5. Reports the results: created, updated, and deleted agents + +## Prerequisites + +- Must be run from a Base44 project directory +- Project must have agent definitions in the `base44/agents/` folder + +## Output + +```bash +$ npx base44 agents push + +Found 2 agents to push +Pushing agents to Base44... + +Created: support_agent +Updated: order_bot +Deleted: old_agent + +✓ Agents pushed to Base44 +``` + +## Agent Synchronization + +The push operation synchronizes your local agents with Base44: + +- **Created**: New agents that didn't exist in Base44 +- **Updated**: Existing agents with modified configuration +- **Deleted**: Agents that were removed from your local configuration + +**Warning**: This is a full sync operation. Agents removed locally will be deleted from Base44. + +## Error Handling + +If no agents are found in your project: +```bash +$ npx base44 agents push +No local agents found - this will delete all remote agents +``` + +If an agent has an invalid name: +```bash +$ npx base44 agents push +Error: Agent name must be lowercase alphanumeric with underscores +``` + +## Agent Configuration Schema + +Each agent file should be a `.jsonc` file in `base44/agents/` with this structure: + +```jsonc +{ + "name": "agent_name", // Required: lowercase alphanumeric with underscores, 1-100 chars + "description": "Brief description of what this agent does", // Required: min 1 char + "instructions": "Detailed instructions for the agent's behavior", // Required: min 1 char + "tool_configs": [ // Optional: defaults to [] + // Entity tool - gives agent access to entity operations + { "entity_name": "Task", "allowed_operations": ["read", "create", "update", "delete"] }, + // Backend function tool - gives agent access to a function + { "function_name": "send_email", "description": "Send an email notification" } + ], + "whatsapp_greeting": "Hello! How can I help you today?" // Optional +} +``` + +**Naming rules:** +- **Agent names** must match pattern: `/^[a-z0-9_]+$/` (lowercase alphanumeric with underscores only, 1-100 characters) + - Valid: `support_agent`, `order_bot`, `task_helper` + - Invalid: `Support-Agent`, `OrderBot`, `task helper` +- **Agent file names** must use underscores (matching the agent name) + - Valid: `support_agent.jsonc`, `order_bot.jsonc` + - Invalid: `support-agent.jsonc` (hyphens not allowed) +- **Entity names in `tool_configs`** must use PascalCase (matching the entity's `name` field) + - Valid: `"entity_name": "Task"`, `"entity_name": "TeamMember"` + - Invalid: `"entity_name": "task"`, `"entity_name": "team_member"` + +**Required fields:** +- `name`: Required, must follow naming rules above +- `description`: Required, minimum 1 character +- `instructions`: Required, minimum 1 character +- `tool_configs`: Optional, defaults to empty array +- `whatsapp_greeting`: Optional + +### Common Mistake: Wrong tool_configs Format + +**WRONG** - Do NOT use `tools` with `type` and `entity`: +```jsonc +{ + "name": "my_agent", + "tools": [ // ❌ WRONG + { "type": "entity_query", "entity": "Task" } + ] +} +``` + +**CORRECT** - Use `tool_configs` with `entity_name` and `allowed_operations`: +```jsonc +{ + "name": "my_agent", + "tool_configs": [ // ✅ CORRECT + { "entity_name": "Task", "allowed_operations": ["read"] } + ] +} +``` + +### Best Practices for Agent Instructions + +When giving agents access to entities, be explicit in the instructions about using the tools: + +```jsonc +{ + "name": "support_agent", + "instructions": "You are a helpful support agent.\n\nIMPORTANT: You have access to customer data through entity tools. When users ask about their orders or account:\n1. ALWAYS use the Order entity tool to query their order history\n2. Use the Customer entity tool to look up account details\n3. Analyze the data and provide personalized responses\n\nAlways query the relevant entities first before answering questions about user data.", + "tool_configs": [ + { "entity_name": "Order", "allowed_operations": ["read"] }, + { "entity_name": "Customer", "allowed_operations": ["read"] } + ] +} +``` + +Without explicit instructions to use the entity tools, the agent may not proactively query user data when asked. + +## Use Cases + +- After defining new agents in your project +- When modifying existing agent configurations +- To sync agent changes before testing +- As part of your development workflow when agent behavior changes + +## Notes + +- This command syncs the agent configuration, not conversation data +- Changes are applied to your Base44 project immediately +- Make sure to test agent changes in a development environment first +- Agent definitions are located in the `base44/agents/` directory +- Use `base44 agents pull` to download agents from Base44 diff --git a/evals/fixtures/sdk-integrations/skills/base44-cli/references/auth-login.md b/evals/fixtures/sdk-integrations/skills/base44-cli/references/auth-login.md new file mode 100644 index 0000000..adebd27 --- /dev/null +++ b/evals/fixtures/sdk-integrations/skills/base44-cli/references/auth-login.md @@ -0,0 +1,54 @@ +# base44 login + +Authenticate with Base44 using device code flow. + +## Syntax + +```bash +npx base44 login +``` + +## Authentication + +**Required**: No (this is the login command itself) + +## How It Works + +The login command uses OAuth 2.0 device code flow for authentication: + +1. Generates a device code for authentication +2. Displays a verification code and verification URI +3. Directs you to visit the URI and enter the code +4. Polls for authentication completion (up to device code expiration) +5. Retrieves access and refresh tokens upon successful authentication +6. Fetches and displays your user information +7. Saves authentication data locally with expiration timestamp + +## Interactive Flow + +```bash +$ npx base44 login + +Please visit: https://auth.base44.com/device +Enter code: ABCD-EFGH + +Waiting for authentication... +✓ Successfully authenticated! + +Logged in as: user@example.com +``` + +## Session Management + +- Authentication tokens are stored locally on your device +- Tokens include expiration timestamps +- The session persists across CLI sessions +- Other commands will automatically use your stored credentials +- Use `npx base44 logout` to clear your session +- Use `npx base44 whoami` to check your current authentication status + +## Notes + +- You only need to login once per device +- If your session expires, you'll be prompted to login again when running authenticated commands +- The CLI automatically prompts for login when you run commands that require authentication diff --git a/evals/fixtures/sdk-integrations/skills/base44-cli/references/auth-logout.md b/evals/fixtures/sdk-integrations/skills/base44-cli/references/auth-logout.md new file mode 100644 index 0000000..33c2ddf --- /dev/null +++ b/evals/fixtures/sdk-integrations/skills/base44-cli/references/auth-logout.md @@ -0,0 +1,32 @@ +# base44 logout + +Logout from current device and clear stored authentication data. + +## Syntax + +```bash +npx base44 logout +``` + +## Authentication + +**Required**: No + +## What It Does + +- Deletes stored authentication data from your device +- Clears your local session +- Removes access and refresh tokens + +## Output + +```bash +$ npx base44 logout +Logged out successfully +``` + +## Notes + +- You can logout even if you're not currently logged in (no error) +- After logout, you'll need to run `npx base44 login` again to use authenticated commands +- This only affects the current device; your Base44 account remains active diff --git a/evals/fixtures/sdk-integrations/skills/base44-cli/references/auth-whoami.md b/evals/fixtures/sdk-integrations/skills/base44-cli/references/auth-whoami.md new file mode 100644 index 0000000..abe450c --- /dev/null +++ b/evals/fixtures/sdk-integrations/skills/base44-cli/references/auth-whoami.md @@ -0,0 +1,37 @@ +# base44 whoami + +Display the currently authenticated user. + +## Syntax + +```bash +npx base44 whoami +``` + +## Authentication + +**Required**: Yes. If not authenticated, you'll be prompted to login first. + +## What It Does + +- Reads stored authentication data +- Displays the email of the currently logged-in user + +## Output + +```bash +$ npx base44 whoami +Logged in as: user@example.com +``` + +## Use Cases + +- Verify you're logged in before running other commands +- Check which account you're currently using +- Confirm authentication is working properly +- Useful in scripts or automation to verify credentials + +## Notes + +- If you're not logged in, the command will prompt you to authenticate first +- The email displayed matches your Base44 account email diff --git a/evals/fixtures/sdk-integrations/skills/base44-cli/references/create.md b/evals/fixtures/sdk-integrations/skills/base44-cli/references/create.md new file mode 100644 index 0000000..7580645 --- /dev/null +++ b/evals/fixtures/sdk-integrations/skills/base44-cli/references/create.md @@ -0,0 +1,111 @@ +# base44 create + +Creates a new Base44 project from a template. This command is framework-agnostic and can either scaffold a complete project or add Base44 configuration to an existing project. + +## Critical: Non-Interactive Mode Required + +ALWAYS provide both the project name AND `--path` flag. Without both, the command opens an interactive TUI which agents cannot use properly. + +WRONG: `npx base44 create` +WRONG: `npx base44 create my-app` +RIGHT: `npx base44 create my-app -p ./my-app` + +## Syntax + +```bash +npx base44 create [name] --path [options] +``` + +## Arguments & Options + +| Argument/Option | Description | Required | +|--------|-------------|----------| +| `name` | Project name (positional argument) | Yes* | +| `-p, --path ` | Path where to create the project | Yes* | +| `-t, --template ` | Template ID (see templates below) | No | +| `--deploy` | Build and deploy the site (includes pushing entities) | No | +| `--no-skills` | Skip AI agent skills installation (skills are added by default) | No | + +*Required for non-interactive mode. Both `name` and `--path` must be provided together. + +## Template Selection (CRITICAL - Choose Appropriately) + +**You MUST select the most appropriate template based on user requirements:** + +| Template ID | When to Use | Example Scenarios | +|-------------|-------------|-------------------| +| `backend-and-client` | Creating a NEW full-stack web app from scratch | "Create a task app", "Build me a dashboard", "Make a SaaS app" | +| `backend-only` | Adding Base44 to an EXISTING project OR using a different framework (Next.js, Vue, Svelte, etc.) | "Add Base44 to my project", "I want to use Next.js", "I already have a frontend" | + +**Default Choice:** When the user asks to "create an app" or "build a project" without specifying a particular framework, use `backend-and-client` to provide a complete, production-ready application with Vite + React + Tailwind. + +## The `--path` Flag + +- **For `backend-and-client` template (new projects):** Use a new subfolder path + ```bash + npx base44 create my-app -p ./my-app -t backend-and-client + ``` +- **For `backend-only` template (existing projects):** Use `-p .` in the current directory + ```bash + npx base44 create my-app -p . + ``` + +## Workflow: Using `backend-only` with External Frameworks + +**CRITICAL: The project folder MUST exist BEFORE running `base44 create` with `backend-only`** + +The `backend-only` template only adds Base44 configuration files - it does NOT create a frontend. If you need a frontend with a specific framework: + +```bash +# Step 1: Initialize the frontend project FIRST +npm create vite@latest my-app -- --template react # or vue, svelte, etc. +# OR: npx create-next-app@latest my-app +# OR: any other framework's init command + +# Step 2: Navigate into the created folder +cd my-app + +# Step 3: Install Base44 CLI +npm install --save-dev base44 + +# Step 4: Add Base44 configuration +npx base44 create my-app -p . +``` + +**WARNING:** Do NOT: +- Create an empty folder manually, then try to run `npx create vite` inside it (will fail - folder exists) +- Run `base44 create` with `backend-only` expecting it to create a frontend (it won't) + +**DO:** +- Run the external framework's init command FIRST (it creates its own folder) +- Then run `base44 create` inside that folder with `-p .` + +## Examples + +```bash +# RECOMMENDED: Create full-stack project (for new apps) +npx base44 create my-app -p ./my-app -t backend-and-client + +# Create full-stack and deploy in one step +npx base44 create my-app -p ./my-app -t backend-and-client --deploy + +# Add Base44 to EXISTING project (must be inside the project folder) +npx base44 create my-app -p . + +# Add Base44 to existing project and deploy +npx base44 create my-app -p . --deploy + +# Create without adding AI agent skills +npx base44 create my-app -p . --no-skills +``` + +## What It Does + +1. Applies the selected template to the target path +2. Creates a `base44/` folder with configuration files +3. Registers the project with Base44 backend +4. Creates `base44/.app.jsonc` with the app ID +5. If `--deploy` is used: + - Pushes any entities defined in `base44/entities/` + - Runs install and build commands (for templates with frontend) + - Deploys the site to Base44 hosting diff --git a/evals/fixtures/sdk-integrations/skills/base44-cli/references/dashboard.md b/evals/fixtures/sdk-integrations/skills/base44-cli/references/dashboard.md new file mode 100644 index 0000000..95a534a --- /dev/null +++ b/evals/fixtures/sdk-integrations/skills/base44-cli/references/dashboard.md @@ -0,0 +1,35 @@ +# base44 dashboard + +Opens the Base44 app dashboard in your default web browser. + +## Syntax + +```bash +npx base44 dashboard +``` + +## Options + +This command has no options. + +## What It Does + +1. Reads the project's app ID from `base44/.app.jsonc` +2. Opens the dashboard URL in your default browser + +## Example + +```bash +# Open dashboard for current project +npx base44 dashboard +``` + +## Requirements + +- Must be run from a linked Base44 project directory (contains `base44/.app.jsonc`) +- Must be authenticated (run `npx base44 login` first) + +## Notes + +- The dashboard provides a web interface to manage your app's entities, functions, users, and settings +- If you're not in a project directory, the command will fail with an error diff --git a/evals/fixtures/sdk-integrations/skills/base44-cli/references/deploy.md b/evals/fixtures/sdk-integrations/skills/base44-cli/references/deploy.md new file mode 100644 index 0000000..500526f --- /dev/null +++ b/evals/fixtures/sdk-integrations/skills/base44-cli/references/deploy.md @@ -0,0 +1,86 @@ +# base44 deploy + +Deploys all project resources (entities, functions, and site) to Base44 in a single command. + +## Syntax + +```bash +npx base44 deploy [options] +``` + +## Options + +| Option | Description | +|--------|-------------| +| `-y, --yes` | Skip confirmation prompt | + +## What It Deploys + +The command automatically detects and deploys: + +1. **Entities** - All `.jsonc` files in `base44/entities/` +2. **Functions** - All functions in `base44/functions/` +3. **Agents** - All agent configurations in `base44/agents/` +4. **Site** - Built files from `site.outputDirectory` (if configured) + +## Examples + +```bash +# Interactive mode - shows what will be deployed and asks for confirmation +npx base44 deploy + +# Non-interactive - skip confirmation (for CI/CD or agent use) +npx base44 deploy -y +``` + +## Typical Workflow + +```bash +# 1. Make your changes (entities, functions, frontend code) + +# 2. Build the frontend (if you have one) +npm run build + +# 3. Deploy everything +npx base44 deploy -y +``` + +## What It Does + +1. Reads project configuration from `base44/config.jsonc` +2. Detects available resources (entities, functions, site) +3. Shows a summary of what will be deployed +4. Asks for confirmation (unless `-y` flag is used) +5. Deploys all resources in sequence: + - Pushes entity schemas + - Deploys functions + - Pushes agent configurations + - Uploads site files +6. Displays the dashboard URL and app URL (if site was deployed) + +## Requirements + +- Must be run from a linked Base44 project directory +- Must be authenticated (run `npx base44 login` first) +- For site deployment, must run `npm run build` first + +## Output + +After successful deployment: +- **Dashboard**: Link to your app's management dashboard +- **App URL**: Your deployed site's public URL (if site was included) + +## Notes + +- If no resources are found, the command exits with a message +- Use individual commands (`entities push`, `functions deploy`, `site deploy`) if you only want to deploy specific resources +- The site must be built before deployment - this command does not run `npm run build` for you + +## Related Commands + +| Command | Description | +|---------|-------------| +| `base44 entities push` | Push only entities | +| `base44 functions deploy` | Deploy only functions | +| `base44 agents push` | Push only agents | +| `base44 site deploy` | Deploy only the site | diff --git a/evals/fixtures/sdk-integrations/skills/base44-cli/references/entities-create.md b/evals/fixtures/sdk-integrations/skills/base44-cli/references/entities-create.md new file mode 100644 index 0000000..29b40aa --- /dev/null +++ b/evals/fixtures/sdk-integrations/skills/base44-cli/references/entities-create.md @@ -0,0 +1,552 @@ +# Creating Entities + +Base44 entities are defined locally in your project and then pushed to the Base44 backend. + +## Critical: File Naming + +Entity files MUST use kebab-case naming: `{kebab-case-name}.jsonc` + +| Entity Name | File Name | +|-------------|-----------| +| `Task` | `task.jsonc` | +| `TeamMember` | `team-member.jsonc` | +| `ActivityLog` | `activity-log.jsonc` | + +WRONG: `TeamMember.jsonc`, `teamMember.jsonc` +RIGHT: `team-member.jsonc` + +## Table of Contents + +- [Creating Entities](#creating-entities) + - [Entity Directory](#entity-directory) + - [How to Create an Entity](#how-to-create-an-entity) + - [Entity Schema Structure](#entity-schema-structure) + - [Supported Field Types](#supported-field-types) + - [Field Properties](#field-properties) + - [Complete Example](#complete-example) + - [Naming Conventions](#naming-conventions) + - [Relationships Between Entities](#relationships-between-entities) + - [Row Level Security (RLS)](#row-level-security-rls) + - [Field Level Security (FLS)](#field-level-security-fls) + - [Pushing Entities](#pushing-entities) + +## Entity Directory + +All entity definitions must be placed in the `base44/entities/` folder in your project root. Each entity is defined in its own `.jsonc` file. + +Example structure: +``` +my-app/ + base44/ + entities/ + user.jsonc + product.jsonc + order.jsonc +``` + +## How to Create an Entity + +1. Create a new `.jsonc` file in the `base44/entities/` directory +2. Define your entity schema following the structure below +3. Push the changes to Base44 using the CLI + +## Entity Schema Structure + +Each entity file follows a JSON Schema-like structure: + +```jsonc +{ + "name": "EntityName", // PascalCase entity name + "type": "object", // Always "object" + "properties": { + // Define your fields here + }, + "required": ["field1"] // Array of required field names +} +``` + +### Common Mistake: Nested Schema Property + +**WRONG** - Do NOT wrap properties in a `schema` object: +```jsonc +{ + "name": "Task", + "description": "A task entity", + "schema": { // ❌ WRONG - don't use nested "schema" + "type": "object", + "properties": { ... } + } +} +``` + +**CORRECT** - Put `type` and `properties` at the top level: +```jsonc +{ + "name": "Task", + "description": "A task entity", + "type": "object", // ✅ CORRECT - top level + "properties": { ... } // ✅ CORRECT - top level +} +``` + +This is a common mistake that will cause "Invalid schema: Schema must have a 'type' field" errors when pushing entities. + +## Supported Field Types + +### String + +Basic text field: +```jsonc +{ + "title": { + "type": "string", + "description": "Task title" + } +} +``` + +With format: +```jsonc +{ + "due_date": { + "type": "string", + "format": "date", + "description": "Due date" + } +} +``` + +Available formats: `date`, `date-time`, `time`, `email`, `uri`, `hostname`, `ipv4`, `ipv6`, `uuid`, `file`, `regex`, `richtext` + +### String with Enum + +Constrained to specific values: +```jsonc +{ + "status": { + "type": "string", + "enum": ["todo", "in_progress", "done"], + "default": "todo", + "description": "Current status" + } +} +``` + +### Number + +```jsonc +{ + "position": { + "type": "number", + "description": "Position for ordering" + } +} +``` + +### Integer + +For whole numbers only: +```jsonc +{ + "quantity": { + "type": "integer", + "description": "Item quantity", + "minimum": 0, + "maximum": 1000 + } +} +``` + +### Binary + +For file/blob data: +```jsonc +{ + "attachment": { + "type": "binary", + "description": "File attachment" + } +} +``` + +### Boolean + +```jsonc +{ + "notify_on_change": { + "type": "boolean", + "default": true, + "description": "Enable notifications" + } +} +``` + +### Array of Strings + +```jsonc +{ + "labels": { + "type": "array", + "items": { "type": "string" }, + "description": "Task labels/tags" + } +} +``` + +### Array of Objects + +```jsonc +{ + "attachments": { + "type": "array", + "description": "File attachments", + "items": { + "type": "object", + "properties": { + "name": { "type": "string" }, + "url": { "type": "string" }, + "type": { "type": "string" } + } + } + } +} +``` + +## Field Properties + +| Property | Description | +| ------------- | ---------------------------------------------------------------------------------------- | +| `type` | Data type: `string`, `number`, `integer`, `boolean`, `array`, `object`, `binary` | +| `description` | Human-readable description of the field | +| `enum` | Array of allowed values (for strings) | +| `enumNames` | Human-readable labels for enum values (same order as `enum`) | +| `default` | Default value when not provided | +| `format` | Format hint: `date`, `date-time`, `time`, `email`, `uri`, `hostname`, `ipv4`, `ipv6`, `uuid`, `file`, `regex`, `richtext` | +| `items` | Schema for array items | +| `properties` | Nested properties for object types | +| `$ref` | Reference to another schema definition | +| `minLength` | Minimum string length | +| `maxLength` | Maximum string length | +| `pattern` | Regex pattern for string validation | +| `minimum` | Minimum value for numbers | +| `maximum` | Maximum value for numbers | +| `rls` | Field-level security rules (see Field Level Security section) | + +## Complete Example + +Here's a complete entity definition for a Task: + +```jsonc +{ + "name": "Task", + "type": "object", + "properties": { + "title": { + "type": "string", + "description": "Task title" + }, + "description": { + "type": "string", + "description": "Task description" + }, + "status": { + "type": "string", + "enum": ["todo", "in_progress", "done"], + "default": "todo", + "description": "Current status of the task" + }, + "board_id": { + "type": "string", + "description": "Board this task belongs to" + }, + "assignee_email": { + "type": "string", + "description": "Email of assigned user" + }, + "priority": { + "type": "string", + "enum": ["low", "medium", "high"], + "default": "medium", + "description": "Task priority" + }, + "due_date": { + "type": "string", + "format": "date", + "description": "Due date" + }, + "labels": { + "type": "array", + "items": { "type": "string" }, + "description": "Task labels/tags" + } + }, + "required": ["title"] +} +``` + +## Naming Conventions + +- **Entity name**: Use PascalCase with alphanumeric characters only (e.g., `Task`, `TeamMember`, `ActivityLog`) + - Must match pattern: `/^[a-zA-Z0-9]+$/` + - Valid: `Task`, `TeamMember`, `Order123` + - Invalid: `Team_Member`, `Team-Member`, `Team Member` +- **File name**: Use kebab-case matching the entity (e.g., `task.jsonc`, `team-member.jsonc`, `activity-log.jsonc`) +- **Field names**: Use snake_case (e.g., `board_id`, `user_email`, `due_date`) + +## Relationships Between Entities + +To create relationships between entities, use ID reference fields: + +```jsonc +{ + "board_id": { + "type": "string", + "description": "Board this task belongs to" + }, + "team_id": { + "type": "string", + "description": "Associated team ID" + } +} +``` + +## Row Level Security (RLS) + +Row Level Security (RLS) controls which records users can access based on their identity and attributes. RLS rules are defined per entity inside the `rls` field of the schema. + +**Important:** If no RLS is defined, all records are accessible to all users. + +### RLS Operations + +RLS supports five operations: + +| Operation | Description | +|-----------|-------------| +| `create` | Control who can add new records | +| `read` | Control who can view records | +| `update` | Control who can modify records | +| `delete` | Control who can remove records | +| `write` | Shorthand for `create`, `update`, and `delete` combined | + +### Permission Values + +Each operation accepts one of the following values: + +1. **`true`** - Allow all users (including anonymous/unauthenticated) +2. **`false`** - Block all users +3. **Condition object** - Allow users matching the condition + +### Template Variables + +Use template variables to reference the current user's attributes: + +| Template | Description | +|----------|-------------| +| `{{user.id}}` | The user's ID | +| `{{user.email}}` | The user's email | +| `{{user.role}}` | The user's role | +| `{{user.data.field_name}}` | Custom field from the user's `data` object | + +### Built-in Entity Attributes + +Every entity record has these built-in attributes available for RLS rules: + +| Attribute | Description | +|-----------|-------------| +| `id` | Unique record identifier | +| `created_date` | Timestamp when record was created | +| `updated_date` | Timestamp when record was last updated | +| `created_by` | Email of the user who created the record | + +### Rule Types + +There are two condition types you can use: + +**1. Entity-to-user comparison** - Compare record fields to the current user's values: +```jsonc +{ + "created_by": "{{user.email}}" +} +``` + +**2. User condition check** - Check user properties directly using `user_condition`: +```jsonc +{ + "user_condition": { "role": "admin" } +} +``` + +**Important limitations:** +- `user_condition` only supports **simple equality** (e.g., `{ "role": "admin" }`) +- For `data.*` field comparisons, you can use operators: `$in`, `$nin`, `$ne`, `$all` +- Logical operators `$or`, `$and`, `$nor` are available for combining conditions +- You cannot filter by entity field values directly (e.g., `{"status": "published"}`) +- Only user-related conditions are allowed + +### RLS Examples + +**Owner-only access:** +```jsonc +{ + "created_by": "{{user.email}}" +} +``` + +**Department-based access:** +```jsonc +{ + "data.department": "{{user.data.department}}" +} +``` + +**Admin-only access:** +```jsonc +{ + "user_condition": { "role": "admin" } +} +``` + +**Complete RLS configuration:** +```jsonc +{ + "name": "Task", + "type": "object", + "properties": { + "title": { + "type": "string", + "description": "Task title" + }, + "status": { + "type": "string", + "enum": ["todo", "in_progress", "done"], + "default": "todo" + } + }, + "required": ["title"], + "rls": { + "create": true, + "read": { "created_by": "{{user.email}}" }, + "update": { "created_by": "{{user.email}}" }, + "delete": { "created_by": "{{user.email}}" } + } +} +``` + +### Common RLS Patterns + +**Public create, admin-only management (e.g., contact forms, waitlists):** +```jsonc +{ + "rls": { + "create": true, + "read": { "user_condition": { "role": "admin" } }, + "update": { "user_condition": { "role": "admin" } }, + "delete": { "user_condition": { "role": "admin" } } + } +} +``` + +**Owner-only access:** +```jsonc +{ + "rls": { + "create": true, + "read": { "created_by": "{{user.email}}" }, + "update": { "created_by": "{{user.email}}" }, + "delete": { "created_by": "{{user.email}}" } + } +} +``` + +**Logged-in users only:** +```jsonc +{ + "rls": { + "create": { "user_condition": { "id": "{{user.id}}" } }, + "read": true, + "update": { "created_by": "{{user.email}}" }, + "delete": { "created_by": "{{user.email}}" } + } +} +``` + +### Limitations + +- **user_condition is equality only:** `user_condition` only supports exact match (e.g., `{ "role": "admin" }`) - no operators +- **No comparison operators on user_condition:** `$gt`, `$lt`, `$regex`, `$expr`, `$where` are NOT supported for user conditions +- **No entity field filtering:** Cannot filter by entity field values (e.g., `{"status": "published"}`) +- **No deeply nested templates:** Templates like `{{user.data.profile.department}}` may not work + +**Supported operators:** +- **Logical operators:** `$or`, `$and`, `$nor` for combining multiple conditions +- **Field operators (for `data.*` fields only):** `$in`, `$nin`, `$ne`, `$all` + +### Complex Access Patterns + +For complex access patterns that require multiple conditions (e.g., "owner OR admin"), you have two options: + +1. **Use the Base44 Dashboard UI** - The dashboard allows adding multiple rules per operation with OR logic +2. **Use separate entities** - Split data into multiple entities with different access rules +3. **Use backend functions** - Implement custom access logic in backend functions + +## Field Level Security (FLS) + +Field Level Security allows you to control access to individual fields within an entity. FLS rules are defined within each field's schema using the `rls` property. + +### FLS Operations + +FLS supports the same operations as entity-level RLS: + +| Operation | Description | +|-----------|-------------| +| `create` | Control who can set this field when creating records | +| `read` | Control who can view this field | +| `update` | Control who can modify this field | +| `delete` | Control who can clear this field | +| `write` | Shorthand for `create`, `update`, and `delete` combined | + +### FLS Example + +```jsonc +{ + "name": "Employee", + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "Employee name" + }, + "salary": { + "type": "number", + "description": "Employee salary", + "rls": { + "read": { "user_condition": { "role": "hr" } }, + "update": { "user_condition": { "role": "hr" } } + } + }, + "department": { + "type": "string", + "description": "Department name" + } + }, + "required": ["name"] +} +``` + +In this example, only users with the `hr` role can read or update the `salary` field. All users with access to the entity can read/update other fields. + +### FLS Notes + +- If no field-level RLS is defined, the field inherits the entity-level RLS rules +- FLS rules follow the same condition format as entity-level RLS +- Use FLS for sensitive fields like salary, SSN, or internal notes + +## Pushing Entities + +The `entities push` command will push all entities that exist in the `base44/entities` folder. + +```bash +npx base44 entities push +``` + +For more details on the push command, see [entities-push.md](entities-push.md). diff --git a/evals/fixtures/sdk-integrations/skills/base44-cli/references/entities-push.md b/evals/fixtures/sdk-integrations/skills/base44-cli/references/entities-push.md new file mode 100644 index 0000000..63b92d7 --- /dev/null +++ b/evals/fixtures/sdk-integrations/skills/base44-cli/references/entities-push.md @@ -0,0 +1,71 @@ +# base44 entities push + +Push local entity definitions to Base44. + +## Syntax + +```bash +npx base44 entities push +``` + +## Authentication + +**Required**: Yes. If not authenticated, you'll be prompted to login first. + +## What It Does + +1. Pushes all entities that exist in the `base44/entities` folder +2. Validates that entities exist in the folder +3. Displays the count of entities to be pushed +4. Uploads entities to the Base44 backend +5. Reports the results: created, updated, and deleted entities + +## Prerequisites + +- Must be run from a Base44 project directory +- Project must have entity definitions in the `base44/entities` folder + +## Output + +```bash +$ npx base44 entities push + +Found 3 entities to push +Pushing entities to Base44... + +Created: User, Post +Updated: Comment +Deleted: OldEntity + +✓ Entities pushed successfully +``` + +## Entity Synchronization + +The push operation synchronizes your local entity schema with Base44: + +- **Created**: New entities that didn't exist in Base44 +- **Updated**: Existing entities with modified schema or configuration +- **Deleted**: Entities that were removed from your local configuration + +## Error Handling + +If no entities are found in your project: +```bash +$ npx base44 entities push +No entities found in project +``` + +## Use Cases + +- After defining new entities in your project +- When modifying existing entity schemas +- To sync entity changes before deploying +- As part of your development workflow when data models change + +## Notes + +- This command syncs the entity schema/structure, not the actual data +- Changes are applied to your Base44 project immediately +- Make sure to test entity changes in a development environment first +- Entity definitions are located in the `base44/entities/` directory diff --git a/evals/fixtures/sdk-integrations/skills/base44-cli/references/functions-create.md b/evals/fixtures/sdk-integrations/skills/base44-cli/references/functions-create.md new file mode 100644 index 0000000..c523cdc --- /dev/null +++ b/evals/fixtures/sdk-integrations/skills/base44-cli/references/functions-create.md @@ -0,0 +1,229 @@ +# Creating Functions + +Base44 functions are serverless backend functions that run on Deno. They are defined locally in your project and deployed to the Base44 backend. + +## Function Directory + +All function definitions must be placed in the `base44/functions/` folder in your project. Each function lives in its own subdirectory with a configuration file and entry point. + +Example structure: +``` +my-app/ + base44/ + functions/ + process-order/ + function.jsonc + index.ts + send-notification/ + function.jsonc + index.ts +``` + +## How to Create a Function + +1. Create a new directory in `base44/functions/` with your function name (use kebab-case) +2. Create a `function.jsonc` configuration file in the directory +3. Create the entry point file (e.g., `index.ts`) +4. Deploy the function using the CLI + +## Function Configuration + +Each function requires a `function.jsonc` configuration file: + +```jsonc +{ + "name": "my-function", + "entry": "index.ts" +} +``` + +### Configuration Properties + +| Property | Description | Required | +|----------|-------------|----------| +| `name` | Function name (must match `/^[^.]+$/` - no dots allowed) | Yes | +| `entry` | Entry point file path relative to the function directory (min 1 char) | Yes | + +## Entry Point File + +Functions run on Deno and must export using `Deno.serve()`. Use `npm:` prefix for npm packages. + +```typescript +import { createClientFromRequest } from "npm:@base44/sdk"; + +Deno.serve(async (req) => { + // Get authenticated client from request + const base44 = createClientFromRequest(req); + + // Parse input + const { orderId, action } = await req.json(); + + // Your logic here + const order = await base44.entities.Orders.get(orderId); + + // Return response + return Response.json({ + success: true, + order: order + }); +}); +``` + +### Request Object + +The function receives a standard Deno `Request` object: +- `req.json()` - Parse JSON body +- `req.text()` - Get raw text body +- `req.headers` - Access request headers +- `req.method` - HTTP method + +### Response Object + +Return using `Response.json()` for JSON responses: + +```typescript +// Success response +return Response.json({ data: result }); + +// Error response with status code +return Response.json({ error: "Something went wrong" }, { status: 400 }); + +// Not found +return Response.json({ error: "Order not found" }, { status: 404 }); +``` + +## Complete Example + +### Directory Structure +``` +base44/ + functions/ + process-order/ + function.jsonc + index.ts +``` + +### function.jsonc +```jsonc +{ + "name": "process-order", + "entry": "index.ts" +} +``` + +### index.ts +```typescript +import { createClientFromRequest } from "npm:@base44/sdk"; + +Deno.serve(async (req) => { + try { + const base44 = createClientFromRequest(req); + const { orderId } = await req.json(); + + // Validate input + if (!orderId) { + return Response.json( + { error: "Order ID is required" }, + { status: 400 } + ); + } + + // Fetch and process the order + const order = await base44.entities.Orders.get(orderId); + if (!order) { + return Response.json( + { error: "Order not found" }, + { status: 404 } + ); + } + + return Response.json({ + success: true, + orderId: order.id, + processedAt: new Date().toISOString() + }); + + } catch (error) { + return Response.json( + { error: error.message }, + { status: 500 } + ); + } +}); +``` + +## Using Service Role Access + +For admin-level operations, use `asServiceRole`: + +```typescript +import { createClientFromRequest } from "npm:@base44/sdk"; + +Deno.serve(async (req) => { + const base44 = createClientFromRequest(req); + + // Check user is authenticated + const user = await base44.auth.me(); + if (!user) { + return Response.json({ error: "Unauthorized" }, { status: 401 }); + } + + // Use service role for admin operations + const allOrders = await base44.asServiceRole.entities.Orders.list(); + + return Response.json({ orders: allOrders }); +}); +``` + +## Using Secrets + +Access environment variables configured in the app dashboard: + +```typescript +Deno.serve(async (req) => { + // Access environment variables (configured in app settings) + const apiKey = Deno.env.get("STRIPE_API_KEY"); + + const response = await fetch("https://api.stripe.com/v1/charges", { + headers: { + "Authorization": `Bearer ${apiKey}` + } + }); + + return Response.json(await response.json()); +}); +``` + +## Naming Conventions + +- **Directory name**: Use kebab-case (e.g., `process-order`, `send-notification`) +- **Function name**: Match the directory name, must match pattern `/^[^.]+$/` (no dots allowed) + - Valid: `process-order`, `send_notification`, `myFunction` + - Invalid: `process.order`, `send.notification.v2` +- **Entry file**: Typically `index.ts` or `index.js` + +## Deploying Functions + +After creating your function, deploy it to Base44: + +```bash +npx base44 functions deploy +``` + +For more details on deploying, see [functions-deploy.md](functions-deploy.md). + +## Notes + +- Functions run on Deno runtime, not Node.js +- Use `npm:` prefix for npm packages (e.g., `npm:@base44/sdk`) +- Use `createClientFromRequest(req)` to get a client that inherits the caller's auth context +- Configure secrets via app dashboard for API keys +- Make sure to handle errors gracefully and return appropriate HTTP status codes + +## Common Mistakes + +| Wrong | Correct | Why | +|-------|---------|-----| +| `functions/myFunction.js` (single file) | `functions/my-function/index.ts` + `function.jsonc` | Functions require subdirectory with config | +| `import { ... } from "@base44/sdk"` | `import { ... } from "npm:@base44/sdk"` | Deno requires `npm:` prefix for npm packages | +| `MyFunction` or `myFunction` directory | `my-function` directory | Use kebab-case for directory names | diff --git a/evals/fixtures/sdk-integrations/skills/base44-cli/references/functions-deploy.md b/evals/fixtures/sdk-integrations/skills/base44-cli/references/functions-deploy.md new file mode 100644 index 0000000..f89d796 --- /dev/null +++ b/evals/fixtures/sdk-integrations/skills/base44-cli/references/functions-deploy.md @@ -0,0 +1,78 @@ +# base44 functions deploy + +Deploy local function definitions to Base44. + +## Syntax + +```bash +npx base44 functions deploy +``` + +## Authentication + +**Required**: Yes. If not authenticated, you'll be prompted to login first. + +## What It Does + +1. Scans the `base44/functions/` directory for function definitions +2. Validates that functions exist and have valid configurations +3. Displays the count of functions to be deployed +4. Uploads function code and configuration to Base44 +5. Reports the results: deployed and deleted functions + +## Prerequisites + +- Must be run from a Base44 project directory +- Project must have function definitions in the `base44/functions/` folder +- Each function must have a valid `function.jsonc` config file + +## Output + +```bash +$ npx base44 functions deploy + +Found 2 functions to deploy +Deploying functions to Base44... + +Deployed: process-order, send-notification +Deleted: old-function + +✓ Functions deployed successfully +``` + +## Function Synchronization + +The deploy operation synchronizes your local functions with Base44: + +- **Deployed**: Functions that were created or updated +- **Deleted**: Functions that were removed from your local configuration + +## Error Handling + +If no functions are found in your project: +```bash +$ npx base44 functions deploy +No functions found. Create functions in the 'functions' directory. +``` + +If a function has configuration errors: +```bash +$ npx base44 functions deploy +Function deployment errors: +'my-function' function: Entry point cannot be empty +``` + +## Use Cases + +- After creating new functions in your project +- When modifying existing function code or configuration +- To sync function changes before testing +- As part of your development workflow when backend logic changes + +## Notes + +- This command deploys the function code and configuration +- Changes are applied to your Base44 project immediately +- Make sure to test functions in a development environment first +- Function definitions are located in the `base44/functions/` directory +- For how to create functions, see [functions-create.md](functions-create.md) diff --git a/evals/fixtures/sdk-integrations/skills/base44-cli/references/link.md b/evals/fixtures/sdk-integrations/skills/base44-cli/references/link.md new file mode 100644 index 0000000..0b009ce --- /dev/null +++ b/evals/fixtures/sdk-integrations/skills/base44-cli/references/link.md @@ -0,0 +1,81 @@ +# base44 link + +Links an existing local Base44 project to a Base44 app in the cloud. Use this when you have a `base44/config.jsonc` but haven't connected it to a Base44 app yet. + +## Critical: When to Use Link vs Create + +| Scenario | Command | +|----------|---------| +| Starting fresh, no `base44/` folder | `npx base44 create` | +| Have `base44/config.jsonc` but no `.app.jsonc` | `npx base44 link` | +| Project already linked (has `.app.jsonc`) | Already done, use `deploy` | + +## Syntax + +```bash +npx base44 link [options] +``` + +## Options + +| Option | Description | Required | +|--------|-------------|----------| +| `-c, --create` | Create a new project (skip selection prompt) | No | +| `-n, --name ` | Project name (required when `--create` is used) | With `--create` | +| `-d, --description ` | Project description | No | +| `-p, --projectId ` | Project ID to link to an existing project (skip selection prompt) | No | + +## Non-Interactive Mode + +For CI/CD or agent use: + +**Create a new project:** +```bash +npx base44 link --create --name my-app +``` + +**Link to an existing project:** +```bash +npx base44 link --projectId +``` + +WRONG: `npx base44 link --create` (missing --name) +WRONG: `npx base44 link --create --projectId ` (cannot use both) +RIGHT: `npx base44 link --create --name my-app` +RIGHT: `npx base44 link --projectId ` + +## Examples + +```bash +# Interactive mode - prompts for project details +npx base44 link + +# Non-interactive - create and link in one step +npx base44 link --create --name my-app + +# With description +npx base44 link --create --name my-app --description "My awesome app" + +# Link to a specific existing project by ID +npx base44 link --projectId abc123 +``` + +## What It Does + +1. Finds the `base44/config.jsonc` in the current directory (or parent directories) +2. Verifies no `.app.jsonc` exists (project not already linked) +3. Either: + - Creates a new Base44 app in the cloud (with `--create`), OR + - Links to an existing project (with `--projectId` or interactive selection) +4. Writes the app ID to `base44/.app.jsonc` + +## Requirements + +- Must have `base44/config.jsonc` in the project +- Must NOT have `base44/.app.jsonc` (use `deploy` if already linked) +- Must be authenticated (run `npx base44 login` first) + +## Notes + +- After linking, you can deploy resources with `npx base44 deploy` +- The `.app.jsonc` file should be git-ignored (contains your app ID) diff --git a/evals/fixtures/sdk-integrations/skills/base44-cli/references/rls-examples.md b/evals/fixtures/sdk-integrations/skills/base44-cli/references/rls-examples.md new file mode 100644 index 0000000..7d3ef42 --- /dev/null +++ b/evals/fixtures/sdk-integrations/skills/base44-cli/references/rls-examples.md @@ -0,0 +1,463 @@ +# RLS Examples + +Practical Row-Level Security patterns for common application types. + +**Important:** Base44 RLS supports: +- **Logical operators:** `$or`, `$and`, `$nor` for combining conditions +- **Field operators (for `data.*` fields):** `$in`, `$nin`, `$ne`, `$all` +- **user_condition:** Equality only (no operators) + +## Contents +- [Simple Patterns (JSON Schema)](#simple-patterns-json-schema) +- [Using Operators](#using-operators) +- [Field-Level Security Examples](#field-level-security-examples) +- [Complex Patterns (Dashboard UI or Backend)](#complex-patterns-dashboard-ui-or-backend) +- [Best Practices](#best-practices) + +--- + +## Simple Patterns (JSON Schema) + +These patterns work with the JSON schema RLS format. + +### Todo App - Owner-only access + +Users see and manage only their own tasks. + +```jsonc +{ + "name": "Task", + "type": "object", + "properties": { + "title": { "type": "string" }, + "description": { "type": "string" }, + "completed": { "type": "boolean" }, + "priority": { "type": "string", "enum": ["low", "medium", "high"] }, + "due_date": { "type": "string", "format": "date" } + }, + "rls": { + "create": true, + "read": { "created_by": "{{user.email}}" }, + "update": { "created_by": "{{user.email}}" }, + "delete": { "created_by": "{{user.email}}" } + } +} +``` + +### Contact Form - Public create, admin-only read + +Anyone can submit, only admins can view submissions. + +```jsonc +{ + "name": "ContactSubmission", + "type": "object", + "properties": { + "name": { "type": "string" }, + "email": { "type": "string", "format": "email" }, + "message": { "type": "string" } + }, + "rls": { + "create": true, + "read": { "user_condition": { "role": "admin" } }, + "update": { "user_condition": { "role": "admin" } }, + "delete": { "user_condition": { "role": "admin" } } + } +} +``` + +### User Profile - Self-management + +Users can only access their own profile. + +```jsonc +{ + "name": "UserProfile", + "type": "object", + "properties": { + "name": { "type": "string" }, + "avatar_url": { "type": "string" }, + "bio": { "type": "string" }, + "preferences": { "type": "object" } + }, + "rls": { + "create": true, + "read": { "created_by": "{{user.email}}" }, + "update": { "created_by": "{{user.email}}" }, + "delete": { "created_by": "{{user.email}}" } + } +} +``` + +### Department Data - Same department access + +Users can only see records from their department. + +```jsonc +{ + "name": "DepartmentAnnouncement", + "type": "object", + "properties": { + "title": { "type": "string" }, + "content": { "type": "string" }, + "department": { "type": "string" } + }, + "rls": { + "create": { "user_condition": { "role": "manager" } }, + "read": { "data.department": "{{user.data.department}}" }, + "update": { "user_condition": { "role": "manager" } }, + "delete": { "user_condition": { "role": "admin" } } + } +} +``` + +### Subscription - Admin-managed, user-readable via email field + +```jsonc +{ + "name": "Subscription", + "type": "object", + "properties": { + "user_email": { "type": "string" }, + "tier": { "type": "string", "enum": ["free", "basic", "pro", "enterprise"] }, + "credits": { "type": "number" }, + "renewal_date": { "type": "string", "format": "date" } + }, + "rls": { + "create": { "user_condition": { "role": "admin" } }, + "read": { "data.user_email": "{{user.email}}" }, + "update": { "user_condition": { "role": "admin" } }, + "delete": { "user_condition": { "role": "admin" } } + } +} +``` + +**Note:** This pattern only allows users to read their own subscription. Admins need to use the Dashboard UI to configure additional read access for themselves. + +### Private Data - Owner-only + +```jsonc +{ + "name": "PrivateNotes", + "type": "object", + "properties": { + "title": { "type": "string" }, + "content": { "type": "string" }, + "tags": { "type": "array", "items": { "type": "string" } } + }, + "rls": { + "create": true, + "read": { "created_by": "{{user.email}}" }, + "update": { "created_by": "{{user.email}}" }, + "delete": { "created_by": "{{user.email}}" } + } +} +``` + +### Public Read, Authenticated Write + +Anyone can read, only logged-in users can create/edit their own records. + +```jsonc +{ + "name": "BlogPost", + "type": "object", + "properties": { + "title": { "type": "string" }, + "content": { "type": "string" }, + "author_email": { "type": "string" } + }, + "rls": { + "create": true, + "read": true, + "update": { "created_by": "{{user.email}}" }, + "delete": { "created_by": "{{user.email}}" } + } +} +``` + +--- + +## Using Operators + +### Logical Operators + +Combine multiple conditions using `$or`, `$and`, or `$nor`: + +**Owner OR Admin access:** +```jsonc +{ + "name": "Document", + "type": "object", + "properties": { + "title": { "type": "string" }, + "content": { "type": "string" } + }, + "rls": { + "create": true, + "read": { + "$or": [ + { "created_by": "{{user.email}}" }, + { "user_condition": { "role": "admin" } } + ] + }, + "update": { + "$or": [ + { "created_by": "{{user.email}}" }, + { "user_condition": { "role": "admin" } } + ] + }, + "delete": { "user_condition": { "role": "admin" } } + } +} +``` + +**Multiple roles with $or:** +```jsonc +{ + "rls": { + "read": { + "$or": [ + { "user_condition": { "role": "admin" } }, + { "user_condition": { "role": "manager" } }, + { "user_condition": { "role": "hr" } } + ] + } + } +} +``` + +### Field Operators for data.* Fields + +Use `$in`, `$nin`, `$ne`, `$all` for comparing entity data fields: + +**Access based on tags ($in):** +```jsonc +{ + "rls": { + "read": { + "data.category": { "$in": ["public", "shared"] } + } + } +} +``` + +**Exclude specific statuses ($nin):** +```jsonc +{ + "rls": { + "read": { + "data.status": { "$nin": ["archived", "deleted"] } + } + } +} +``` + +**Not equal ($ne):** +```jsonc +{ + "rls": { + "read": { + "data.visibility": { "$ne": "private" } + } + } +} +``` + +**All tags must match ($all):** +```jsonc +{ + "rls": { + "read": { + "data.required_tags": { "$all": ["approved", "reviewed"] } + } + } +} +``` + +### Combining Logical and Field Operators + +```jsonc +{ + "rls": { + "read": { + "$and": [ + { "data.status": { "$ne": "draft" } }, + { + "$or": [ + { "created_by": "{{user.email}}" }, + { "data.visibility": "public" } + ] + } + ] + } + } +} +``` + +--- + +## Field-Level Security Examples + +Control access to specific fields within an entity. + +### Sensitive Salary Field + +```jsonc +{ + "name": "Employee", + "type": "object", + "properties": { + "name": { "type": "string" }, + "email": { "type": "string", "format": "email" }, + "salary": { + "type": "number", + "description": "Annual salary", + "rls": { + "read": { "user_condition": { "role": "hr" } }, + "write": { "user_condition": { "role": "hr" } } + } + }, + "performance_notes": { + "type": "string", + "description": "Manager notes", + "rls": { + "read": { + "$or": [ + { "user_condition": { "role": "manager" } }, + { "user_condition": { "role": "hr" } } + ] + }, + "write": { "user_condition": { "role": "manager" } } + } + } + } +} +``` + +### Admin-Only Internal Fields + +```jsonc +{ + "name": "Order", + "type": "object", + "properties": { + "order_number": { "type": "string" }, + "total": { "type": "number" }, + "internal_notes": { + "type": "string", + "description": "Internal processing notes", + "rls": { + "read": { "user_condition": { "role": "admin" } }, + "write": { "user_condition": { "role": "admin" } } + } + }, + "profit_margin": { + "type": "number", + "description": "Profit margin percentage", + "rls": { + "read": { "user_condition": { "role": "admin" } }, + "write": false + } + } + } +} +``` + +--- + +## Complex Patterns (Dashboard UI or Backend) + +Some patterns may still require the Dashboard UI or backend functions. + +### Bidirectional Relationships (e.g., Friendships, Matches) + +**Requirement:** Either party in a relationship should have access. + +**Now possible with $or:** +```jsonc +{ + "rls": { + "read": { + "$or": [ + { "data.user_a_email": "{{user.email}}" }, + { "data.user_b_email": "{{user.email}}" } + ] + } + } +} +``` + +**Alternative solutions:** +1. **Entity redesign:** Store two records per relationship (one for each party) +2. **Backend function:** Query with custom logic + +### Complex Business Logic + +**Requirement:** Access depends on multiple entity fields with complex conditions. + +**JSON Schema limitation:** While operators help, very complex business logic may still be hard to express. + +**Solution options:** +1. **Backend function:** Implement custom access logic +2. **Combine simpler rules:** Break complex rules into simpler entity-level and field-level rules + +--- + +## Best Practices + +### Security Strategy + +Use a combination of entity-level RLS and field-level security: + +| Data Type | Approach | Example | +|-----------|----------|---------| +| User-editable | Entity RLS: Owner-only | UserProfile with `created_by` check | +| Sensitive fields | Field-level RLS | Salary field with HR role check | +| Multi-role access | `$or` with user_condition | Admin OR Manager access | +| Conditional access | Field operators | `$in`, `$ne` on data fields | +| Public content | Entity RLS: `read: true` | PublicPost | +| Private content | Entity RLS: Owner-only | PrivateNote | + +### When to Use Each Approach + +| Requirement | Approach | +|-------------|----------| +| Single condition (owner, admin, department) | JSON Schema RLS | +| Multiple OR/AND conditions | JSON Schema RLS with `$or`/`$and` | +| Field value checks with `$in`/`$ne`/etc. | JSON Schema RLS for `data.*` fields | +| Field-level access control | JSON Schema FLS (field-level `rls`) | +| Complex comparison operators (`$gt`, `$lt`) | Backend functions | +| Very complex business logic | Backend functions | + +### Common Role Patterns + +| Role | Typical Access | +|------|----------------| +| `admin` | Full access to all records | +| `moderator` | Read/update access, limited delete | +| `manager` | Department-scoped access | +| `user` | Own records only | + +### Supported Operators Summary + +| Operator | Supported | Notes | +|----------|-----------|-------| +| `$or` | Yes | Combine multiple conditions | +| `$and` | Yes | All conditions must match | +| `$nor` | Yes | None of the conditions match | +| `$in` | Yes | For `data.*` fields only | +| `$nin` | Yes | For `data.*` fields only | +| `$ne` | Yes | For `data.*` fields only | +| `$all` | Yes | For `data.*` fields only | +| `$gt`, `$lt`, `$gte`, `$lte` | No | Use backend functions | +| `$regex` | No | Use backend functions | + +### Limitations Summary + +| Not Supported | Alternative | +|---------------|-------------| +| Operators on `user_condition` | Use equality only for user checks | +| Comparison operators (`$gt`, `$lt`) | Backend functions | +| Regex matching (`$regex`) | Backend functions | +| Cross-entity relationships | Backend functions | diff --git a/evals/fixtures/sdk-integrations/skills/base44-cli/references/site-deploy.md b/evals/fixtures/sdk-integrations/skills/base44-cli/references/site-deploy.md new file mode 100644 index 0000000..929d302 --- /dev/null +++ b/evals/fixtures/sdk-integrations/skills/base44-cli/references/site-deploy.md @@ -0,0 +1,118 @@ +# base44 site deploy + +Deploy built site files to Base44 hosting. + +## Table of Contents + +- [Syntax](#syntax) +- [Authentication](#authentication) +- [Prerequisites](#prerequisites) +- [How It Works](#how-it-works) +- [Interactive Flow](#interactive-flow) +- [Typical Workflow](#typical-workflow) +- [Configuration](#configuration) +- [Error Handling](#error-handling) +- [Use Cases](#use-cases) +- [Notes](#notes) + +## Syntax + +```bash +npx base44 site deploy [options] +``` + +## Options + +| Option | Description | +| ------------ | ------------------------- | +| `-y, --yes` | Skip confirmation prompt | + +Use `-y` flag for non-interactive/automated deployments: + +```bash +npx base44 site deploy -y +``` + +## Authentication + +**Required**: Yes. If not authenticated, you'll be prompted to login first. + +## Prerequisites + +- Must be run from a Base44 project directory +- Project must have `site.outputDirectory` configured in project config +- Site must be built before deploying (run your build command first) +- **SPA only**: Base44 hosting supports Single Page Applications with a single `index.html` entry point. All routes are served from `index.html` (client-side routing). + +## How It Works + +1. Reads project configuration +2. Validates that site configuration exists +3. Prompts for deployment confirmation showing the output directory +4. Creates an archive of site files from the output directory +5. Deploys to Base44 hosting +6. Returns the app URL + +## Interactive Flow + +```bash +$ npx base44 site deploy + +Deploy site from ./dist? (yes/no) yes + +Creating archive... +Uploading to Base44... +Deploying... + +✓ Deployment successful! + +Visit your site at: https://my-app.base44.app +``` + +## Typical Workflow + +```bash +# 1. Build your site using your framework's build command +npm run build + +# 2. Deploy to Base44 +npx base44 site deploy +``` + +## Configuration + +The `site.outputDirectory` in your project configuration should point to where your framework outputs built files: + +- Vite: typically `./dist` +- Next.js: typically `./.next` or `./out` +- Create React App: typically `./build` +- Custom: whatever your build tool outputs to + +## Error Handling + +If site configuration is missing: +```bash +$ npx base44 site deploy +Error: No site configuration found in project +``` + +If you cancel the deployment: +```bash +Deploy site from ./dist? (yes/no) no +Deployment cancelled +``` + +## Use Cases + +- Deploy your site after making changes +- Push new versions of your application +- Deploy after updating content or functionality +- Part of your CI/CD pipeline + +## Notes + +- Always build your site before deploying +- The command deploys whatever is in your output directory +- Make sure your build completed successfully before deploying +- Previous deployments are preserved (versioned) in Base44 +- Deployment is immediate and updates your live site diff --git a/evals/fixtures/sdk-integrations/skills/base44-sdk/SKILL.md b/evals/fixtures/sdk-integrations/skills/base44-sdk/SKILL.md new file mode 100644 index 0000000..01ddd7f --- /dev/null +++ b/evals/fixtures/sdk-integrations/skills/base44-sdk/SKILL.md @@ -0,0 +1,296 @@ +--- +name: base44-sdk +description: "The base44 SDK is the library to communicate with base44 services. In projects, you use it to communicate with remote resources (entities, backend functions, ai agents) and to write backend functions. This skill is the place for learning about available modules and types. When you plan or implement a feature, you must learn this skill" +--- + +# Base44 Coder + +Build apps on the Base44 platform using the Base44 JavaScript SDK. + +## ⚡ IMMEDIATE ACTION REQUIRED - Read This First + +This skill activates on ANY mention of "base44" or when a `base44/` folder exists. **DO NOT read documentation files or search the web before acting.** + +**Your first action MUST be:** +1. Check if `base44/config.jsonc` exists in the current directory +2. If **YES** (existing project scenario): + - This skill (base44-sdk) handles the request + - Implement features using Base44 SDK + - Do NOT use base44-cli unless user explicitly requests CLI commands +3. If **NO** (new project scenario): + - Transfer to base44-cli skill for project initialization + - This skill cannot help until project is initialized + +## When to Use This Skill vs base44-cli + +**Use base44-sdk when:** +- Building features in an **EXISTING** Base44 project +- `base44/config.jsonc` already exists in the project +- Base44 SDK imports are present (`@base44/sdk`) +- Writing JavaScript/TypeScript code using Base44 SDK modules +- Implementing functionality, components, or features +- User mentions: "implement", "build a feature", "add functionality", "write code for" +- User says "create a [type] app" **and** a Base44 project already exists + +**DO NOT USE base44-sdk for:** +- ❌ Initializing new Base44 projects (use `base44-cli` instead) +- ❌ Empty directories without Base44 configuration +- ❌ When user says "create a new Base44 project/app/site" and no project exists +- ❌ CLI commands like `npx base44 create`, `npx base44 deploy`, `npx base44 login` (use `base44-cli`) + +**Skill Dependencies:** +- `base44-sdk` assumes a Base44 project is **already initialized** +- `base44-cli` is a **prerequisite** for `base44-sdk` in new projects +- If user wants to "create an app" and no Base44 project exists, use `base44-cli` first + +**State Check Logic:** +Before selecting this skill, verify: +- IF (user mentions "create/build app" OR "make a project"): + - IF (directory is empty OR no `base44/config.jsonc` exists): + → Use **base44-cli** (project initialization needed) + - ELSE: + → Use **base44-sdk** (project exists, build features) + +## Quick Start + +```javascript +// In Base44-generated apps, base44 client is pre-configured and available + +// CRUD operations +const task = await base44.entities.Task.create({ title: "New task", status: "pending" }); +const tasks = await base44.entities.Task.list(); +await base44.entities.Task.update(task.id, { status: "done" }); + +// Get current user +const user = await base44.auth.me(); +``` + +```javascript +// External apps +import { createClient } from "@base44/sdk"; + +// IMPORTANT: Use 'appId' (NOT 'clientId' or 'id') +const base44 = createClient({ appId: "your-app-id" }); +await base44.auth.loginViaEmailPassword("user@example.com", "password"); +``` + +## ⚠️ CRITICAL: Do Not Hallucinate APIs + +**Before writing ANY Base44 code, verify method names against this table or [QUICK_REFERENCE.md](references/QUICK_REFERENCE.md).** + +Base44 SDK has unique method names. Do NOT assume patterns from Firebase, Supabase, or other SDKs. + +### Authentication - WRONG vs CORRECT + +| ❌ WRONG (hallucinated) | ✅ CORRECT | +|------------------------|-----------| +| `signInWithGoogle()` | `loginWithProvider('google')` | +| `signInWithProvider('google')` | `loginWithProvider('google')` | +| `auth.google()` | `loginWithProvider('google')` | +| `signInWithEmailAndPassword(email, pw)` | `loginViaEmailPassword(email, pw)` | +| `signIn(email, pw)` | `loginViaEmailPassword(email, pw)` | +| `createUser()` / `signUp()` | `register({email, password})` | +| `onAuthStateChanged()` | `me()` (no listener, call when needed) | +| `currentUser` | `await auth.me()` | + +### Functions - WRONG vs CORRECT + +| ❌ WRONG (hallucinated) | ✅ CORRECT | +|------------------------|-----------| +| `functions.call('name', data)` | `functions.invoke('name', data)` | +| `functions.run('name', data)` | `functions.invoke('name', data)` | +| `callFunction('name', data)` | `functions.invoke('name', data)` | +| `httpsCallable('name')(data)` | `functions.invoke('name', data)` | + +### Integrations - WRONG vs CORRECT + +| ❌ WRONG (hallucinated) | ✅ CORRECT | +|------------------------|-----------| +| `ai.generate(prompt)` | `integrations.Core.InvokeLLM({prompt})` | +| `openai.chat(prompt)` | `integrations.Core.InvokeLLM({prompt})` | +| `llm(prompt)` | `integrations.Core.InvokeLLM({prompt})` | +| `sendEmail(to, subject, body)` | `integrations.Core.SendEmail({to, subject, body})` | +| `email.send()` | `integrations.Core.SendEmail({to, subject, body})` | +| `uploadFile(file)` | `integrations.Core.UploadFile({file})` | +| `storage.upload(file)` | `integrations.Core.UploadFile({file})` | + +### Entities - WRONG vs CORRECT + +| ❌ WRONG (hallucinated) | ✅ CORRECT | +|------------------------|-----------| +| `entities.Task.find({...})` | `entities.Task.filter({...})` | +| `entities.Task.findOne(id)` | `entities.Task.get(id)` | +| `entities.Task.insert(data)` | `entities.Task.create(data)` | +| `entities.Task.remove(id)` | `entities.Task.delete(id)` | +| `entities.Task.onChange(cb)` | `entities.Task.subscribe(cb)` | + +## SDK Modules + +| Module | Purpose | Reference | +|--------|---------|-----------| +| `entities` | CRUD operations on data models | [entities.md](references/entities.md) | +| `auth` | Login, register, user management | [auth.md](references/auth.md) | +| `agents` | AI conversations and messages | [base44-agents.md](references/base44-agents.md) | +| `functions` | Backend function invocation | [functions.md](references/functions.md) | +| `integrations` | AI, email, file uploads, custom APIs | [integrations.md](references/integrations.md) | +| `connectors` | OAuth tokens (service role only) | [connectors.md](references/connectors.md) | +| `analytics` | Track custom events and user activity | [analytics.md](references/analytics.md) | +| `appLogs` | Log user activity in app | [app-logs.md](references/app-logs.md) | +| `users` | Invite users to the app | [users.md](references/users.md) | + +For client setup and authentication modes, see [client.md](references/client.md). + +**TypeScript Support:** Each reference file includes a "Type Definitions" section with TypeScript interfaces and types for the module's methods, parameters, and return values. + +## Installation + +Install the Base44 SDK: + +```bash +npm install @base44/sdk +``` + +**Important:** Never assume or hardcode the `@base44/sdk` package version. Always install without a version specifier to get the latest version. + +## Creating a Client (External Apps) + +When creating a client in external apps, **ALWAYS use `appId` as the parameter name**: + +```javascript +import { createClient } from "@base44/sdk"; + +// ✅ CORRECT +const base44 = createClient({ appId: "your-app-id" }); + +// ❌ WRONG - Do NOT use these: +// const base44 = createClient({ clientId: "your-app-id" }); // WRONG +// const base44 = createClient({ id: "your-app-id" }); // WRONG +``` + +**Required parameter:** `appId` (string) - Your Base44 application ID + +**Optional parameters:** +- `token` (string) - Pre-authenticated user token +- `options` (object) - Configuration options + - `options.onError` (function) - Global error handler + +**Example with error handler:** +```javascript +const base44 = createClient({ + appId: "your-app-id", + options: { + onError: (error) => { + console.error("Base44 error:", error); + } + } +}); +``` + +## Module Selection + +**Working with app data?** +- Create/read/update/delete records → `entities` +- Import data from file → `entities.importEntities()` +- Realtime updates → `entities.EntityName.subscribe()` + +**User management?** +- Login/register/logout → `auth` +- Get current user → `auth.me()` +- Update user profile → `auth.updateMe()` +- Invite users → `users.inviteUser()` + +**AI features?** +- Chat with AI agents → `agents` (requires logged-in user) +- Create new conversation → `agents.createConversation()` +- Manage conversations → `agents.getConversations()` +- Generate text/JSON with AI → `integrations.Core.InvokeLLM()` +- Generate images → `integrations.Core.GenerateImage()` + +**Custom backend logic?** +- Run server-side code → `functions.invoke()` +- Need admin access → `base44.asServiceRole.functions.invoke()` + +**External services?** +- Send emails → `integrations.Core.SendEmail()` +- Upload files → `integrations.Core.UploadFile()` +- Custom APIs → `integrations.custom.call()` +- OAuth tokens (Google, Slack) → `connectors` (backend only) + +**Tracking and analytics?** +- Track custom events → `analytics.track()` +- Log page views/activity → `appLogs.logUserInApp()` + +## Common Patterns + +### Filter and Sort Data + +```javascript +const pendingTasks = await base44.entities.Task.filter( + { status: "pending", assignedTo: userId }, // query + "-created_date", // sort (descending) + 10, // limit + 0 // skip +); +``` + +### Protected Routes (check auth) + +```javascript +const user = await base44.auth.me(); +if (!user) { + // Navigate to your custom login page + navigate('/login', { state: { returnTo: window.location.pathname } }); + return; +} +``` + +### Backend Function Call + +```javascript +// Frontend +const result = await base44.functions.invoke("processOrder", { + orderId: "123", + action: "ship" +}); + +// Backend function (Deno) +import { createClientFromRequest } from "npm:@base44/sdk"; + +Deno.serve(async (req) => { + const base44 = createClientFromRequest(req); + const { orderId, action } = await req.json(); + // Process with service role for admin access + const order = await base44.asServiceRole.entities.Orders.get(orderId); + return Response.json({ success: true }); +}); +``` + +### Service Role Access + +Use `asServiceRole` in backend functions for admin-level operations: + +```javascript +// User mode - respects permissions +const myTasks = await base44.entities.Task.list(); + +// Service role - full access (backend only) +const allTasks = await base44.asServiceRole.entities.Task.list(); +const token = await base44.asServiceRole.connectors.getAccessToken("slack"); +``` + +## Frontend vs Backend + +| Capability | Frontend | Backend | +|------------|----------|---------| +| `entities` (user's data) | Yes | Yes | +| `auth` | Yes | Yes | +| `agents` | Yes | Yes | +| `functions.invoke()` | Yes | Yes | +| `integrations` | Yes | Yes | +| `analytics` | Yes | Yes | +| `appLogs` | Yes | Yes | +| `users` | Yes | Yes | +| `asServiceRole.*` | No | Yes | +| `connectors` | No | Yes | + +Backend functions use `Deno.serve()` and `createClientFromRequest(req)` to get a properly authenticated client. diff --git a/evals/fixtures/sdk-integrations/skills/base44-sdk/references/QUICK_REFERENCE.md b/evals/fixtures/sdk-integrations/skills/base44-sdk/references/QUICK_REFERENCE.md new file mode 100644 index 0000000..d357384 --- /dev/null +++ b/evals/fixtures/sdk-integrations/skills/base44-sdk/references/QUICK_REFERENCE.md @@ -0,0 +1,155 @@ +# Base44 SDK Quick Reference + +Compact method signatures for all SDK modules. **Verify against this before writing code.** + +--- + +## Auth (`base44.auth.*`) + +``` +loginViaEmailPassword(email, password, turnstileToken?) → Promise<{access_token, user}> +loginWithProvider('google' | 'microsoft' | 'facebook', fromUrl?) → void +me() → Promise +updateMe(data) → Promise +isAuthenticated() → Promise +logout(redirectUrl?) → void +redirectToLogin(nextUrl) → void # ⚠️ Avoid - prefer custom login UI +register({email, password, turnstile_token?, referral_code?}) → Promise +verifyOtp({email, otpCode}) → Promise +resendOtp(email) → Promise +inviteUser(userEmail, role) → Promise +resetPasswordRequest(email) → Promise +resetPassword({resetToken, newPassword}) → Promise +changePassword({userId, currentPassword, newPassword}) → Promise +setToken(token, saveToStorage?) → void +``` + +--- + +## Entities (`base44.entities.EntityName.*`) + +``` +create(data) → Promise +bulkCreate(dataArray) → Promise +list(sort?, limit?, skip?, fields?) → Promise +filter(query, sort?, limit?, skip?, fields?) → Promise +get(id) → Promise +update(id, data) → Promise +delete(id) → Promise +deleteMany(query) → Promise +importEntities(file) → Promise // frontend only +subscribe(callback) → () => void // returns unsubscribe fn +``` + +**Sort:** Use `-fieldName` for descending (e.g., `-created_date`) + +--- + +## Functions (`base44.functions.*`) + +``` +invoke(functionName, data) → Promise +``` + +**Backend:** Use `base44.asServiceRole.functions.invoke()` for admin access. + +--- + +## Integrations (`base44.integrations.Core.*`) + +``` +InvokeLLM({prompt, add_context_from_internet?, response_json_schema?, file_urls?}) → Promise +GenerateImage({prompt}) → Promise<{url}> +SendEmail({to, subject, body, from_name?}) → Promise +UploadFile({file}) → Promise<{file_url}> +UploadPrivateFile({file}) → Promise<{file_uri}> +CreateFileSignedUrl({file_uri, expires_in?}) → Promise<{signed_url}> +ExtractDataFromUploadedFile({file_url, json_schema}) → Promise +``` + +### Custom Integrations (`base44.integrations.custom.*`) + +``` +call(slug, operationId, {payload?, pathParams?, queryParams?, headers?}?) → Promise<{success, status_code, data}> +``` + +**operationId format:** `"method:/path"` (e.g., `"get:/contacts"`, `"post:/users/{id}"`) + +--- + +## Analytics (`base44.analytics.*`) + +``` +track({eventName, properties?}) → void +``` + +--- + +## App Logs (`base44.appLogs.*`) + +``` +logUserInApp(pageName) → Promise +``` + +--- + +## Users (`base44.users.*`) + +``` +inviteUser(userEmail, role) → Promise // role: 'user' | 'admin' +``` + +--- + +## Connectors (`base44.asServiceRole.connectors.*`) + +**Backend only, service role required.** + +``` +getAccessToken(integrationType) → Promise +``` + +**Types:** `'googlecalendar'`, `'googledrive'`, `'slack'`, `'notion'`, `'salesforce'`, `'hubspot'`, `'linkedin'`, `'tiktok'`, `'github'` + +--- + +## Service Role Access + +**Backend functions only.** Prefix any module with `asServiceRole` for admin access: + +```javascript +base44.asServiceRole.entities.Task.list() +base44.asServiceRole.functions.invoke('name', data) +base44.asServiceRole.connectors.getAccessToken('slack') +``` + +--- + +## Backend Function Template + +```javascript +import { createClientFromRequest } from "@base44/sdk"; + +Deno.serve(async (req) => { + const base44 = createClientFromRequest(req); + const data = await req.json(); + + // User context + const user = await base44.auth.me(); + + // Service role for admin operations + const allRecords = await base44.asServiceRole.entities.Task.list(); + + return Response.json({ success: true }); +}); +``` + +--- + +## Client Initialization (External Apps) + +```javascript +import { createClient } from "@base44/sdk"; + +const base44 = createClient({ appId: "your-app-id" }); // MUST use 'appId' +``` diff --git a/evals/fixtures/sdk-integrations/skills/base44-sdk/references/analytics.md b/evals/fixtures/sdk-integrations/skills/base44-sdk/references/analytics.md new file mode 100644 index 0000000..d1b2c69 --- /dev/null +++ b/evals/fixtures/sdk-integrations/skills/base44-sdk/references/analytics.md @@ -0,0 +1,113 @@ +# Analytics Module + +Track custom events and user activity via `base44.analytics`. + +## Contents +- [Methods](#methods) +- [Examples](#examples) +- [Automatic Tracking](#automatic-tracking) +- [Best Practices](#best-practices) + +## Methods + +| Method | Signature | Description | +|--------|-----------|-------------| +| `track(params)` | `void` | Track a custom event | + +## Examples + +### Track Custom Event + +```javascript +// Track a simple event +base44.analytics.track({ + eventName: "button_clicked" +}); + +// Track event with properties +base44.analytics.track({ + eventName: "purchase_completed", + properties: { + product_id: "prod-123", + amount: 99.99, + currency: "USD" + } +}); +``` + +### Track User Actions + +```javascript +// Track page view +base44.analytics.track({ + eventName: "page_view", + properties: { + page: "/dashboard", + referrer: document.referrer + } +}); + +// Track feature usage +base44.analytics.track({ + eventName: "feature_used", + properties: { + feature: "export_data", + format: "csv" + } +}); +``` + +## Automatic Tracking + +The analytics module automatically tracks: +- **Initialization events**: When the app loads +- **Heartbeat events**: Periodic activity signals +- **Session duration**: Time spent in the app + +These internal events help measure user engagement without manual instrumentation. + +## Best Practices + +1. **Use descriptive event names**: `order_completed` instead of `click` +2. **Include relevant properties**: Add context that helps analyze the event +3. **Be consistent**: Use the same event names and property keys across your app +4. **Don't track sensitive data**: Avoid PII in event properties + +```javascript +// Good: Descriptive with relevant properties +base44.analytics.track({ + eventName: "subscription_started", + properties: { + plan: "pro", + billing_cycle: "annual" + } +}); + +// Avoid: Vague event name, no context +base44.analytics.track({ + eventName: "click" +}); +``` + +## Type Definitions + +```typescript +/** Properties that can be attached to a tracked event. */ +type TrackEventProperties = { + [key: string]: string | number | boolean | null | undefined; +}; + +/** Parameters for the track() method. */ +interface TrackEventParams { + /** The name of the event to track. */ + eventName: string; + /** Optional properties to attach to the event. */ + properties?: TrackEventProperties; +} + +/** The analytics module interface. */ +interface AnalyticsModule { + /** Track a custom event with optional properties. */ + track(params: TrackEventParams): void; +} +``` diff --git a/evals/fixtures/sdk-integrations/skills/base44-sdk/references/app-logs.md b/evals/fixtures/sdk-integrations/skills/base44-sdk/references/app-logs.md new file mode 100644 index 0000000..0439805 --- /dev/null +++ b/evals/fixtures/sdk-integrations/skills/base44-sdk/references/app-logs.md @@ -0,0 +1,78 @@ +# App Logs Module + +Log user activity in your app via `base44.appLogs`. + +## Contents +- [Methods](#methods) +- [Examples](#examples) +- [Use Cases](#use-cases) + +## Methods + +| Method | Signature | Description | +|--------|-----------|-------------| +| `logUserInApp(pageName)` | `Promise` | Log user activity on a page | + +## Examples + +### Log User Activity + +```javascript +// Log when user visits a page +await base44.appLogs.logUserInApp("dashboard"); + +// Log specific page visits +await base44.appLogs.logUserInApp("settings"); +await base44.appLogs.logUserInApp("profile"); + +// Log feature usage +await base44.appLogs.logUserInApp("export-button-click"); +``` + +The page name doesn't have to be an actual page - it can be any string you want to track. + +## Use Cases + +### Track Page Views in React + +```javascript +// Log page views on route change +useEffect(() => { + base44.appLogs.logUserInApp(window.location.pathname); +}, [location.pathname]); +``` + +### Track Feature Usage + +```javascript +// Log when user uses specific features +function handleExport() { + base44.appLogs.logUserInApp("export-data"); + // ... export logic +} + +function handleSettingsChange() { + base44.appLogs.logUserInApp("settings-updated"); + // ... save settings +} +``` + +## Notes + +- Logs appear in the Analytics page of your app dashboard +- App logs track page-level and feature-level activity +- Use `analytics.track()` for custom events with properties, `appLogs.logUserInApp()` for simple page/feature tracking + +## Type Definitions + +```typescript +/** App Logs module for tracking and analyzing app usage. */ +interface AppLogsModule { + /** + * Log user activity in the app. + * @param pageName - Name of the page or section being visited. + * @returns Promise that resolves when the log is recorded. + */ + logUserInApp(pageName: string): Promise; +} +``` diff --git a/evals/fixtures/sdk-integrations/skills/base44-sdk/references/auth.md b/evals/fixtures/sdk-integrations/skills/base44-sdk/references/auth.md new file mode 100644 index 0000000..5195b3a --- /dev/null +++ b/evals/fixtures/sdk-integrations/skills/base44-sdk/references/auth.md @@ -0,0 +1,761 @@ +# Auth Module + +User authentication, registration, and session management via `base44.auth`. + +## Contents +- [TypeScript Types](#typescript-types) +- [Methods](#methods) +- [Examples](#examples) +- [Error Handling](#error-handling) +- [Auth Providers](#auth-providers) +- [Environment Availability](#environment-availability) +- [App Visibility](#app-visibility) +- [Limitations](#limitations) + +--- + +## TypeScript Types + +### User Interface +```typescript +interface User { + id: string; + created_date: string; + updated_date: string; + email: string; + full_name: string | null; + disabled: boolean | null; + is_verified: boolean; + app_id: string; + is_service: boolean; + role: string; + [key: string]: any; // Custom schema fields +} +``` + +### LoginResponse Interface +```typescript +interface LoginResponse { + access_token: string; // JWT token + user: User; // Complete user object +} +``` + +### Parameter Interfaces + +#### RegisterParams +```typescript +interface RegisterParams { + email: string; // Required + password: string; // Required + turnstile_token?: string | null; // Optional: Cloudflare Turnstile for bot protection + referral_code?: string | null; // Optional: Referral code +} +``` + +#### VerifyOtpParams +```typescript +interface VerifyOtpParams { + email: string; // User's email + otpCode: string; // OTP code from email +} +``` + +#### ResetPasswordParams +```typescript +interface ResetPasswordParams { + resetToken: string; // Token from password reset email + newPassword: string; // New password to set +} +``` + +#### ChangePasswordParams +```typescript +interface ChangePasswordParams { + userId: string; // User ID + currentPassword: string; // Current password for verification + newPassword: string; // New password to set +} +``` + +### Provider Type +```typescript +type Provider = 'google' | 'microsoft' | 'facebook'; +``` + +--- + +## Methods + +### Module Interface +```typescript +interface AuthModule { + // User Info + me(): Promise; + updateMe(data: Partial>): Promise; + isAuthenticated(): Promise; + + // Login/Logout + loginViaEmailPassword(email: string, password: string, turnstileToken?: string): Promise; + loginWithProvider(provider: Provider, fromUrl?: string): void; + logout(redirectUrl?: string): void; + redirectToLogin(nextUrl: string): void; + + // Token Management + setToken(token: string, saveToStorage?: boolean): void; + + // Registration + register(params: RegisterParams): Promise; + verifyOtp(params: VerifyOtpParams): Promise; + resendOtp(email: string): Promise; + + // User Management + inviteUser(userEmail: string, role: string): Promise; + + // Password Management + resetPasswordRequest(email: string): Promise; + resetPassword(params: ResetPasswordParams): Promise; + changePassword(params: ChangePasswordParams): Promise; +} +``` + +### Method Reference Table + +| Method | Parameters | Return Type | Description | +|--------|-----------|-------------|-------------| +| `register()` | `params: RegisterParams` | `Promise` | Create new user account | +| `loginViaEmailPassword()` | `email: string, password: string, turnstileToken?: string` | `Promise` | Authenticate with email/password | +| `loginWithProvider()` | `provider: Provider, fromUrl?: string` | `void` | Initiate OAuth login flow | +| `me()` | None | `Promise` | Get current authenticated user | +| `updateMe()` | `data: Partial` | `Promise` | Update current user's profile | +| `logout()` | `redirectUrl?: string` | `void` | Clear session, optionally redirect | +| `redirectToLogin()` | `nextUrl: string` | `void` | ⚠️ **Avoid** - Prefer custom login UI with `loginViaEmailPassword()` or `loginWithProvider()` | +| `isAuthenticated()` | None | `Promise` | Check if user is logged in | +| `setToken()` | `token: string, saveToStorage?: boolean` | `void` | Manually set auth token | +| `inviteUser()` | `userEmail: string, role: string` | `Promise` | Send invitation email | +| `verifyOtp()` | `params: VerifyOtpParams` | `Promise` | Verify OTP code | +| `resendOtp()` | `email: string` | `Promise` | Resend OTP code | +| `resetPasswordRequest()` | `email: string` | `Promise` | Request password reset | +| `resetPassword()` | `params: ResetPasswordParams` | `Promise` | Reset password with token | +| `changePassword()` | `params: ChangePasswordParams` | `Promise` | Change user password | + +--- + +## Examples + +### Register New User (Complete Flow) + +Registration requires email verification before login. Complete flow: + +1. **Register** - Create the user account +2. **Verification email sent** - User receives an OTP code +3. **Verify OTP** - User enters code to verify email +4. **Login** - User can now log in + +```javascript +try { + // Step 1: Register the user + await base44.auth.register({ + email: "user@example.com", + password: "securePassword123", + referral_code: "OPTIONAL_CODE", // optional + turnstile_token: "CAPTCHA_TOKEN" // optional, for bot protection + }); + console.log('Registration successful. Check email for OTP code.'); + + // Step 2: User receives email with OTP code (e.g., "123456") + + // Step 3: Verify the OTP code + await base44.auth.verifyOtp({ + email: "user@example.com", + otpCode: "123456" // code from verification email + }); + console.log('Email verified successfully.'); + + // Step 4: Now the user can log in + const loginResponse = await base44.auth.loginViaEmailPassword( + "user@example.com", + "securePassword123" + ); + console.log('Login successful:', loginResponse.user); + +} catch (error) { + console.error('Registration flow failed:', error.message); + // Handle specific errors (see Error Handling section) +} +``` + +> **Important**: Users cannot log in until they complete OTP verification. Attempting to call `loginViaEmailPassword` before verification will fail. + +### Login with Email/Password + +```javascript +try { + const response = await base44.auth.loginViaEmailPassword( + "user@example.com", + "password123", + turnstileToken // optional: for bot protection + ); + + console.log('Login successful'); + console.log('User:', response.user); + console.log('Token:', response.access_token); + + // JWT is automatically stored for subsequent requests + +} catch (error) { + console.error('Login failed:', error.message); + if (error.status === 401) { + console.error('Invalid credentials'); + } else if (error.status === 403) { + console.error('Email not verified. Please check your email for OTP.'); + } +} +``` + +### Login with OAuth Provider + +```javascript +// Redirect to Google OAuth +base44.auth.loginWithProvider('google'); + +// Redirect to Google OAuth and return to current page after +base44.auth.loginWithProvider('google', window.location.href); + +// Supported providers: 'google', 'microsoft', 'facebook' +base44.auth.loginWithProvider('microsoft'); +``` + +### Get Current User + +```javascript +try { + const user = await base44.auth.me(); + + if (user) { + console.log('User ID:', user.id); + console.log('Email:', user.email); + console.log('Name:', user.full_name); + console.log('Role:', user.role); + console.log('Verified:', user.is_verified); + } else { + console.log('User not authenticated'); + } + +} catch (error) { + console.error('Failed to fetch user:', error.message); + if (error.status === 401) { + // Token expired or invalid - navigate to your custom login page + navigate('/login'); + } +} +``` + +### Update User Profile + +```javascript +try { + const updatedUser = await base44.auth.updateMe({ + full_name: "John Doe", + // Custom schema fields can be updated here + phone: "+1234567890", + preferences: { theme: "dark" } + }); + + console.log('Profile updated:', updatedUser); + +} catch (error) { + console.error('Profile update failed:', error.message); + if (error.status === 400) { + console.error('Invalid data provided'); + } else if (error.status === 401) { + console.error('Authentication required'); + navigate('/login'); + } +} +``` + +### Check Authentication Status + +```javascript +try { + const isLoggedIn = await base44.auth.isAuthenticated(); + + if (isLoggedIn) { + // Show authenticated UI + console.log('User is authenticated'); + } else { + // Show login button + console.log('User is not authenticated'); + } + +} catch (error) { + console.error('Failed to check authentication:', error.message); + // On error, treat as not authenticated +} +``` + +### Logout + +```javascript +// Simple logout +base44.auth.logout(); + +// Logout and redirect to goodbye page +base44.auth.logout("/goodbye"); + +// Logout and redirect to homepage +base44.auth.logout("/"); +``` + +### Protected Route Pattern + +```javascript +// Using a navigation function (e.g., React Router's useNavigate, Next.js router) +async function requireAuth(navigate) { + try { + const user = await base44.auth.me(); + + if (!user) { + // Navigate to your custom login page + navigate('/login', { state: { returnTo: window.location.pathname } }); + return null; + } + + return user; + + } catch (error) { + console.error('Authentication check failed:', error.message); + navigate('/login', { state: { returnTo: window.location.pathname } }); + return null; + } +} + +// Usage in your app +async function loadProtectedPage(navigate) { + const user = await requireAuth(navigate); + if (!user) { + // Will navigate to login + return; + } + + // Continue with authenticated logic + console.log('Welcome,', user.full_name); +} +``` + +### Set Authentication Token + +```javascript +// SECURITY WARNING: Never hardcode tokens or expose them in client code +// Tokens should only be received from secure authentication flows + +// Set token and save to localStorage (default) +base44.auth.setToken(receivedToken); + +// Set token without saving to localStorage (temporary session) +base44.auth.setToken(receivedToken, false); + +// Verify token was set +try { + const isAuthenticated = await base44.auth.isAuthenticated(); + if (!isAuthenticated) { + console.error('Token validation failed'); + } +} catch (error) { + console.error('Failed to set token:', error.message); +} +``` + +### Invite User to Application + +```javascript +try { + // Note: Typically requires admin privileges + const response = await base44.auth.inviteUser( + "newuser@example.com", + "user" // or "admin" + ); + + console.log('Invitation sent successfully'); + +} catch (error) { + console.error('Failed to invite user:', error.message); + if (error.status === 403) { + console.error('Insufficient permissions to invite users'); + } else if (error.status === 400) { + console.error('Invalid email or role'); + } +} +``` + +### OTP Verification + +```javascript +try { + // Verify OTP code sent to user's email + await base44.auth.verifyOtp({ + email: "user@example.com", + otpCode: "123456" + }); + + console.log('OTP verified successfully'); + +} catch (error) { + console.error('OTP verification failed:', error.message); + if (error.status === 400) { + console.error('Invalid or expired OTP code'); + } else if (error.status === 429) { + console.error('Too many attempts. Please try again later.'); + } +} + +// Resend OTP if needed +try { + await base44.auth.resendOtp("user@example.com"); + console.log('OTP resent successfully'); + +} catch (error) { + console.error('Failed to resend OTP:', error.message); + if (error.status === 429) { + console.error('Too many requests. Please wait before trying again.'); + } +} +``` + +### Password Reset Flow + +```javascript +// Step 1: Request password reset +try { + await base44.auth.resetPasswordRequest("user@example.com"); + console.log('Password reset email sent. Check your inbox.'); + +} catch (error) { + console.error('Password reset request failed:', error.message); + if (error.status === 429) { + console.error('Too many requests. Please try again later.'); + } + // Note: For security, don't reveal if email exists +} + +// Step 2: Reset password with token from email +try { + await base44.auth.resetPassword({ + resetToken: "token-from-email", + newPassword: "newSecurePassword123" + }); + + console.log('Password reset successfully. You can now log in.'); + +} catch (error) { + console.error('Password reset failed:', error.message); + if (error.status === 400) { + console.error('Invalid or expired reset token'); + } else if (error.status === 422) { + console.error('Password does not meet requirements'); + } +} +``` + +### Change Password + +```javascript +try { + const currentUser = await base44.auth.me(); + + if (!currentUser) { + throw new Error('User must be authenticated to change password'); + } + + await base44.auth.changePassword({ + userId: currentUser.id, + currentPassword: "oldPassword123", + newPassword: "newSecurePassword456" + }); + + console.log('Password changed successfully'); + +} catch (error) { + console.error('Password change failed:', error.message); + if (error.status === 401) { + console.error('Current password is incorrect'); + } else if (error.status === 422) { + console.error('New password does not meet requirements'); + } else if (error.status === 403) { + console.error('Not authorized to change this password'); + } +} +``` + +--- + +## Error Handling + +### Common Error Scenarios + +The auth module can throw various errors. Here are common scenarios and how to handle them: + +#### Authentication Errors (401/403) +```javascript +try { + const user = await base44.auth.me(); +} catch (error) { + if (error.status === 401) { + // Token expired or invalid - navigate to your custom login page + navigate('/login'); + } else if (error.status === 403) { + // Email not verified or insufficient permissions + console.error('Access denied:', error.message); + } +} +``` + +#### Validation Errors (400/422) +```javascript +try { + await base44.auth.register({ + email: "invalid-email", + password: "weak" + }); +} catch (error) { + if (error.status === 400) { + console.error('Invalid input:', error.message); + // Handle validation errors + } else if (error.status === 422) { + console.error('Data validation failed:', error.details); + } +} +``` + +#### Rate Limiting (429) +```javascript +try { + await base44.auth.resendOtp("user@example.com"); +} catch (error) { + if (error.status === 429) { + console.error('Too many requests. Please wait before trying again.'); + // Show countdown or disable button temporarily + } +} +``` + +#### Generic Error Handler +```javascript +function handleAuthError(error) { + switch (error.status) { + case 400: + return 'Invalid input. Please check your data.'; + case 401: + return 'Authentication required. Redirecting to login...'; + case 403: + return 'Access denied. You may need to verify your email.'; + case 404: + return 'Resource not found.'; + case 422: + return 'Validation failed. Please check your input.'; + case 429: + return 'Too many requests. Please try again later.'; + case 500: + return 'Server error. Please try again.'; + default: + return 'An unexpected error occurred.'; + } +} + +// Usage +try { + await base44.auth.loginViaEmailPassword(email, password); +} catch (error) { + console.error(handleAuthError(error)); +} +``` + +--- + +## Auth Providers + +Configure authentication providers in your app dashboard: + +### Available Providers + +**Built-in (All Plans):** +- **Email/Password** - Default, always enabled +- **Google** - OAuth authentication +- **Microsoft** - OAuth authentication +- **Facebook** - OAuth authentication + +**SSO Providers (Elite Plan):** +- **Okta** +- **Azure AD** +- **GitHub** + +### Using OAuth Providers + +```javascript +// Initiate OAuth login flow +base44.auth.loginWithProvider('google'); + +// Return to specific page after authentication +base44.auth.loginWithProvider('microsoft', '/dashboard'); + +// Supported values: 'google', 'microsoft', 'facebook' +``` + +--- + +## Environment Availability + +| Environment | Availability | Notes | +|------------|--------------|-------| +| **Frontend** | ✅ Yes | All methods available | +| **Backend Functions** | ✅ Yes | Use `createClientFromRequest(req)` for authenticated client | +| **Service Role** | ❌ No | Auth methods not available in service role context | + +### Frontend Usage +```javascript +// Standard browser usage +const user = await base44.auth.me(); +``` + +### Backend Functions Usage +```javascript +import { createClientFromRequest } from "@base44/sdk"; + +Deno.serve(async (req) => { + const base44 = createClientFromRequest(req); + + // Get user from request context + const user = await base44.auth.me(); + + // User operations here... + return Response.json({ user }); +}); +``` + +--- + +## App Visibility + +Control who can access your app in the app settings: + +### Public Apps +- No login required for basic access +- Users can view public content without authentication +- Authenticated users get additional features/data + +### Private Apps +- Login required to access any content +- Unauthenticated users are automatically redirected to login +- All content is protected by default + +--- + +## Limitations + +### Authentication UI Options +- **Recommended:** Build custom login/signup UI using `loginViaEmailPassword()` and `loginWithProvider()` for full control over user experience and branding +- **Alternative:** `redirectToLogin()` uses Base44's hosted authentication pages with limited customization + +### Hosted Login (via redirectToLogin) +- `redirectToLogin()` shows both login and signup options on the same page +- No separate `redirectToSignup()` method +- Users can switch between login/signup on the hosted page +- ⚠️ **Note:** Prefer building custom login UI for better user experience + +### Password Requirements +- Minimum length and complexity requirements enforced +- Requirements not exposed via API +- Validation errors returned when requirements not met + +### Rate Limiting +- OTP requests are rate-limited to prevent abuse +- Password reset requests are rate-limited +- Login attempts may be rate-limited with Turnstile protection + +### Token Management +- JWTs are automatically stored in localStorage by default +- Token expiration and refresh not exposed in API +- Call `me()` or `isAuthenticated()` to verify token validity + +--- + +## Best Practices + +### 1. Always Handle Errors +```javascript +try { + await base44.auth.loginViaEmailPassword(email, password); +} catch (error) { + // Always handle authentication errors + console.error('Login failed:', error.message); +} +``` + +### 2. Verify Authentication Before Protected Actions +```javascript +const user = await requireAuth(); +if (!user) return; // Will redirect to login +// Proceed with authenticated action +``` + +### 3. Use Type Safety with TypeScript +```typescript +import type { User, LoginResponse } from '@base44/sdk'; + +const user: User = await base44.auth.me(); +const response: LoginResponse = await base44.auth.loginViaEmailPassword(email, password); +``` + +### 4. Don't Hardcode Credentials +```javascript +// ❌ BAD +base44.auth.setToken("hardcoded-token-here"); + +// ✅ GOOD +const token = await secureAuthFlow(); +base44.auth.setToken(token); +``` + +### 5. Provide User Feedback +```javascript +try { + await base44.auth.register(params); + showSuccessMessage('Registration successful! Check your email for verification code.'); +} catch (error) { + showErrorMessage('Registration failed: ' + error.message); +} +``` + +### 6. Handle Token Expiration Gracefully +```javascript +try { + const user = await base44.auth.me(); +} catch (error) { + if (error.status === 401) { + // Clear local state and navigate to login + localStorage.clear(); + navigate('/login'); + } +} +``` + +### 7. Build Custom Login UI (Recommended) +```javascript +// ✅ RECOMMENDED - Custom login form with direct methods +const handleLogin = async (email, password) => { + try { + const { user } = await base44.auth.loginViaEmailPassword(email, password); + navigate('/dashboard'); + } catch (error) { + setError(error.message); + } +}; + +const handleGoogleLogin = () => { + base44.auth.loginWithProvider('google', '/dashboard'); +}; + +// ❌ AVOID - Redirecting to hosted login page +// base44.auth.redirectToLogin(window.location.href); +``` diff --git a/evals/fixtures/sdk-integrations/skills/base44-sdk/references/base44-agents.md b/evals/fixtures/sdk-integrations/skills/base44-sdk/references/base44-agents.md new file mode 100644 index 0000000..ee55dee --- /dev/null +++ b/evals/fixtures/sdk-integrations/skills/base44-sdk/references/base44-agents.md @@ -0,0 +1,364 @@ +# Agents Module + +AI agent conversations and messages via `base44.agents`. + +> **Note:** This module requires a logged-in user. All agent methods work in the context of the authenticated user. + +## Contents +- [Concepts](#concepts) +- [Methods](#methods) +- [Examples](#examples) (Create, Get Conversations, List, Subscribe, Send Message, WhatsApp) +- [Message Structure](#message-structure) +- [Conversation Structure](#conversation-structure) +- [Common Patterns](#common-patterns) + +## Concepts + +- **Conversation**: A dialogue between user and an AI agent. Has unique ID, agent name, user reference, and metadata. +- **Message**: Single message in a conversation. Has role (`user`, `assistant`, `system`), content, timestamps, and optional metadata. + +## Methods + +| Method | Signature | Description | +|--------|-----------|-------------| +| `createConversation(params)` | `Promise` | Create a new conversation with an agent | +| `getConversations()` | `Promise` | Get all user's conversations | +| `getConversation(id)` | `Promise` | Get conversation with messages | +| `listConversations(filterParams)` | `Promise` | Filter/sort/paginate conversations | +| `subscribeToConversation(id, onUpdate?)` | `() => void` | Real-time updates via WebSocket (returns unsubscribe function) | +| `addMessage(conversation, message)` | `Promise` | Send a message | +| `getWhatsAppConnectURL(agentName)` | `string` | Get WhatsApp connection URL for agent | + +## Examples + +### Create Conversation + +```javascript +const conversation = await base44.agents.createConversation({ + agent_name: "support-agent", + metadata: { + order_id: "ORD-123", + category: "billing" + } +}); + +console.log(conversation.id); +``` + +### Get All Conversations + +```javascript +const conversations = await base44.agents.getConversations(); + +conversations.forEach(conv => { + console.log(conv.id, conv.agent_name, conv.created_date); +}); +``` + +### Get Single Conversation (with messages) + +```javascript +const conversation = await base44.agents.getConversation("conv-id-123"); + +console.log(conversation.messages); +``` + +### List with Filters + +```javascript +// Using filterParams object with q, sort, limit, skip, fields +const recent = await base44.agents.listConversations({ + q: { agent_name: "support-agent" }, + sort: "-created_date", + limit: 10, + skip: 0 +}); + +// Filter by metadata +const highPriority = await base44.agents.listConversations({ + q: { + agent_name: "support-agent", + "metadata.priority": "high" + }, + sort: "-updated_date", + limit: 20 +}); +``` + +### Subscribe to Updates (Real-time) + +```javascript +const unsubscribe = base44.agents.subscribeToConversation( + "conv-id-123", + (updatedConversation) => { + // Called when new messages arrive + console.log("New messages:", updatedConversation.messages); + } +); + +// Later: unsubscribe +unsubscribe(); +``` + +### Send a Message + +```javascript +const conversation = await base44.agents.getConversation("conv-id-123"); + +await base44.agents.addMessage(conversation, { + role: "user", + content: "What's the weather like today?" +}); +``` + +### Get WhatsApp Connection URL + +```javascript +const whatsappUrl = base44.agents.getWhatsAppConnectURL("support-agent"); +// Returns URL for users to connect with agent via WhatsApp +console.log(whatsappUrl); +``` + +## Message Structure + +```javascript +{ + role: "user" | "assistant" | "system", + content: "Message text or structured object", + created_date: "2024-01-15T10:30:00Z", + updated_date: "2024-01-15T10:30:00Z", + + // Optional fields + reasoning: { + content: "Agent's reasoning process", + timing: 1500 + }, + tool_calls: [{ + name: "search", + arguments: { query: "weather" }, + result: { ... }, + status: "success" + }], + file_urls: ["https://..."], + usage: { + prompt_tokens: 150, + completion_tokens: 50 + }, + metadata: { ... }, + custom_context: { ... } +} +``` + +## Conversation Structure + +```javascript +{ + id: "conv-id-123", + app_id: "app-id", + agent_name: "support-agent", + created_by_id: "user-id", + created_date: "2024-01-15T10:00:00Z", + updated_date: "2024-01-15T10:30:00Z", + messages: [ ... ], + metadata: { ... } +} +``` + +## Common Patterns + +### Chat Interface + +```javascript +// Load conversation +const conv = await base44.agents.getConversation(conversationId); +setMessages(conv.messages); + +// Subscribe to updates +const unsubscribe = base44.agents.subscribeToConversation(conversationId, (updated) => { + setMessages(updated.messages); +}); + +// Send message +async function sendMessage(text) { + await base44.agents.addMessage(conv, { role: "user", content: text }); +} + +// Cleanup on unmount +return () => unsubscribe(); +``` + +## Type Definitions + +### AgentConversation + +```typescript +/** An agent conversation containing messages exchanged with an AI agent. */ +interface AgentConversation { + /** Unique identifier for the conversation. */ + id: string; + /** Application ID. */ + app_id: string; + /** Name of the agent in this conversation. */ + agent_name: string; + /** ID of the user who created the conversation. */ + created_by_id: string; + /** When the conversation was created. */ + created_date: string; + /** When the conversation was last updated. */ + updated_date: string; + /** Array of messages in the conversation. */ + messages: AgentMessage[]; + /** Optional metadata associated with the conversation. */ + metadata?: Record; +} +``` + +### AgentMessage + +```typescript +/** A message in an agent conversation. */ +interface AgentMessage { + /** Unique identifier for the message. */ + id: string; + /** Role of the message sender. */ + role: "user" | "assistant" | "system"; + /** When the message was created. */ + created_date: string; + /** When the message was last updated. */ + updated_date: string; + /** Message content. */ + content?: string | Record; + /** Optional reasoning information for the message. */ + reasoning?: AgentMessageReasoning | null; + /** URLs to files attached to the message. */ + file_urls?: string[]; + /** Tool calls made by the agent. */ + tool_calls?: AgentMessageToolCall[]; + /** Token usage statistics. */ + usage?: AgentMessageUsage; + /** Whether the message is hidden from the user. */ + hidden?: boolean; + /** Custom context provided with the message. */ + custom_context?: AgentMessageCustomContext[]; + /** Model used to generate the message. */ + model?: string; + /** Checkpoint ID for the message. */ + checkpoint_id?: string; + /** Metadata about when and by whom the message was created. */ + metadata?: AgentMessageMetadata; +} +``` + +### Supporting Types + +```typescript +/** Reasoning information for an agent message. */ +interface AgentMessageReasoning { + /** When reasoning started. */ + start_date: string; + /** When reasoning ended. */ + end_date?: string; + /** Reasoning content. */ + content: string; +} + +/** A tool call made by the agent. */ +interface AgentMessageToolCall { + /** Tool call ID. */ + id: string; + /** Name of the tool called. */ + name: string; + /** Arguments passed to the tool as JSON string. */ + arguments_string: string; + /** Status of the tool call. */ + status: "running" | "success" | "error" | "stopped"; + /** Results from the tool call. */ + results?: string; +} + +/** Token usage statistics for an agent message. */ +interface AgentMessageUsage { + /** Number of tokens in the prompt. */ + prompt_tokens?: number; + /** Number of tokens in the completion. */ + completion_tokens?: number; +} + +/** Custom context provided with an agent message. */ +interface AgentMessageCustomContext { + /** Context message. */ + message: string; + /** Associated data for the context. */ + data: Record; + /** Type of context. */ + type: string; +} + +/** Metadata about when and by whom a message was created. */ +interface AgentMessageMetadata { + /** When the message was created. */ + created_date: string; + /** Email of the user who created the message. */ + created_by_email: string; + /** Full name of the user who created the message. */ + created_by_full_name: string; +} +``` + +### CreateConversationParams + +```typescript +/** Parameters for creating a new conversation. */ +interface CreateConversationParams { + /** The name of the agent to create a conversation with. */ + agent_name: string; + /** Optional metadata to attach to the conversation. */ + metadata?: Record; +} +``` + +### ModelFilterParams + +```typescript +/** Parameters for filtering, sorting, and paginating conversations. */ +interface ModelFilterParams { + /** Query object with field-value pairs for filtering. */ + q?: Record; + /** Sort parameter (e.g., "-created_date" for descending). */ + sort?: string | null; + /** Maximum number of results to return. */ + limit?: number | null; + /** Number of results to skip for pagination. */ + skip?: number | null; + /** Array of field names to include in the response. */ + fields?: string[] | null; +} +``` + +### AgentsModule + +```typescript +/** Agents module for managing AI agent conversations. */ +interface AgentsModule { + /** Gets all conversations from all agents in the app. */ + getConversations(): Promise; + + /** Gets a specific conversation by ID. */ + getConversation(conversationId: string): Promise; + + /** Lists conversations with filtering, sorting, and pagination. */ + listConversations(filterParams: ModelFilterParams): Promise; + + /** Creates a new conversation with an agent. */ + createConversation(conversation: CreateConversationParams): Promise; + + /** Adds a message to a conversation. */ + addMessage(conversation: AgentConversation, message: Partial): Promise; + + /** Subscribes to real-time updates for a conversation. Returns unsubscribe function. */ + subscribeToConversation(conversationId: string, onUpdate?: (conversation: AgentConversation) => void): () => void; + + /** Gets WhatsApp connection URL for an agent. */ + getWhatsAppConnectURL(agentName: string): string; +} +``` diff --git a/evals/fixtures/sdk-integrations/skills/base44-sdk/references/client.md b/evals/fixtures/sdk-integrations/skills/base44-sdk/references/client.md new file mode 100644 index 0000000..554bf2b --- /dev/null +++ b/evals/fixtures/sdk-integrations/skills/base44-sdk/references/client.md @@ -0,0 +1,257 @@ +# Client Setup + +How to create and configure the Base44 client. + +## Contents +- [In Base44-Generated Apps](#in-base44-generated-apps) +- [In External Apps](#in-external-apps) +- [In Backend Functions](#in-backend-functions) +- [Authentication Modes](#authentication-modes) (Anonymous, User, Service Role) +- [Available Modules](#available-modules) +- [Client Methods](#client-methods) +- [Client Configuration Options](#client-configuration-options) + +## In Base44-Generated Apps + +The client is pre-configured and available as `base44`. Just use it: + +```javascript +const tasks = await base44.entities.Task.list(); +``` + +## In External Apps + +Install the SDK and create a client: + +```bash +npm install @base44/sdk +``` + +```javascript +import { createClient } from "@base44/sdk"; + +// IMPORTANT: The parameter name is 'appId' (NOT 'clientId', NOT 'id') +// IMPORTANT: onError must be nested inside 'options' object +const base44 = createClient({ + appId: "your-app-id", // Required: Use 'appId' parameter + token: "optional-user-token", // Optional: for pre-authenticated requests + options: { // Optional: configuration options + onError: (error) => { // Optional: error handler (must be in options) + console.error("Base44 error:", error); + } + } +}); +``` + +**Common Mistakes:** +- ❌ `createClient({ clientId: "..." })` - WRONG parameter name +- ❌ `createClient({ id: "..." })` - WRONG parameter name +- ❌ `createClient({ appId: "...", onError: ... })` - WRONG: onError must be in options +- ✅ `createClient({ appId: "..." })` - CORRECT parameter name +- ✅ `createClient({ appId: "...", options: { onError: ... } })` - CORRECT: onError in options + +## In Backend Functions + +Use `createClientFromRequest` to get a client with the caller's auth context: + +```javascript +import { createClientFromRequest } from "@base44/sdk"; + +Deno.serve(async (req) => { + const base44 = createClientFromRequest(req); + + // Client inherits authentication from the request + const user = await base44.auth.me(); + + return Response.json({ user }); +}); +``` + +## Authentication Modes + +| Mode | How to Get | Permissions | +|------|-----------|-------------| +| **Anonymous** | `createClient({ appId })` without token | Public data only | +| **User** | After `loginViaEmailPassword()` or via `createClientFromRequest` | User's own data | +| **Service Role** | `base44.asServiceRole.*` in backend | Full admin access | + +## Anonymous Mode + +No authentication. Can only access public resources. + +```javascript +const base44 = createClient({ appId: "your-app-id" }); + +// Only works if Task entity allows anonymous read +const publicTasks = await base44.entities.Task.list(); +``` + +## User Mode + +After user logs in, the client automatically includes their token. + +```javascript +const base44 = createClient({ appId: "your-app-id" }); + +// Login sets the token +await base44.auth.loginViaEmailPassword("user@example.com", "password"); + +// Subsequent requests are authenticated +const user = await base44.auth.me(); +const myTasks = await base44.entities.Task.list(); // filtered by permissions +``` + +## Service Role Mode + +Admin-level access. **Backend only.** + +```javascript +// Inside a backend function +Deno.serve(async (req) => { + const base44 = createClientFromRequest(req); + + // User mode - respects permissions + const myTasks = await base44.entities.Task.list(); + + // Service role - bypasses permissions + const allTasks = await base44.asServiceRole.entities.Task.list(); + const allUsers = await base44.asServiceRole.entities.User.list(); + const oauthToken = await base44.asServiceRole.connectors.getAccessToken("slack"); + + return Response.json({ myTasks, allTasks }); +}); +``` + +## Available Modules + +The client exposes these modules: + +```javascript +base44.entities // CRUD operations +base44.auth // Authentication +base44.agents // AI conversations +base44.functions // Backend function invocation +base44.integrations // Third-party services +base44.analytics // Event tracking +base44.appLogs // App usage logging +base44.users // User invitations + +// Service role only (backend) +base44.asServiceRole.entities +base44.asServiceRole.agents +base44.asServiceRole.functions +base44.asServiceRole.integrations +base44.asServiceRole.appLogs +base44.asServiceRole.connectors +``` + +## Client Methods + +The client provides these methods: + +```javascript +// Set authentication token for all subsequent requests +base44.setToken(newToken); + +// Cleanup WebSocket connections (call when done with client) +base44.cleanup(); +``` + +### setToken + +Updates the authentication token for all subsequent API requests and WebSocket connections. + +```javascript +// After receiving a token (e.g., from external auth) +base44.setToken("new-jwt-token"); +``` + +### cleanup + +Disconnects WebSocket connections. Call when you're done with the client or when the component unmounts. + +```javascript +// Cleanup on component unmount (React example) +useEffect(() => { + return () => base44.cleanup(); +}, []); +``` + +## Client Configuration Options + +```javascript +createClient({ + appId: "your-app-id", // Required: MUST use 'appId' (not 'clientId' or 'id') + token: "jwt-token", // Optional: pre-set auth token + options: { // Optional: configuration options + onError: (error) => {} // Optional: global error handler (must be in options) + } +}); +``` + +**⚠️ Critical:** +- The parameter name is `appId`, not `clientId` or `id`. Using the wrong parameter name will cause errors. +- The `onError` handler must be nested inside the `options` object, not at the top level. + +## Type Definitions + +### CreateClientConfig + +```typescript +/** Configuration for creating a Base44 client. */ +interface CreateClientConfig { + /** The Base44 app ID (required). */ + appId: string; + /** User authentication token. Used to authenticate as a specific user. */ + token?: string; + /** Service role authentication token (backend only). */ + serviceToken?: string; + /** Additional client options. */ + options?: CreateClientOptions; +} + +/** Options for creating a Base44 client. */ +interface CreateClientOptions { + /** Optional error handler called whenever an API error occurs. */ + onError?: (error: Error) => void; +} +``` + +### Base44Client + +```typescript +/** The Base44 client instance. */ +interface Base44Client { + /** Entities module for CRUD operations on your data models. */ + entities: EntitiesModule; + /** Integrations module for calling pre-built integration endpoints. */ + integrations: IntegrationsModule; + /** Auth module for user authentication and management. */ + auth: AuthModule; + /** Functions module for invoking custom backend functions. */ + functions: FunctionsModule; + /** Agents module for managing AI agent conversations. */ + agents: AgentsModule; + /** App logs module for tracking app usage. */ + appLogs: AppLogsModule; + /** Analytics module for tracking custom events. */ + analytics: AnalyticsModule; + + /** Cleanup function to disconnect WebSocket connections. */ + cleanup(): void; + + /** Sets a new authentication token for all subsequent requests. */ + setToken(newToken: string): void; + + /** Provides access to modules with elevated service role permissions (backend only). */ + readonly asServiceRole: { + entities: EntitiesModule; + integrations: IntegrationsModule; + connectors: ConnectorsModule; + functions: FunctionsModule; + agents: AgentsModule; + appLogs: AppLogsModule; + cleanup(): void; + }; +} +``` diff --git a/evals/fixtures/sdk-integrations/skills/base44-sdk/references/connectors.md b/evals/fixtures/sdk-integrations/skills/base44-sdk/references/connectors.md new file mode 100644 index 0000000..f3fbcb4 --- /dev/null +++ b/evals/fixtures/sdk-integrations/skills/base44-sdk/references/connectors.md @@ -0,0 +1,113 @@ +# Connectors Module + +OAuth token management for external services. **Service role only** (backend functions). + +## Method + +```javascript +base44.asServiceRole.connectors.getAccessToken(integrationType): Promise +``` + +Returns a raw OAuth access token for the specified service. + +## Supported Services + +| Integration Type | Service | +|-----------------|---------| +| `"googlecalendar"` | Google Calendar | +| `"googledrive"` | Google Drive | +| `"slack"` | Slack | +| `"notion"` | Notion | +| `"salesforce"` | Salesforce | +| `"hubspot"` | HubSpot | +| `"linkedin"` | LinkedIn | +| `"tiktok"` | TikTok | +| `"github"` | GitHub | + +## Example Usage + +```javascript +// Backend function only +Deno.serve(async (req) => { + const base44 = createClientFromRequest(req); + + // Get OAuth token for Slack + const slackToken = await base44.asServiceRole.connectors.getAccessToken("slack"); + + // Use token directly with Slack API + const response = await fetch("https://slack.com/api/chat.postMessage", { + method: "POST", + headers: { + "Authorization": `Bearer ${slackToken}`, + "Content-Type": "application/json" + }, + body: JSON.stringify({ + channel: "#general", + text: "Hello from Base44!" + }) + }); + + return Response.json(await response.json()); +}); +``` + +## Google Calendar Example + +```javascript +Deno.serve(async (req) => { + const base44 = createClientFromRequest(req); + + const token = await base44.asServiceRole.connectors.getAccessToken("googlecalendar"); + + // List upcoming events + const response = await fetch( + "https://www.googleapis.com/calendar/v3/calendars/primary/events?" + + new URLSearchParams({ + maxResults: "10", + orderBy: "startTime", + singleEvents: "true", + timeMin: new Date().toISOString() + }), + { + headers: { "Authorization": `Bearer ${token}` } + } + ); + + const events = await response.json(); + return Response.json(events); +}); +``` + +## Setup Requirements + +1. **Builder plan** or higher +2. **Backend functions** enabled +3. **Connector configured** in Base44 dashboard (OAuth flow completed) + +## Important Notes + +- **One account per connector per app**: All users share the same connected account +- **Backend only**: `connectors` module not available in frontend code +- **Service role required**: Must use `base44.asServiceRole.connectors` +- **You handle the API calls**: Base44 only provides the token; you make the actual API requests +- **Token refresh**: Base44 handles token refresh automatically + +## Type Definitions + +```typescript +/** + * The type of external integration/connector. + * Examples: 'googlecalendar', 'slack', 'github', 'notion', etc. + */ +type ConnectorIntegrationType = string; + +/** Connectors module for managing OAuth tokens for external services. */ +interface ConnectorsModule { + /** + * Retrieves an OAuth access token for a specific external integration type. + * @param integrationType - The type of integration (e.g., 'googlecalendar', 'slack'). + * @returns Promise resolving to the access token string. + */ + getAccessToken(integrationType: ConnectorIntegrationType): Promise; +} +``` diff --git a/evals/fixtures/sdk-integrations/skills/base44-sdk/references/entities.md b/evals/fixtures/sdk-integrations/skills/base44-sdk/references/entities.md new file mode 100644 index 0000000..a7bdf35 --- /dev/null +++ b/evals/fixtures/sdk-integrations/skills/base44-sdk/references/entities.md @@ -0,0 +1,253 @@ +# Entities Module + +CRUD operations on data models. Access via `base44.entities.EntityName.method()`. + +## Contents +- [Methods](#methods) +- [Examples](#examples) (Create, Bulk Create, List, Filter, Get, Update, Delete, Subscribe) +- [User Entity](#user-entity) +- [Service Role Access](#service-role-access) +- [Permissions](#permissions) + +## Methods + +| Method | Signature | Description | +|--------|-----------|-------------| +| `create(data)` | `Promise` | Create one record | +| `bulkCreate(dataArray)` | `Promise` | Create multiple records | +| `list(sort?, limit?, skip?, fields?)` | `Promise` | Get all records (paginated) | +| `filter(query, sort?, limit?, skip?, fields?)` | `Promise` | Get records matching conditions | +| `get(id)` | `Promise` | Get single record by ID | +| `update(id, data)` | `Promise` | Update record (partial update) | +| `delete(id)` | `Promise` | Delete record by ID | +| `deleteMany(query)` | `Promise` | Delete all matching records | +| `importEntities(file)` | `Promise` | Import from CSV (frontend only) | +| `subscribe(callback)` | `() => void` | Subscribe to realtime updates (returns unsubscribe function) | + +## Examples + +### Create + +```javascript +const task = await base44.entities.Task.create({ + title: "Complete documentation", + status: "pending", + dueDate: "2024-12-31" +}); +``` + +### Bulk Create + +```javascript +const tasks = await base44.entities.Task.bulkCreate([ + { title: "Task 1", status: "pending" }, + { title: "Task 2", status: "pending" } +]); +``` + +### List with Pagination + +```javascript +// Get first 10 records, sorted by created_date descending +const tasks = await base44.entities.Task.list( + "-created_date", // sort (string: prefix with - for descending) + 10, // limit + 0 // skip +); + +// Get next page +const page2 = await base44.entities.Task.list("-created_date", 10, 10); +``` + +### Filter + +```javascript +// Simple filter +const pending = await base44.entities.Task.filter({ status: "pending" }); + +// Multiple conditions +const myPending = await base44.entities.Task.filter({ + status: "pending", + assignedTo: userId +}); + +// With sort, limit, skip +const recent = await base44.entities.Task.filter( + { status: "pending" }, + "-created_date", // sort (string: prefix with - for descending) + 5, + 0 +); + +// Select specific fields +const titles = await base44.entities.Task.filter( + { status: "pending" }, + null, + null, + null, + ["id", "title"] +); +``` + +### Get by ID + +```javascript +const task = await base44.entities.Task.get("task-id-123"); +``` + +### Update + +```javascript +// Partial update - only specified fields change +await base44.entities.Task.update("task-id-123", { + status: "completed", + completedAt: new Date().toISOString() +}); +``` + +### Delete + +```javascript +// Single record +await base44.entities.Task.delete("task-id-123"); + +// Multiple records matching query +await base44.entities.Task.deleteMany({ status: "archived" }); +``` + +### Subscribe to Realtime Updates + +```javascript +// Subscribe to all changes on Task entity +const unsubscribe = base44.entities.Task.subscribe((event) => { + console.log(`Task ${event.id} was ${event.type}:`, event.data); + // event.type is "create", "update", or "delete" +}); + +// Later: unsubscribe to stop receiving updates +unsubscribe(); +``` + +**Event structure:** +```javascript +{ + type: "create" | "update" | "delete", + data: { ... }, // the entity data + id: "entity-id", // the affected entity's ID + timestamp: "2024-01-15T10:30:00Z" +} +``` + +## User Entity + +Every app has a built-in `User` entity with special rules: + +- Regular users can only read/update **their own** record +- Cannot create users via `entities.create()` - use `auth.register()` instead +- Service role has full access to all user records + +```javascript +// Get current user's record +const me = await base44.entities.User.get(currentUserId); + +// Service role: get any user +const anyUser = await base44.asServiceRole.entities.User.get(userId); +``` + +## Service Role Access + +For admin-level operations (bypass user permissions): + +```javascript +// Backend only +const allTasks = await base44.asServiceRole.entities.Task.list(); +const allUsers = await base44.asServiceRole.entities.User.list(); +``` + +## Permissions (RLS & FLS) + +Data access is controlled by **Row Level Security (RLS)** and **Field Level Security (FLS)** rules defined in entity schemas. + +1. **Authentication level**: anonymous, authenticated, or service role +2. **RLS rules**: Control which records (rows) users can create/read/update/delete +3. **FLS rules**: Control which fields users can read/write within accessible records + +Operations succeed or fail based on these rules - no partial results. + +RLS and FLS are configured in entity schema files (`base44/entities/*.jsonc`). See [entities-create.md](../../base44-cli/references/entities-create.md#row-level-security-rls) for configuration details. + +**Note:** `asServiceRole` sets the user's role to `"admin"` but does NOT bypass RLS. Your RLS rules must include admin access (e.g., `{ "user_condition": { "role": "admin" } }`) for service role operations to succeed. + +## Type Definitions + +### RealtimeEvent + +```typescript +/** Event types for realtime entity updates. */ +type RealtimeEventType = "create" | "update" | "delete"; + +/** Payload received when a realtime event occurs. */ +interface RealtimeEvent { + /** The type of change that occurred. */ + type: RealtimeEventType; + /** The entity data. */ + data: any; + /** The unique identifier of the affected entity. */ + id: string; + /** ISO 8601 timestamp of when the event occurred. */ + timestamp: string; +} + +/** Callback function invoked when a realtime event occurs. */ +type RealtimeCallback = (event: RealtimeEvent) => void; + +/** Function returned from subscribe, call it to unsubscribe. */ +type Subscription = () => void; +``` + +### EntityHandler + +```typescript +/** Entity handler providing CRUD operations for a specific entity type. */ +interface EntityHandler { + /** Lists records with optional pagination and sorting. */ + list(sort?: string, limit?: number, skip?: number, fields?: string[]): Promise; + + /** Filters records based on a query. */ + filter(query: Record, sort?: string, limit?: number, skip?: number, fields?: string[]): Promise; + + /** Gets a single record by ID. */ + get(id: string): Promise; + + /** Creates a new record. */ + create(data: Record): Promise; + + /** Updates an existing record. */ + update(id: string, data: Record): Promise; + + /** Deletes a single record by ID. */ + delete(id: string): Promise; + + /** Deletes multiple records matching a query. */ + deleteMany(query: Record): Promise; + + /** Creates multiple records in a single request. */ + bulkCreate(data: Record[]): Promise; + + /** Imports records from a file (frontend only). */ + importEntities(file: File): Promise; + + /** Subscribes to realtime updates. Returns unsubscribe function. */ + subscribe(callback: RealtimeCallback): Subscription; +} +``` + +### EntitiesModule + +```typescript +/** Entities module for managing app data. */ +interface EntitiesModule { + /** Access any entity by name dynamically. */ + [entityName: string]: EntityHandler; +} +``` diff --git a/evals/fixtures/sdk-integrations/skills/base44-sdk/references/functions.md b/evals/fixtures/sdk-integrations/skills/base44-sdk/references/functions.md new file mode 100644 index 0000000..8072465 --- /dev/null +++ b/evals/fixtures/sdk-integrations/skills/base44-sdk/references/functions.md @@ -0,0 +1,216 @@ +# Functions Module + +Invoke custom backend functions via `base44.functions`. + +## Contents +- [Method](#method) +- [Invoking Functions](#invoking-functions) (Frontend, File Upload, Service Role, REST API) +- [Writing Backend Functions](#writing-backend-functions) (Basic, Service Role, Secrets, Errors) +- [Setup Requirements](#setup-requirements) +- [Authentication Modes](#authentication-modes) + +## Method + +```javascript +base44.functions.invoke(functionName, data): Promise +``` + +- `functionName`: Name of the backend function +- `data`: Object of parameters (sent as JSON, or multipart if contains File objects) +- Returns: Whatever the function returns + +## Invoking Functions + +### From Frontend + +```javascript +const result = await base44.functions.invoke("processOrder", { + orderId: "order-123", + action: "ship" +}); + +console.log(result); +``` + +### With File Upload + +```javascript +const fileInput = document.querySelector('input[type="file"]'); +const file = fileInput.files[0]; + +// Automatically uses multipart/form-data when File objects present +const result = await base44.functions.invoke("uploadDocument", { + file: file, + category: "invoices" +}); +``` + +### With Service Role (Backend) + +```javascript +// Inside another backend function +const result = await base44.asServiceRole.functions.invoke("adminTask", { + userId: "user-123" +}); +``` + +### Via REST API (curl) + +Functions can be called via HTTP POST to your app domain: + +```bash +curl -X POST "https:///functions/" \ + -H "Content-Type: application/json" \ + -d '{"key": "value"}' +``` + +## Writing Backend Functions + +Backend functions run on Deno. Must export using `Deno.serve()`. + +### Required Directory Structure + +Each function must be in its own subdirectory under `base44/functions/` with a configuration file: + +``` +base44/ + functions/ + process-order/ # kebab-case directory name + function.jsonc # required configuration + index.ts # entry point +``` + +**function.jsonc:** +```jsonc +{ + "name": "process-order", + "entry": "index.ts" +} +``` + +For complete setup and deployment instructions, see [functions-create.md](../../base44-cli/references/functions-create.md) in base44-cli. + +### Basic Structure + +```javascript +// base44/functions/process-order/index.ts +import { createClientFromRequest } from "npm:@base44/sdk"; + +Deno.serve(async (req) => { + // Get authenticated client from request + const base44 = createClientFromRequest(req); + + // Parse input + const { orderId, action } = await req.json(); + + // Your logic here + const order = await base44.entities.Orders.get(orderId); + + // Return response + return Response.json({ + success: true, + order: order + }); +}); +``` + +### With Service Role Access + +```javascript +import { createClientFromRequest } from "npm:@base44/sdk"; + +Deno.serve(async (req) => { + const base44 = createClientFromRequest(req); + + // Check user is authenticated + const user = await base44.auth.me(); + if (!user) { + return Response.json({ error: "Unauthorized" }, { status: 401 }); + } + + // Use service role for admin operations + const allOrders = await base44.asServiceRole.entities.Orders.list(); + + return Response.json({ orders: allOrders }); +}); +``` + +### Using Secrets + +```javascript +Deno.serve(async (req) => { + // Access environment variables (configured in app settings) + const apiKey = Deno.env.get("STRIPE_API_KEY"); + + const response = await fetch("https://api.stripe.com/v1/charges", { + headers: { + "Authorization": `Bearer ${apiKey}` + } + }); + + return Response.json(await response.json()); +}); +``` + +### Error Handling + +```javascript +import { createClientFromRequest } from "npm:@base44/sdk"; + +Deno.serve(async (req) => { + try { + const base44 = createClientFromRequest(req); + const { orderId } = await req.json(); + + const order = await base44.entities.Orders.get(orderId); + if (!order) { + return Response.json( + { error: "Order not found" }, + { status: 404 } + ); + } + + return Response.json({ order }); + + } catch (error) { + return Response.json( + { error: error.message }, + { status: 500 } + ); + } +}); +``` + +## Setup Requirements + +1. Enable Backend Functions in app settings (requires appropriate plan) +2. Create function files in `/functions` folder +3. Configure secrets via app dashboard for API keys + +## Authentication Modes + +| Mode | Context | Permissions | +|------|---------|-------------| +| User | `base44.functions.invoke()` | Runs under calling user's permissions | +| Service Role | `base44.asServiceRole.functions.invoke()` | Admin-level access | + +Inside the function, use `createClientFromRequest(req)` to get a client that inherits the caller's auth context. + +## Type Definitions + +```typescript +/** Functions module for invoking custom backend functions. */ +interface FunctionsModule { + /** + * Invokes a custom backend function by name. + * + * If any parameter is a File object, the request will automatically be + * sent as multipart/form-data. Otherwise, it will be sent as JSON. + * + * @param functionName - The name of the function to invoke. + * @param data - An object containing named parameters for the function. + * @returns Promise resolving to the function's response. + */ + invoke(functionName: string, data: Record): Promise; +} +``` diff --git a/evals/fixtures/sdk-integrations/skills/base44-sdk/references/integrations.md b/evals/fixtures/sdk-integrations/skills/base44-sdk/references/integrations.md new file mode 100644 index 0000000..f356a18 --- /dev/null +++ b/evals/fixtures/sdk-integrations/skills/base44-sdk/references/integrations.md @@ -0,0 +1,377 @@ +# Integrations Module + +Access third-party services via `base44.integrations`. + +## Types of Integrations + +1. **Core/Built-in**: AI, email, file uploads (available by default) +2. **Catalog integrations**: Pre-built connectors from Base44 catalog +3. **Custom integrations**: Your own OpenAPI-based integrations + +## Accessing Integrations + +```javascript +// Core integrations +base44.integrations.Core.FunctionName(params) + +// Custom integrations +base44.integrations.custom.call(slug, operationId, params) +``` + +## Core Integrations + +### InvokeLLM (AI Text Generation) + +Generate text or structured JSON data using AI models. + +```javascript +// Basic prompt - returns string +const response = await base44.integrations.Core.InvokeLLM({ + prompt: "Summarize this text: ..." +}); + +// With internet context (uses Google Search, Maps, News) +const response = await base44.integrations.Core.InvokeLLM({ + prompt: "What's the current weather in NYC?", + add_context_from_internet: true +}); + +// Structured JSON response - returns object +const response = await base44.integrations.Core.InvokeLLM({ + prompt: "Analyze the sentiment of: 'Great product but slow shipping'", + response_json_schema: { + type: "object", + properties: { + sentiment: { type: "string", enum: ["positive", "negative", "mixed"] }, + score: { type: "number", description: "Score from 1-10" }, + key_points: { type: "array", items: { type: "string" } } + } + } +}); +// Returns: { sentiment: "mixed", score: 7, key_points: ["great product", "slow shipping"] } + +// With file attachments (uploaded via UploadFile) +const response = await base44.integrations.Core.InvokeLLM({ + prompt: "Describe what's in this image", + file_urls: ["https://...uploaded_image.png"] +}); +``` + +**Parameters:** +- `prompt` (string, required): The prompt text to send to the model +- `add_context_from_internet` (boolean, optional): If true, uses Google Search/Maps/News for real-time context +- `response_json_schema` (object, optional): JSON schema for structured output +- `file_urls` (string[], optional): URLs of uploaded files for context + +### GenerateImage + +Create AI-generated images from text prompts. + +```javascript +const { url } = await base44.integrations.Core.GenerateImage({ + prompt: "A serene mountain landscape with a lake" +}); +console.log(url); // https://...generated_image.png +``` + +### SendEmail + +Send emails to registered users. Every app gets this integration (no plan upgrade required). + +```javascript +await base44.integrations.Core.SendEmail({ + to: "user@example.com", + subject: "Welcome!", + body: "

Hello

Welcome to our app.

", + from_name: "My App" // optional, defaults to app name +}); +``` + +**Parameters:** +- `to` (string, required): Recipient email address +- `subject` (string, required): Email subject line +- `body` (string, required): Plain text or HTML email body +- `from_name` (string, optional): Sender name displayed to recipient + +**Limitations:** +- 1 credit per email (2 credits with custom domain) + +### UploadFile (Public) + +Upload files to public storage. + +```javascript +const fileInput = document.querySelector('input[type="file"]'); +const { file_url } = await base44.integrations.Core.UploadFile({ + file: fileInput.files[0] +}); +console.log(file_url); // https://...uploaded_file.pdf +``` + +### UploadPrivateFile + +Upload files to private storage that requires a signed URL to access. + +```javascript +const { file_uri } = await base44.integrations.Core.UploadPrivateFile({ + file: fileInput.files[0] +}); +console.log(file_uri); // "private/user123/document.pdf" + +// Create a signed URL to access the file +const { signed_url } = await base44.integrations.Core.CreateFileSignedUrl({ + file_uri, + expires_in: 3600 // URL expires in 1 hour (default: 300 seconds) +}); +console.log(signed_url); // Temporary URL to access the private file +``` + +### CreateFileSignedUrl + +Generate temporary access links for private files. + +```javascript +const { signed_url } = await base44.integrations.Core.CreateFileSignedUrl({ + file_uri: "private/user123/document.pdf", + expires_in: 7200 // 2 hours +}); +``` + +**Parameters:** +- `file_uri` (string, required): URI from UploadPrivateFile +- `expires_in` (number, optional): Expiration time in seconds (default: 300) + +### ExtractDataFromUploadedFile + +Extract structured data from uploaded files using AI. + +```javascript +// First upload the file +const { file_url } = await base44.integrations.Core.UploadFile({ file }); + +// Then extract structured data +const result = await base44.integrations.Core.ExtractDataFromUploadedFile({ + file_url, + json_schema: { + type: "object", + properties: { + invoice_number: { type: "string" }, + total_amount: { type: "number" }, + date: { type: "string" }, + vendor_name: { type: "string" } + } + } +}); +console.log(result); // { invoice_number: "INV-12345", total_amount: 1250.00, ... } +``` + +## Custom Integrations + +Custom integrations allow workspace administrators to connect any external API by importing an OpenAPI specification. Use `base44.integrations.custom.call()` to invoke them. + +### Syntax + +```javascript +const response = await base44.integrations.custom.call( + slug, // Integration identifier (set by admin) + operationId, // Endpoint in "method:path" format + params // Optional: payload, pathParams, queryParams +); +``` + +### Examples + +```javascript +// GET request with query params +const response = await base44.integrations.custom.call( + "my-crm", + "get:/contacts", + { queryParams: { limit: 10, status: "active" } } +); + +// POST request with body +const response = await base44.integrations.custom.call( + "my-crm", + "post:/contacts", + { payload: { name: "John Doe", email: "john@example.com" } } +); + +// Request with path parameters +const response = await base44.integrations.custom.call( + "github", + "post:/repos/{owner}/{repo}/issues", + { + pathParams: { owner: "myorg", repo: "myrepo" }, + payload: { title: "Bug report", body: "Something is broken" } + } +); +``` + +### Response Structure + +```javascript +{ + success: true, // Whether external API returned 2xx + status_code: 200, // HTTP status code + data: { ... } // Response from external API +} +``` + +## Requirements + +- **Core integrations**: Available on all plans +- **Catalog/Custom integrations**: Require Builder plan or higher + +## Type Definitions + +### Core Integration Parameters + +```typescript +/** Parameters for the InvokeLLM function. */ +interface InvokeLLMParams { + /** The prompt text to send to the model. */ + prompt: string; + /** If true, uses Google Search/Maps/News for real-time context. */ + add_context_from_internet?: boolean; + /** JSON schema for structured output. If provided, returns object instead of string. */ + response_json_schema?: object; + /** File URLs (from UploadFile) to provide as context. */ + file_urls?: string[]; +} + +/** Parameters for the GenerateImage function. */ +interface GenerateImageParams { + /** Description of the image to generate. */ + prompt: string; +} + +/** Result from GenerateImage. */ +interface GenerateImageResult { + /** URL of the generated image. */ + url: string; +} + +/** Parameters for the UploadFile function. */ +interface UploadFileParams { + /** The file object to upload. */ + file: File; +} + +/** Result from UploadFile. */ +interface UploadFileResult { + /** URL of the uploaded file. */ + file_url: string; +} + +/** Parameters for the SendEmail function. */ +interface SendEmailParams { + /** Recipient email address. */ + to: string; + /** Email subject line. */ + subject: string; + /** Plain text or HTML email body. */ + body: string; + /** Sender name (defaults to app name). */ + from_name?: string; +} + +/** Parameters for ExtractDataFromUploadedFile. */ +interface ExtractDataFromUploadedFileParams { + /** URL of the uploaded file. */ + file_url: string; + /** JSON schema defining fields to extract. */ + json_schema: object; +} + +/** Parameters for UploadPrivateFile. */ +interface UploadPrivateFileParams { + /** The file object to upload. */ + file: File; +} + +/** Result from UploadPrivateFile. */ +interface UploadPrivateFileResult { + /** URI of the private file (used for signed URLs). */ + file_uri: string; +} + +/** Parameters for CreateFileSignedUrl. */ +interface CreateFileSignedUrlParams { + /** URI from UploadPrivateFile. */ + file_uri: string; + /** Expiration time in seconds (default: 300). */ + expires_in?: number; +} + +/** Result from CreateFileSignedUrl. */ +interface CreateFileSignedUrlResult { + /** Temporary signed URL to access the file. */ + signed_url: string; +} +``` + +### CoreIntegrations + +```typescript +/** Core package containing built-in Base44 integration functions. */ +interface CoreIntegrations { + InvokeLLM(params: InvokeLLMParams): Promise; + GenerateImage(params: GenerateImageParams): Promise; + UploadFile(params: UploadFileParams): Promise; + SendEmail(params: SendEmailParams): Promise; + ExtractDataFromUploadedFile(params: ExtractDataFromUploadedFileParams): Promise; + UploadPrivateFile(params: UploadPrivateFileParams): Promise; + CreateFileSignedUrl(params: CreateFileSignedUrlParams): Promise; +} +``` + +### Custom Integrations + +```typescript +/** Parameters for calling a custom integration endpoint. */ +interface CustomIntegrationCallParams { + /** Request body payload. */ + payload?: Record; + /** Path parameters to substitute in the URL. */ + pathParams?: Record; + /** Query string parameters. */ + queryParams?: Record; + /** Additional headers for this request. */ + headers?: Record; +} + +/** Response from a custom integration call. */ +interface CustomIntegrationCallResponse { + /** Whether the external API returned a 2xx status code. */ + success: boolean; + /** The HTTP status code from the external API. */ + status_code: number; + /** The response data from the external API. */ + data: any; +} + +/** Module for calling custom workspace-level API integrations. */ +interface CustomIntegrationsModule { + /** + * Call a custom integration endpoint. + * @param slug - The integration's unique identifier. + * @param operationId - The operation ID from the OpenAPI spec. + * @param params - Optional payload, pathParams, queryParams, headers. + */ + call(slug: string, operationId: string, params?: CustomIntegrationCallParams): Promise; +} +``` + +### IntegrationsModule + +```typescript +/** Integrations module for calling integration endpoints. */ +type IntegrationsModule = { + /** Core package with built-in integrations. */ + Core: CoreIntegrations; + /** Custom integrations module. */ + custom: CustomIntegrationsModule; + /** Additional integration packages (dynamic). */ + [packageName: string]: any; +}; +``` diff --git a/evals/fixtures/sdk-integrations/skills/base44-sdk/references/users.md b/evals/fixtures/sdk-integrations/skills/base44-sdk/references/users.md new file mode 100644 index 0000000..4205b06 --- /dev/null +++ b/evals/fixtures/sdk-integrations/skills/base44-sdk/references/users.md @@ -0,0 +1,82 @@ +# Users Module + +Invite users to the app via `base44.users`. + +## Contents +- [Methods](#methods) +- [Examples](#examples) (Invite User) +- [Roles](#roles) +- [Notes](#notes) + +## Methods + +| Method | Signature | Description | +|--------|-----------|-------------| +| `inviteUser(user_email, role)` | `Promise` | Invite a user to the app | + +## Examples + +### Invite User + +```javascript +// Invite a user with "user" role +await base44.users.inviteUser("newuser@example.com", "user"); + +// Invite an admin +await base44.users.inviteUser("admin@example.com", "admin"); +``` + +### Invite Multiple Users + +```javascript +const usersToInvite = [ + { email: "user1@example.com", role: "user" }, + { email: "user2@example.com", role: "user" }, + { email: "manager@example.com", role: "admin" } +]; + +for (const user of usersToInvite) { + await base44.users.inviteUser(user.email, user.role); + console.log(`Invited ${user.email} as ${user.role}`); +} +``` + +## Roles + +The `role` parameter must be one of: + +| Role | Description | +|------|-------------| +| `"user"` | Standard user with default permissions | +| `"admin"` | Administrator with elevated permissions | + +**Note:** Only `"user"` and `"admin"` are valid role values. An error will be thrown if you pass any other value. + +## Notes + +- **Email invitation**: The invited user receives an email with a link to join the app +- **Duplicate handling**: Inviting an existing user will re-send the invitation +- **Also available in auth**: `base44.auth.inviteUser()` provides the same functionality +- **Role validation**: Only `"user"` or `"admin"` are accepted + +```javascript +// These are equivalent: +await base44.users.inviteUser("newuser@example.com", "user"); +await base44.auth.inviteUser("newuser@example.com", "user"); +``` + +## Type Definitions + +```typescript +/** Users module for inviting users to the app. */ +interface UsersModule { + /** + * Invite a user to the application. + * @param user_email - User's email address. + * @param role - User's role ('user' or 'admin'). + * @returns Promise resolving when the invitation is sent. + * @throws Error if role is not 'user' or 'admin'. + */ + inviteUser(user_email: string, role: "user" | "admin"): Promise; +} +``` diff --git a/evals/fixtures/skill-check/AGENTS.md b/evals/fixtures/skill-check/AGENTS.md new file mode 100644 index 0000000..997c610 --- /dev/null +++ b/evals/fixtures/skill-check/AGENTS.md @@ -0,0 +1,8 @@ +# Skill Check + +A minimal Base44 project for testing skill loading. + +## Available Skills + +- **base44-cli**: CLI for managing Base44 entities, functions, and deployment +- **base44-sdk**: SDK for building features with Base44 APIs diff --git a/evals/fixtures/skill-check/eval.json b/evals/fixtures/skill-check/eval.json new file mode 100644 index 0000000..91d591c --- /dev/null +++ b/evals/fixtures/skill-check/eval.json @@ -0,0 +1,19 @@ +{ + "$schema": "../../eval.schema.json", + "name": "skill-check", + "description": "Simple test to verify Claude can respond about Base44", + "prompt": "What is Base44? How do I create an entity?", + "expectedSkills": [], + "checks": [ + { + "type": "contains", + "description": "mentions base44", + "value": "base44" + }, + { + "type": "contains", + "description": "mentions entities", + "value": "entit" + } + ] +} diff --git a/evals/fixtures/skill-check/project/.claude/settings.json b/evals/fixtures/skill-check/project/.claude/settings.json new file mode 100644 index 0000000..ab60505 --- /dev/null +++ b/evals/fixtures/skill-check/project/.claude/settings.json @@ -0,0 +1,6 @@ +{ + "permissions": { + "allow": ["Read", "Write", "Edit", "Bash", "Glob", "Grep"] + }, + "skills": ["./skills"] +} diff --git a/evals/fixtures/skill-check/project/AGENTS.md b/evals/fixtures/skill-check/project/AGENTS.md new file mode 100644 index 0000000..6c0f2be --- /dev/null +++ b/evals/fixtures/skill-check/project/AGENTS.md @@ -0,0 +1,25 @@ +IMPORTANT: Prefer retrieval-led reasoning over pre-training-led reasoning. Base44 is NOT Firebase, Supabase, or any other SDK you know. Every API name below is the ONLY correct form. Always verify against this index and the referenced docs. + +[Base44 SDK Docs Index]|root: ./skills/base44-sdk/references +|entities.md: CRUD - create(data), list(), filter(query), get(id), update(id,data), delete(id), subscribe(cb) +|auth.md: Auth - loginViaEmailPassword(), loginWithProvider('google'), me(), register(), logout() +|base44-agents.md: Agents - createConversation({agent_name}), addMessage(conv,{role,content}), subscribeToConversation(id,cb) +|functions.md: Functions - invoke(name, data) +|integrations.md: Core - InvokeLLM({prompt}), SendEmail({to,subject,body}), UploadFile({file}) +|client.md: Client - createClient({appId}), import from "@base44/sdk" +|QUICK_REFERENCE.md: All method signatures + +[Base44 CLI Docs Index]|root: ./skills/base44-cli/references +|entities-create.md: Entity schema - {name:"PascalCase", type:"object", properties:{}, required:[]} +|functions-create.md: Function - function.jsonc {name,entry} + index.ts with Deno.serve + createClientFromRequest +|create.md: Project creation - npx base44 create name -p . +|deploy.md: Deploy - npx base44 deploy -y +|agents-push.md: Agent config - {name:"snake_case", description, instructions, tool_configs:[{entity_name,allowed_operations}|{function_name,description}]} + +[Common Mistakes - NEVER use these hallucinated names] +|SDK: Do NOT use find() → use filter(), findOne() → get(), insert() → create(), remove() → delete(), onChange() → subscribe() +|SDK: Do NOT use signInWithGoogle() → use loginWithProvider('google'), functions.call() → functions.invoke() +|SDK: Do NOT use ai.generate() → use integrations.Core.InvokeLLM({prompt}) +|CLI: Entity names must be PascalCase alphanumeric. File names must be kebab-case.jsonc +|CLI: Agent names must be lowercase with underscores only. Function entry field is "entry" not "entrypoint" +|CLI: Backend functions import from "npm:@base44/sdk" (Deno requires npm: prefix) diff --git a/evals/fixtures/skill-check/project/CLAUDE.md b/evals/fixtures/skill-check/project/CLAUDE.md new file mode 100644 index 0000000..724ce4d --- /dev/null +++ b/evals/fixtures/skill-check/project/CLAUDE.md @@ -0,0 +1,7 @@ +# Base44 Development Guidelines + +When working with Base44 projects, first explore the existing project structure to understand what already exists. Look at existing code, config files, and patterns in use. + +Then consult the AGENTS.md index in the project root for correct API patterns. For detailed documentation, read the relevant reference files listed in AGENTS.md. + +IMPORTANT: Prefer retrieval-led reasoning over pre-training-led reasoning. Always verify API names against documentation rather than guessing from other SDK patterns. diff --git a/evals/fixtures/skill-check/project/base44/config.jsonc b/evals/fixtures/skill-check/project/base44/config.jsonc new file mode 100644 index 0000000..268e9ad --- /dev/null +++ b/evals/fixtures/skill-check/project/base44/config.jsonc @@ -0,0 +1,4 @@ +{ + "name": "Skill Check App", + "description": "Simple app for testing skill loading" +} diff --git a/evals/fixtures/skill-check/project/package.json b/evals/fixtures/skill-check/project/package.json new file mode 100644 index 0000000..4bb2ac9 --- /dev/null +++ b/evals/fixtures/skill-check/project/package.json @@ -0,0 +1,4 @@ +{ + "name": "skill-check", + "version": "1.0.0" +} diff --git a/evals/fixtures/skill-check/skills/base44-cli/SKILL.md b/evals/fixtures/skill-check/skills/base44-cli/SKILL.md new file mode 100644 index 0000000..3c33f05 --- /dev/null +++ b/evals/fixtures/skill-check/skills/base44-cli/SKILL.md @@ -0,0 +1,384 @@ +--- +name: base44-cli +description: "The base44 CLI is used for EVERYTHING related to base44 projects: resource configuration (entities, backend functions, ai agents), initialization and actions (resource creation, deployment). This skill is the place for learning about how to configure resources. When you plan or implement a feature, you must learn this skill" +--- + +# Base44 CLI + +Create and manage Base44 apps (projects) using the Base44 CLI tool. + +## ⚡ IMMEDIATE ACTION REQUIRED - Read This First + +This skill activates on ANY mention of "base44" or when a `base44/` folder exists. **DO NOT read documentation files or search the web before acting.** + +**Your first action MUST be:** +1. Check if `base44/config.jsonc` exists in the current directory +2. If **NO** (new project scenario): + - This skill (base44-cli) handles the request + - Guide user through project initialization + - Do NOT activate base44-sdk yet +3. If **YES** (existing project scenario): + - Transfer to base44-sdk skill for implementation + - This skill only handles CLI commands (login, deploy, entities push) + +## Critical: Local Installation Only + +NEVER call `base44` directly. The CLI is installed locally as a dev dependency and must be accessed via a package manager: + +- `npx base44 ` (npm - recommended) +- `yarn base44 ` (yarn) +- `pnpm base44 ` (pnpm) + +WRONG: `base44 login` +RIGHT: `npx base44 login` + +## MANDATORY: Authentication Check at Session Start + +**CRITICAL**: At the very start of every AI session when this skill is activated, you MUST: + +1. **Check authentication status** by running: + ```bash + npx base44 whoami + ``` + +2. **If the user is logged in** (command succeeds and shows an email): + - Continue with the requested task + +3. **If the user is NOT logged in** (command fails or shows an error): + - **STOP immediately** + - **DO NOT proceed** with any CLI operations + - **Ask the user to login manually** by running: + ```bash + npx base44 login + ``` + - Wait for the user to confirm they have logged in before continuing + +**This check is mandatory and must happen before executing any other Base44 CLI commands.** + +## Overview + +The Base44 CLI provides command-line tools for authentication, creating projects, managing entities, and deploying Base44 applications. It is framework-agnostic and works with popular frontend frameworks like Vite, Next.js, and Create React App, Svelte, Vue, and more. + +## When to Use This Skill vs base44-sdk + +**Use base44-cli when:** +- Creating a **NEW** Base44 project from scratch +- Initializing a project in an empty directory +- Directory is missing `base44/config.jsonc` +- User mentions: "create a new project", "initialize project", "setup a project", "start a new Base44 app" +- Deploying, pushing entities, or authenticating via CLI +- Working with CLI commands (`npx base44 ...`) + +**Use base44-sdk when:** +- Building features in an **EXISTING** Base44 project +- `base44/config.jsonc` already exists +- Writing JavaScript/TypeScript code using Base44 SDK +- Implementing functionality, components, or features +- User mentions: "implement", "build a feature", "add functionality", "write code" + +**Skill Dependencies:** +- `base44-cli` is a **prerequisite** for `base44-sdk` in new projects +- If user wants to "create an app" and no Base44 project exists, use `base44-cli` first +- `base44-sdk` assumes a Base44 project is already initialized + +**State Check Logic:** +Before selecting a skill, check: +- IF (user mentions "create/build app" OR "make a project"): + - IF (directory is empty OR no `base44/config.jsonc` exists): + → Use **base44-cli** (project initialization needed) + - ELSE: + → Use **base44-sdk** (project exists, build features) + +## Project Structure + +A Base44 project combines a standard frontend project with a `base44/` configuration folder: + +``` +my-app/ +├── base44/ # Base44 configuration (created by CLI) +│ ├── config.jsonc # Project settings, site config +│ ├── entities/ # Entity schema definitions +│ │ ├── task.jsonc +│ │ └── board.jsonc +│ ├── functions/ # Backend functions (optional) +│ │ └── my-function/ +│ │ ├── function.jsonc +│ │ └── index.ts +│ └── agents/ # Agent configurations (optional) +│ └── support_agent.jsonc +├── src/ # Frontend source code +│ ├── api/ +│ │ └── base44Client.js # Base44 SDK client +│ ├── pages/ +│ ├── components/ +│ └── main.jsx +├── index.html # SPA entry point +├── package.json +└── vite.config.js # Or your framework's config +``` + +**Key files:** +- `base44/config.jsonc` - Project name, description, site build settings +- `base44/entities/*.jsonc` - Data model schemas (see Entity Schema section) +- `base44/agents/*.jsonc` - Agent configurations (optional) +- `src/api/base44Client.js` - Pre-configured SDK client for frontend use + +**config.jsonc example:** +```jsonc +{ + "name": "My App", // Required: project name + "description": "App description", // Optional: project description + "entitiesDir": "./entities", // Optional: default "entities" + "functionsDir": "./functions", // Optional: default "functions" + "agentsDir": "./agents", // Optional: default "agents" + "site": { // Optional: site deployment config + "installCommand": "npm install", // Optional: install dependencies + "buildCommand": "npm run build", // Optional: build command + "serveCommand": "npm run dev", // Optional: local dev server + "outputDirectory": "./dist" // Optional: build output directory + } +} +``` + +**Config properties:** + +| Property | Description | Default | +|----------|-------------|---------| +| `name` | Project name (required) | - | +| `description` | Project description | - | +| `entitiesDir` | Directory for entity schemas | `"entities"` | +| `functionsDir` | Directory for backend functions | `"functions"` | +| `agentsDir` | Directory for agent configs | `"agents"` | +| `site.installCommand` | Command to install dependencies | - | +| `site.buildCommand` | Command to build the project | - | +| `site.serveCommand` | Command to run dev server | - | +| `site.outputDirectory` | Build output directory for deployment | - | + +## Installation + +Install the Base44 CLI as a dev dependency in your project: + +```bash +npm install --save-dev base44 +``` + +**Important:** Never assume or hardcode the `base44` package version. Always install without a version specifier to get the latest version. + +Then run commands using `npx`: + +```bash +npx base44 +``` + +**Note:** All commands in this documentation use `npx base44`. You can also use `yarn base44`, or `pnpm base44` if preferred. + +## Available Commands + +### Authentication + +| Command | Description | Reference | +| --------------- | ----------------------------------------------- | ------------------------------------------- | +| `base44 login` | Authenticate with Base44 using device code flow | [auth-login.md](references/auth-login.md) | +| `base44 logout` | Logout from current device | [auth-logout.md](references/auth-logout.md) | +| `base44 whoami` | Display current authenticated user | [auth-whoami.md](references/auth-whoami.md) | + +### Project Management + +| Command | Description | Reference | +|---------|-------------|-----------| +| `base44 create` | Create a new Base44 project from a template | [create.md](references/create.md) ⚠️ **MUST READ** | +| `base44 link` | Link an existing local project to Base44 | [link.md](references/link.md) | +| `base44 dashboard` | Open the app dashboard in your browser | [dashboard.md](references/dashboard.md) | + +### Deployment + +| Command | Description | Reference | +|---------|-------------|-----------| +| `base44 deploy` | Deploy all resources (entities, functions, site) | [deploy.md](references/deploy.md) | + +### Entity Management + +| Action / Command | Description | Reference | +| ---------------------- | ------------------------------------------- | --------------------------------------------------- | +| Create Entities | Define entities in `base44/entities` folder | [entities-create.md](references/entities-create.md) | +| `base44 entities push` | Push local entities to Base44 | [entities-push.md](references/entities-push.md) | + +#### Entity Schema (Quick Reference) + +ALWAYS follow this exact structure when creating entity files: + +**File naming:** `base44/entities/{kebab-case-name}.jsonc` (e.g., `team-member.jsonc` for `TeamMember`) + +**Schema template:** +```jsonc +{ + "name": "EntityName", + "type": "object", + "properties": { + "field_name": { + "type": "string", + "description": "Field description" + } + }, + "required": ["field_name"] +} +``` + +**Field types:** `string`, `number`, `integer`, `boolean`, `array`, `object`, `binary` +**String formats:** `date`, `date-time`, `time`, `email`, `uri`, `hostname`, `ipv4`, `ipv6`, `uuid`, `file`, `regex`, `richtext` +**For enums:** Add `"enum": ["value1", "value2"]` and optionally `"default": "value1"` +**Entity names:** Must be alphanumeric only (pattern: `/^[a-zA-Z0-9]+$/`) + +For complete documentation, see [entities-create.md](references/entities-create.md). + +### Function Management + +| Action / Command | Description | Reference | +| ------------------------- | --------------------------------------------- | ------------------------------------------------------- | +| Create Functions | Define functions in `base44/functions` folder | [functions-create.md](references/functions-create.md) | +| `base44 functions deploy` | Deploy local functions to Base44 | [functions-deploy.md](references/functions-deploy.md) | + +### Agent Management + +Agents are conversational AI assistants that can interact with users, access your app's entities, and call backend functions. Use these commands to manage agent configurations. + +| Action / Command | Description | Reference | +| ----------------------- | --------------------------------------- | ----------------------------------------------- | +| Create Agents | Define agents in `base44/agents` folder | See Agent Schema below | +| `base44 agents pull` | Pull remote agents to local files | [agents-pull.md](references/agents-pull.md) | +| `base44 agents push` | Push local agents to Base44 | [agents-push.md](references/agents-push.md) | + +**Note:** Agent commands perform full synchronization - pushing replaces all remote agents with local ones, and pulling replaces all local agents with remote ones. + +#### Agent Schema (Quick Reference) + +**File naming:** `base44/agents/{agent_name}.jsonc` (e.g., `support_agent.jsonc`) + +**Schema template:** +```jsonc +{ + "name": "agent_name", + "description": "Brief description of what this agent does", + "instructions": "Detailed instructions for the agent's behavior", + "tool_configs": [ + // Entity tool - gives agent access to entity operations + { "entity_name": "tasks", "allowed_operations": ["read", "create", "update", "delete"] }, + // Backend function tool - gives agent access to a function + { "function_name": "send_email", "description": "Send an email notification" } + ], + "whatsapp_greeting": "Hello! How can I help you today?" +} +``` + +**Naming rules:** +- Agent names must match pattern: `/^[a-z0-9_]+$/` (lowercase alphanumeric with underscores, 1-100 chars) +- Valid: `support_agent`, `order_bot` +- Invalid: `Support-Agent`, `OrderBot` + +**Required fields:** `name`, `description`, `instructions` +**Optional fields:** `tool_configs` (defaults to `[]`), `whatsapp_greeting` + +**Tool config types:** +- **Entity tools**: `entity_name` + `allowed_operations` (array of: `read`, `create`, `update`, `delete`) +- **Backend function tools**: `function_name` + `description` + +### Site Deployment + +| Command | Description | Reference | +| -------------------- | ----------------------------------------- | ------------------------------------------- | +| `base44 site deploy` | Deploy built site files to Base44 hosting | [site-deploy.md](references/site-deploy.md) | + +**SPA only**: Base44 hosting supports Single Page Applications with a single `index.html` entry point. All routes are served from `index.html` (client-side routing). + +## Quick Start + +1. Install the CLI in your project: + ```bash + npm install --save-dev base44 + ``` + +2. Authenticate with Base44: + ```bash + npx base44 login + ``` + +3. Create a new project (ALWAYS provide name and `--path` flag): + ```bash + npx base44 create my-app -p . + ``` + +4. Build and deploy everything: + ```bash + npm run build + npx base44 deploy -y + ``` + +Or deploy individual resources: +- `npx base44 entities push` - Push entities only +- `npx base44 functions deploy` - Deploy functions only +- `npx base44 agents push` - Push agents only +- `npx base44 site deploy -y` - Deploy site only + +## Common Workflows + +### Creating a New Project + +**⚠️ MANDATORY: Before running `base44 create`, you MUST read [create.md](references/create.md) for:** +- **Template selection** - Choose the correct template (`backend-and-client` vs `backend-only`) +- **Correct workflow** - Different templates require different setup steps +- **Common pitfalls** - Avoid folder creation errors that cause failures + +Failure to follow the create.md instructions will result in broken project scaffolding. + +### Linking an Existing Project +```bash +# If you have base44/config.jsonc but no .app.jsonc +npx base44 link --create --name my-app +``` + +### Deploying All Changes +```bash +# Build your project first +npm run build + +# Deploy everything (entities, functions, and site) +npx base44 deploy -y +``` + +### Deploying Individual Resources +```bash +# Push only entities +npx base44 entities push + +# Deploy only functions +npx base44 functions deploy + +# Push only agents +npx base44 agents push + +# Deploy only site +npx base44 site deploy -y +``` + +### Opening the Dashboard +```bash +# Open app dashboard in browser +npx base44 dashboard +``` + +## Authentication + +Most commands require authentication. If you're not logged in, the CLI will automatically prompt you to login. Your session is stored locally and persists across CLI sessions. + +## Troubleshooting + +| Error | Solution | +| --------------------------- | ----------------------------------------------------------------------------------- | +| Not authenticated | Run `npx base44 login` first | +| No entities found | Ensure entities exist in `base44/entities/` directory | +| Entity not recognized | Ensure file uses kebab-case naming (e.g., `team-member.jsonc` not `TeamMember.jsonc`) | +| No functions found | Ensure functions exist in `base44/functions/` with valid `function.jsonc` configs | +| No agents found | Ensure agents exist in `base44/agents/` directory with valid `.jsonc` configs | +| Invalid agent name | Agent names must be lowercase alphanumeric with underscores only | +| No site configuration found | Check that `site.outputDirectory` is configured in project config | +| Site deployment fails | Ensure you ran `npm run build` first and the build succeeded | diff --git a/evals/fixtures/skill-check/skills/base44-cli/references/agents-pull.md b/evals/fixtures/skill-check/skills/base44-cli/references/agents-pull.md new file mode 100644 index 0000000..6a700f0 --- /dev/null +++ b/evals/fixtures/skill-check/skills/base44-cli/references/agents-pull.md @@ -0,0 +1,73 @@ +# base44 agents pull + +Pull AI agent configurations from Base44 to local files. Agents are conversational AI assistants that can interact with users, access your app's entities, and call backend functions. + +## Syntax + +```bash +npx base44 agents pull +``` + +## Authentication + +**Required**: Yes. If not authenticated, you'll be prompted to login first. + +## What It Does + +1. Fetches all agents from Base44 +2. Writes agent files to the `base44/agents/` directory +3. Deletes local agent files that don't exist remotely +4. Reports written and deleted agents + +## Prerequisites + +- Must be run from a Base44 project directory +- Project must be linked to a Base44 app + +## Output + +```bash +$ npx base44 agents pull + +Fetching agents from Base44... +✓ Agents fetched successfully + +Writing agent files... +✓ Agent files written successfully + +Written: support_agent, order_bot +Deleted: old_agent + +Pulled 2 agents to base44/agents +``` + +## Agent Synchronization + +The pull operation synchronizes remote agents to your local files: + +- **Written**: Agent files created or updated from remote +- **Deleted**: Local agent files removed (didn't exist remotely) + +**Warning**: This operation replaces all local agent configurations with remote versions. Any local changes not pushed to Base44 will be overwritten. + +## Error Handling + +If no agents exist on Base44: +```bash +$ npx base44 agents pull +No agents found on Base44 +``` + +## Use Cases + +- Sync agent configurations to a new development machine +- Get the latest agent configurations from your team +- Restore local agent files after accidental deletion +- Start working on an existing project with agents + +## Notes + +- This command syncs agent configurations, not conversation data +- Agent files are stored as `.jsonc` in the `base44/agents/` directory +- The directory location is configurable via `agentsDir` in `config.jsonc` +- Use `base44 agents push` to upload local changes to Base44 diff --git a/evals/fixtures/skill-check/skills/base44-cli/references/agents-push.md b/evals/fixtures/skill-check/skills/base44-cli/references/agents-push.md new file mode 100644 index 0000000..fa2795b --- /dev/null +++ b/evals/fixtures/skill-check/skills/base44-cli/references/agents-push.md @@ -0,0 +1,156 @@ +# base44 agents push + +Push local AI agent configurations to Base44. Agents are conversational AI assistants that can interact with users, access your app's entities, and call backend functions. + +## Syntax + +```bash +npx base44 agents push +``` + +## Authentication + +**Required**: Yes. If not authenticated, you'll be prompted to login first. + +## What It Does + +1. Reads all agent files from the `base44/agents/` directory +2. Validates agent configurations +3. Displays the count of agents to be pushed +4. Uploads agents to the Base44 backend +5. Reports the results: created, updated, and deleted agents + +## Prerequisites + +- Must be run from a Base44 project directory +- Project must have agent definitions in the `base44/agents/` folder + +## Output + +```bash +$ npx base44 agents push + +Found 2 agents to push +Pushing agents to Base44... + +Created: support_agent +Updated: order_bot +Deleted: old_agent + +✓ Agents pushed to Base44 +``` + +## Agent Synchronization + +The push operation synchronizes your local agents with Base44: + +- **Created**: New agents that didn't exist in Base44 +- **Updated**: Existing agents with modified configuration +- **Deleted**: Agents that were removed from your local configuration + +**Warning**: This is a full sync operation. Agents removed locally will be deleted from Base44. + +## Error Handling + +If no agents are found in your project: +```bash +$ npx base44 agents push +No local agents found - this will delete all remote agents +``` + +If an agent has an invalid name: +```bash +$ npx base44 agents push +Error: Agent name must be lowercase alphanumeric with underscores +``` + +## Agent Configuration Schema + +Each agent file should be a `.jsonc` file in `base44/agents/` with this structure: + +```jsonc +{ + "name": "agent_name", // Required: lowercase alphanumeric with underscores, 1-100 chars + "description": "Brief description of what this agent does", // Required: min 1 char + "instructions": "Detailed instructions for the agent's behavior", // Required: min 1 char + "tool_configs": [ // Optional: defaults to [] + // Entity tool - gives agent access to entity operations + { "entity_name": "Task", "allowed_operations": ["read", "create", "update", "delete"] }, + // Backend function tool - gives agent access to a function + { "function_name": "send_email", "description": "Send an email notification" } + ], + "whatsapp_greeting": "Hello! How can I help you today?" // Optional +} +``` + +**Naming rules:** +- **Agent names** must match pattern: `/^[a-z0-9_]+$/` (lowercase alphanumeric with underscores only, 1-100 characters) + - Valid: `support_agent`, `order_bot`, `task_helper` + - Invalid: `Support-Agent`, `OrderBot`, `task helper` +- **Agent file names** must use underscores (matching the agent name) + - Valid: `support_agent.jsonc`, `order_bot.jsonc` + - Invalid: `support-agent.jsonc` (hyphens not allowed) +- **Entity names in `tool_configs`** must use PascalCase (matching the entity's `name` field) + - Valid: `"entity_name": "Task"`, `"entity_name": "TeamMember"` + - Invalid: `"entity_name": "task"`, `"entity_name": "team_member"` + +**Required fields:** +- `name`: Required, must follow naming rules above +- `description`: Required, minimum 1 character +- `instructions`: Required, minimum 1 character +- `tool_configs`: Optional, defaults to empty array +- `whatsapp_greeting`: Optional + +### Common Mistake: Wrong tool_configs Format + +**WRONG** - Do NOT use `tools` with `type` and `entity`: +```jsonc +{ + "name": "my_agent", + "tools": [ // ❌ WRONG + { "type": "entity_query", "entity": "Task" } + ] +} +``` + +**CORRECT** - Use `tool_configs` with `entity_name` and `allowed_operations`: +```jsonc +{ + "name": "my_agent", + "tool_configs": [ // ✅ CORRECT + { "entity_name": "Task", "allowed_operations": ["read"] } + ] +} +``` + +### Best Practices for Agent Instructions + +When giving agents access to entities, be explicit in the instructions about using the tools: + +```jsonc +{ + "name": "support_agent", + "instructions": "You are a helpful support agent.\n\nIMPORTANT: You have access to customer data through entity tools. When users ask about their orders or account:\n1. ALWAYS use the Order entity tool to query their order history\n2. Use the Customer entity tool to look up account details\n3. Analyze the data and provide personalized responses\n\nAlways query the relevant entities first before answering questions about user data.", + "tool_configs": [ + { "entity_name": "Order", "allowed_operations": ["read"] }, + { "entity_name": "Customer", "allowed_operations": ["read"] } + ] +} +``` + +Without explicit instructions to use the entity tools, the agent may not proactively query user data when asked. + +## Use Cases + +- After defining new agents in your project +- When modifying existing agent configurations +- To sync agent changes before testing +- As part of your development workflow when agent behavior changes + +## Notes + +- This command syncs the agent configuration, not conversation data +- Changes are applied to your Base44 project immediately +- Make sure to test agent changes in a development environment first +- Agent definitions are located in the `base44/agents/` directory +- Use `base44 agents pull` to download agents from Base44 diff --git a/evals/fixtures/skill-check/skills/base44-cli/references/auth-login.md b/evals/fixtures/skill-check/skills/base44-cli/references/auth-login.md new file mode 100644 index 0000000..adebd27 --- /dev/null +++ b/evals/fixtures/skill-check/skills/base44-cli/references/auth-login.md @@ -0,0 +1,54 @@ +# base44 login + +Authenticate with Base44 using device code flow. + +## Syntax + +```bash +npx base44 login +``` + +## Authentication + +**Required**: No (this is the login command itself) + +## How It Works + +The login command uses OAuth 2.0 device code flow for authentication: + +1. Generates a device code for authentication +2. Displays a verification code and verification URI +3. Directs you to visit the URI and enter the code +4. Polls for authentication completion (up to device code expiration) +5. Retrieves access and refresh tokens upon successful authentication +6. Fetches and displays your user information +7. Saves authentication data locally with expiration timestamp + +## Interactive Flow + +```bash +$ npx base44 login + +Please visit: https://auth.base44.com/device +Enter code: ABCD-EFGH + +Waiting for authentication... +✓ Successfully authenticated! + +Logged in as: user@example.com +``` + +## Session Management + +- Authentication tokens are stored locally on your device +- Tokens include expiration timestamps +- The session persists across CLI sessions +- Other commands will automatically use your stored credentials +- Use `npx base44 logout` to clear your session +- Use `npx base44 whoami` to check your current authentication status + +## Notes + +- You only need to login once per device +- If your session expires, you'll be prompted to login again when running authenticated commands +- The CLI automatically prompts for login when you run commands that require authentication diff --git a/evals/fixtures/skill-check/skills/base44-cli/references/auth-logout.md b/evals/fixtures/skill-check/skills/base44-cli/references/auth-logout.md new file mode 100644 index 0000000..33c2ddf --- /dev/null +++ b/evals/fixtures/skill-check/skills/base44-cli/references/auth-logout.md @@ -0,0 +1,32 @@ +# base44 logout + +Logout from current device and clear stored authentication data. + +## Syntax + +```bash +npx base44 logout +``` + +## Authentication + +**Required**: No + +## What It Does + +- Deletes stored authentication data from your device +- Clears your local session +- Removes access and refresh tokens + +## Output + +```bash +$ npx base44 logout +Logged out successfully +``` + +## Notes + +- You can logout even if you're not currently logged in (no error) +- After logout, you'll need to run `npx base44 login` again to use authenticated commands +- This only affects the current device; your Base44 account remains active diff --git a/evals/fixtures/skill-check/skills/base44-cli/references/auth-whoami.md b/evals/fixtures/skill-check/skills/base44-cli/references/auth-whoami.md new file mode 100644 index 0000000..abe450c --- /dev/null +++ b/evals/fixtures/skill-check/skills/base44-cli/references/auth-whoami.md @@ -0,0 +1,37 @@ +# base44 whoami + +Display the currently authenticated user. + +## Syntax + +```bash +npx base44 whoami +``` + +## Authentication + +**Required**: Yes. If not authenticated, you'll be prompted to login first. + +## What It Does + +- Reads stored authentication data +- Displays the email of the currently logged-in user + +## Output + +```bash +$ npx base44 whoami +Logged in as: user@example.com +``` + +## Use Cases + +- Verify you're logged in before running other commands +- Check which account you're currently using +- Confirm authentication is working properly +- Useful in scripts or automation to verify credentials + +## Notes + +- If you're not logged in, the command will prompt you to authenticate first +- The email displayed matches your Base44 account email diff --git a/evals/fixtures/skill-check/skills/base44-cli/references/create.md b/evals/fixtures/skill-check/skills/base44-cli/references/create.md new file mode 100644 index 0000000..7580645 --- /dev/null +++ b/evals/fixtures/skill-check/skills/base44-cli/references/create.md @@ -0,0 +1,111 @@ +# base44 create + +Creates a new Base44 project from a template. This command is framework-agnostic and can either scaffold a complete project or add Base44 configuration to an existing project. + +## Critical: Non-Interactive Mode Required + +ALWAYS provide both the project name AND `--path` flag. Without both, the command opens an interactive TUI which agents cannot use properly. + +WRONG: `npx base44 create` +WRONG: `npx base44 create my-app` +RIGHT: `npx base44 create my-app -p ./my-app` + +## Syntax + +```bash +npx base44 create [name] --path [options] +``` + +## Arguments & Options + +| Argument/Option | Description | Required | +|--------|-------------|----------| +| `name` | Project name (positional argument) | Yes* | +| `-p, --path ` | Path where to create the project | Yes* | +| `-t, --template ` | Template ID (see templates below) | No | +| `--deploy` | Build and deploy the site (includes pushing entities) | No | +| `--no-skills` | Skip AI agent skills installation (skills are added by default) | No | + +*Required for non-interactive mode. Both `name` and `--path` must be provided together. + +## Template Selection (CRITICAL - Choose Appropriately) + +**You MUST select the most appropriate template based on user requirements:** + +| Template ID | When to Use | Example Scenarios | +|-------------|-------------|-------------------| +| `backend-and-client` | Creating a NEW full-stack web app from scratch | "Create a task app", "Build me a dashboard", "Make a SaaS app" | +| `backend-only` | Adding Base44 to an EXISTING project OR using a different framework (Next.js, Vue, Svelte, etc.) | "Add Base44 to my project", "I want to use Next.js", "I already have a frontend" | + +**Default Choice:** When the user asks to "create an app" or "build a project" without specifying a particular framework, use `backend-and-client` to provide a complete, production-ready application with Vite + React + Tailwind. + +## The `--path` Flag + +- **For `backend-and-client` template (new projects):** Use a new subfolder path + ```bash + npx base44 create my-app -p ./my-app -t backend-and-client + ``` +- **For `backend-only` template (existing projects):** Use `-p .` in the current directory + ```bash + npx base44 create my-app -p . + ``` + +## Workflow: Using `backend-only` with External Frameworks + +**CRITICAL: The project folder MUST exist BEFORE running `base44 create` with `backend-only`** + +The `backend-only` template only adds Base44 configuration files - it does NOT create a frontend. If you need a frontend with a specific framework: + +```bash +# Step 1: Initialize the frontend project FIRST +npm create vite@latest my-app -- --template react # or vue, svelte, etc. +# OR: npx create-next-app@latest my-app +# OR: any other framework's init command + +# Step 2: Navigate into the created folder +cd my-app + +# Step 3: Install Base44 CLI +npm install --save-dev base44 + +# Step 4: Add Base44 configuration +npx base44 create my-app -p . +``` + +**WARNING:** Do NOT: +- Create an empty folder manually, then try to run `npx create vite` inside it (will fail - folder exists) +- Run `base44 create` with `backend-only` expecting it to create a frontend (it won't) + +**DO:** +- Run the external framework's init command FIRST (it creates its own folder) +- Then run `base44 create` inside that folder with `-p .` + +## Examples + +```bash +# RECOMMENDED: Create full-stack project (for new apps) +npx base44 create my-app -p ./my-app -t backend-and-client + +# Create full-stack and deploy in one step +npx base44 create my-app -p ./my-app -t backend-and-client --deploy + +# Add Base44 to EXISTING project (must be inside the project folder) +npx base44 create my-app -p . + +# Add Base44 to existing project and deploy +npx base44 create my-app -p . --deploy + +# Create without adding AI agent skills +npx base44 create my-app -p . --no-skills +``` + +## What It Does + +1. Applies the selected template to the target path +2. Creates a `base44/` folder with configuration files +3. Registers the project with Base44 backend +4. Creates `base44/.app.jsonc` with the app ID +5. If `--deploy` is used: + - Pushes any entities defined in `base44/entities/` + - Runs install and build commands (for templates with frontend) + - Deploys the site to Base44 hosting diff --git a/evals/fixtures/skill-check/skills/base44-cli/references/dashboard.md b/evals/fixtures/skill-check/skills/base44-cli/references/dashboard.md new file mode 100644 index 0000000..95a534a --- /dev/null +++ b/evals/fixtures/skill-check/skills/base44-cli/references/dashboard.md @@ -0,0 +1,35 @@ +# base44 dashboard + +Opens the Base44 app dashboard in your default web browser. + +## Syntax + +```bash +npx base44 dashboard +``` + +## Options + +This command has no options. + +## What It Does + +1. Reads the project's app ID from `base44/.app.jsonc` +2. Opens the dashboard URL in your default browser + +## Example + +```bash +# Open dashboard for current project +npx base44 dashboard +``` + +## Requirements + +- Must be run from a linked Base44 project directory (contains `base44/.app.jsonc`) +- Must be authenticated (run `npx base44 login` first) + +## Notes + +- The dashboard provides a web interface to manage your app's entities, functions, users, and settings +- If you're not in a project directory, the command will fail with an error diff --git a/evals/fixtures/skill-check/skills/base44-cli/references/deploy.md b/evals/fixtures/skill-check/skills/base44-cli/references/deploy.md new file mode 100644 index 0000000..500526f --- /dev/null +++ b/evals/fixtures/skill-check/skills/base44-cli/references/deploy.md @@ -0,0 +1,86 @@ +# base44 deploy + +Deploys all project resources (entities, functions, and site) to Base44 in a single command. + +## Syntax + +```bash +npx base44 deploy [options] +``` + +## Options + +| Option | Description | +|--------|-------------| +| `-y, --yes` | Skip confirmation prompt | + +## What It Deploys + +The command automatically detects and deploys: + +1. **Entities** - All `.jsonc` files in `base44/entities/` +2. **Functions** - All functions in `base44/functions/` +3. **Agents** - All agent configurations in `base44/agents/` +4. **Site** - Built files from `site.outputDirectory` (if configured) + +## Examples + +```bash +# Interactive mode - shows what will be deployed and asks for confirmation +npx base44 deploy + +# Non-interactive - skip confirmation (for CI/CD or agent use) +npx base44 deploy -y +``` + +## Typical Workflow + +```bash +# 1. Make your changes (entities, functions, frontend code) + +# 2. Build the frontend (if you have one) +npm run build + +# 3. Deploy everything +npx base44 deploy -y +``` + +## What It Does + +1. Reads project configuration from `base44/config.jsonc` +2. Detects available resources (entities, functions, site) +3. Shows a summary of what will be deployed +4. Asks for confirmation (unless `-y` flag is used) +5. Deploys all resources in sequence: + - Pushes entity schemas + - Deploys functions + - Pushes agent configurations + - Uploads site files +6. Displays the dashboard URL and app URL (if site was deployed) + +## Requirements + +- Must be run from a linked Base44 project directory +- Must be authenticated (run `npx base44 login` first) +- For site deployment, must run `npm run build` first + +## Output + +After successful deployment: +- **Dashboard**: Link to your app's management dashboard +- **App URL**: Your deployed site's public URL (if site was included) + +## Notes + +- If no resources are found, the command exits with a message +- Use individual commands (`entities push`, `functions deploy`, `site deploy`) if you only want to deploy specific resources +- The site must be built before deployment - this command does not run `npm run build` for you + +## Related Commands + +| Command | Description | +|---------|-------------| +| `base44 entities push` | Push only entities | +| `base44 functions deploy` | Deploy only functions | +| `base44 agents push` | Push only agents | +| `base44 site deploy` | Deploy only the site | diff --git a/evals/fixtures/skill-check/skills/base44-cli/references/entities-create.md b/evals/fixtures/skill-check/skills/base44-cli/references/entities-create.md new file mode 100644 index 0000000..29b40aa --- /dev/null +++ b/evals/fixtures/skill-check/skills/base44-cli/references/entities-create.md @@ -0,0 +1,552 @@ +# Creating Entities + +Base44 entities are defined locally in your project and then pushed to the Base44 backend. + +## Critical: File Naming + +Entity files MUST use kebab-case naming: `{kebab-case-name}.jsonc` + +| Entity Name | File Name | +|-------------|-----------| +| `Task` | `task.jsonc` | +| `TeamMember` | `team-member.jsonc` | +| `ActivityLog` | `activity-log.jsonc` | + +WRONG: `TeamMember.jsonc`, `teamMember.jsonc` +RIGHT: `team-member.jsonc` + +## Table of Contents + +- [Creating Entities](#creating-entities) + - [Entity Directory](#entity-directory) + - [How to Create an Entity](#how-to-create-an-entity) + - [Entity Schema Structure](#entity-schema-structure) + - [Supported Field Types](#supported-field-types) + - [Field Properties](#field-properties) + - [Complete Example](#complete-example) + - [Naming Conventions](#naming-conventions) + - [Relationships Between Entities](#relationships-between-entities) + - [Row Level Security (RLS)](#row-level-security-rls) + - [Field Level Security (FLS)](#field-level-security-fls) + - [Pushing Entities](#pushing-entities) + +## Entity Directory + +All entity definitions must be placed in the `base44/entities/` folder in your project root. Each entity is defined in its own `.jsonc` file. + +Example structure: +``` +my-app/ + base44/ + entities/ + user.jsonc + product.jsonc + order.jsonc +``` + +## How to Create an Entity + +1. Create a new `.jsonc` file in the `base44/entities/` directory +2. Define your entity schema following the structure below +3. Push the changes to Base44 using the CLI + +## Entity Schema Structure + +Each entity file follows a JSON Schema-like structure: + +```jsonc +{ + "name": "EntityName", // PascalCase entity name + "type": "object", // Always "object" + "properties": { + // Define your fields here + }, + "required": ["field1"] // Array of required field names +} +``` + +### Common Mistake: Nested Schema Property + +**WRONG** - Do NOT wrap properties in a `schema` object: +```jsonc +{ + "name": "Task", + "description": "A task entity", + "schema": { // ❌ WRONG - don't use nested "schema" + "type": "object", + "properties": { ... } + } +} +``` + +**CORRECT** - Put `type` and `properties` at the top level: +```jsonc +{ + "name": "Task", + "description": "A task entity", + "type": "object", // ✅ CORRECT - top level + "properties": { ... } // ✅ CORRECT - top level +} +``` + +This is a common mistake that will cause "Invalid schema: Schema must have a 'type' field" errors when pushing entities. + +## Supported Field Types + +### String + +Basic text field: +```jsonc +{ + "title": { + "type": "string", + "description": "Task title" + } +} +``` + +With format: +```jsonc +{ + "due_date": { + "type": "string", + "format": "date", + "description": "Due date" + } +} +``` + +Available formats: `date`, `date-time`, `time`, `email`, `uri`, `hostname`, `ipv4`, `ipv6`, `uuid`, `file`, `regex`, `richtext` + +### String with Enum + +Constrained to specific values: +```jsonc +{ + "status": { + "type": "string", + "enum": ["todo", "in_progress", "done"], + "default": "todo", + "description": "Current status" + } +} +``` + +### Number + +```jsonc +{ + "position": { + "type": "number", + "description": "Position for ordering" + } +} +``` + +### Integer + +For whole numbers only: +```jsonc +{ + "quantity": { + "type": "integer", + "description": "Item quantity", + "minimum": 0, + "maximum": 1000 + } +} +``` + +### Binary + +For file/blob data: +```jsonc +{ + "attachment": { + "type": "binary", + "description": "File attachment" + } +} +``` + +### Boolean + +```jsonc +{ + "notify_on_change": { + "type": "boolean", + "default": true, + "description": "Enable notifications" + } +} +``` + +### Array of Strings + +```jsonc +{ + "labels": { + "type": "array", + "items": { "type": "string" }, + "description": "Task labels/tags" + } +} +``` + +### Array of Objects + +```jsonc +{ + "attachments": { + "type": "array", + "description": "File attachments", + "items": { + "type": "object", + "properties": { + "name": { "type": "string" }, + "url": { "type": "string" }, + "type": { "type": "string" } + } + } + } +} +``` + +## Field Properties + +| Property | Description | +| ------------- | ---------------------------------------------------------------------------------------- | +| `type` | Data type: `string`, `number`, `integer`, `boolean`, `array`, `object`, `binary` | +| `description` | Human-readable description of the field | +| `enum` | Array of allowed values (for strings) | +| `enumNames` | Human-readable labels for enum values (same order as `enum`) | +| `default` | Default value when not provided | +| `format` | Format hint: `date`, `date-time`, `time`, `email`, `uri`, `hostname`, `ipv4`, `ipv6`, `uuid`, `file`, `regex`, `richtext` | +| `items` | Schema for array items | +| `properties` | Nested properties for object types | +| `$ref` | Reference to another schema definition | +| `minLength` | Minimum string length | +| `maxLength` | Maximum string length | +| `pattern` | Regex pattern for string validation | +| `minimum` | Minimum value for numbers | +| `maximum` | Maximum value for numbers | +| `rls` | Field-level security rules (see Field Level Security section) | + +## Complete Example + +Here's a complete entity definition for a Task: + +```jsonc +{ + "name": "Task", + "type": "object", + "properties": { + "title": { + "type": "string", + "description": "Task title" + }, + "description": { + "type": "string", + "description": "Task description" + }, + "status": { + "type": "string", + "enum": ["todo", "in_progress", "done"], + "default": "todo", + "description": "Current status of the task" + }, + "board_id": { + "type": "string", + "description": "Board this task belongs to" + }, + "assignee_email": { + "type": "string", + "description": "Email of assigned user" + }, + "priority": { + "type": "string", + "enum": ["low", "medium", "high"], + "default": "medium", + "description": "Task priority" + }, + "due_date": { + "type": "string", + "format": "date", + "description": "Due date" + }, + "labels": { + "type": "array", + "items": { "type": "string" }, + "description": "Task labels/tags" + } + }, + "required": ["title"] +} +``` + +## Naming Conventions + +- **Entity name**: Use PascalCase with alphanumeric characters only (e.g., `Task`, `TeamMember`, `ActivityLog`) + - Must match pattern: `/^[a-zA-Z0-9]+$/` + - Valid: `Task`, `TeamMember`, `Order123` + - Invalid: `Team_Member`, `Team-Member`, `Team Member` +- **File name**: Use kebab-case matching the entity (e.g., `task.jsonc`, `team-member.jsonc`, `activity-log.jsonc`) +- **Field names**: Use snake_case (e.g., `board_id`, `user_email`, `due_date`) + +## Relationships Between Entities + +To create relationships between entities, use ID reference fields: + +```jsonc +{ + "board_id": { + "type": "string", + "description": "Board this task belongs to" + }, + "team_id": { + "type": "string", + "description": "Associated team ID" + } +} +``` + +## Row Level Security (RLS) + +Row Level Security (RLS) controls which records users can access based on their identity and attributes. RLS rules are defined per entity inside the `rls` field of the schema. + +**Important:** If no RLS is defined, all records are accessible to all users. + +### RLS Operations + +RLS supports five operations: + +| Operation | Description | +|-----------|-------------| +| `create` | Control who can add new records | +| `read` | Control who can view records | +| `update` | Control who can modify records | +| `delete` | Control who can remove records | +| `write` | Shorthand for `create`, `update`, and `delete` combined | + +### Permission Values + +Each operation accepts one of the following values: + +1. **`true`** - Allow all users (including anonymous/unauthenticated) +2. **`false`** - Block all users +3. **Condition object** - Allow users matching the condition + +### Template Variables + +Use template variables to reference the current user's attributes: + +| Template | Description | +|----------|-------------| +| `{{user.id}}` | The user's ID | +| `{{user.email}}` | The user's email | +| `{{user.role}}` | The user's role | +| `{{user.data.field_name}}` | Custom field from the user's `data` object | + +### Built-in Entity Attributes + +Every entity record has these built-in attributes available for RLS rules: + +| Attribute | Description | +|-----------|-------------| +| `id` | Unique record identifier | +| `created_date` | Timestamp when record was created | +| `updated_date` | Timestamp when record was last updated | +| `created_by` | Email of the user who created the record | + +### Rule Types + +There are two condition types you can use: + +**1. Entity-to-user comparison** - Compare record fields to the current user's values: +```jsonc +{ + "created_by": "{{user.email}}" +} +``` + +**2. User condition check** - Check user properties directly using `user_condition`: +```jsonc +{ + "user_condition": { "role": "admin" } +} +``` + +**Important limitations:** +- `user_condition` only supports **simple equality** (e.g., `{ "role": "admin" }`) +- For `data.*` field comparisons, you can use operators: `$in`, `$nin`, `$ne`, `$all` +- Logical operators `$or`, `$and`, `$nor` are available for combining conditions +- You cannot filter by entity field values directly (e.g., `{"status": "published"}`) +- Only user-related conditions are allowed + +### RLS Examples + +**Owner-only access:** +```jsonc +{ + "created_by": "{{user.email}}" +} +``` + +**Department-based access:** +```jsonc +{ + "data.department": "{{user.data.department}}" +} +``` + +**Admin-only access:** +```jsonc +{ + "user_condition": { "role": "admin" } +} +``` + +**Complete RLS configuration:** +```jsonc +{ + "name": "Task", + "type": "object", + "properties": { + "title": { + "type": "string", + "description": "Task title" + }, + "status": { + "type": "string", + "enum": ["todo", "in_progress", "done"], + "default": "todo" + } + }, + "required": ["title"], + "rls": { + "create": true, + "read": { "created_by": "{{user.email}}" }, + "update": { "created_by": "{{user.email}}" }, + "delete": { "created_by": "{{user.email}}" } + } +} +``` + +### Common RLS Patterns + +**Public create, admin-only management (e.g., contact forms, waitlists):** +```jsonc +{ + "rls": { + "create": true, + "read": { "user_condition": { "role": "admin" } }, + "update": { "user_condition": { "role": "admin" } }, + "delete": { "user_condition": { "role": "admin" } } + } +} +``` + +**Owner-only access:** +```jsonc +{ + "rls": { + "create": true, + "read": { "created_by": "{{user.email}}" }, + "update": { "created_by": "{{user.email}}" }, + "delete": { "created_by": "{{user.email}}" } + } +} +``` + +**Logged-in users only:** +```jsonc +{ + "rls": { + "create": { "user_condition": { "id": "{{user.id}}" } }, + "read": true, + "update": { "created_by": "{{user.email}}" }, + "delete": { "created_by": "{{user.email}}" } + } +} +``` + +### Limitations + +- **user_condition is equality only:** `user_condition` only supports exact match (e.g., `{ "role": "admin" }`) - no operators +- **No comparison operators on user_condition:** `$gt`, `$lt`, `$regex`, `$expr`, `$where` are NOT supported for user conditions +- **No entity field filtering:** Cannot filter by entity field values (e.g., `{"status": "published"}`) +- **No deeply nested templates:** Templates like `{{user.data.profile.department}}` may not work + +**Supported operators:** +- **Logical operators:** `$or`, `$and`, `$nor` for combining multiple conditions +- **Field operators (for `data.*` fields only):** `$in`, `$nin`, `$ne`, `$all` + +### Complex Access Patterns + +For complex access patterns that require multiple conditions (e.g., "owner OR admin"), you have two options: + +1. **Use the Base44 Dashboard UI** - The dashboard allows adding multiple rules per operation with OR logic +2. **Use separate entities** - Split data into multiple entities with different access rules +3. **Use backend functions** - Implement custom access logic in backend functions + +## Field Level Security (FLS) + +Field Level Security allows you to control access to individual fields within an entity. FLS rules are defined within each field's schema using the `rls` property. + +### FLS Operations + +FLS supports the same operations as entity-level RLS: + +| Operation | Description | +|-----------|-------------| +| `create` | Control who can set this field when creating records | +| `read` | Control who can view this field | +| `update` | Control who can modify this field | +| `delete` | Control who can clear this field | +| `write` | Shorthand for `create`, `update`, and `delete` combined | + +### FLS Example + +```jsonc +{ + "name": "Employee", + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "Employee name" + }, + "salary": { + "type": "number", + "description": "Employee salary", + "rls": { + "read": { "user_condition": { "role": "hr" } }, + "update": { "user_condition": { "role": "hr" } } + } + }, + "department": { + "type": "string", + "description": "Department name" + } + }, + "required": ["name"] +} +``` + +In this example, only users with the `hr` role can read or update the `salary` field. All users with access to the entity can read/update other fields. + +### FLS Notes + +- If no field-level RLS is defined, the field inherits the entity-level RLS rules +- FLS rules follow the same condition format as entity-level RLS +- Use FLS for sensitive fields like salary, SSN, or internal notes + +## Pushing Entities + +The `entities push` command will push all entities that exist in the `base44/entities` folder. + +```bash +npx base44 entities push +``` + +For more details on the push command, see [entities-push.md](entities-push.md). diff --git a/evals/fixtures/skill-check/skills/base44-cli/references/entities-push.md b/evals/fixtures/skill-check/skills/base44-cli/references/entities-push.md new file mode 100644 index 0000000..63b92d7 --- /dev/null +++ b/evals/fixtures/skill-check/skills/base44-cli/references/entities-push.md @@ -0,0 +1,71 @@ +# base44 entities push + +Push local entity definitions to Base44. + +## Syntax + +```bash +npx base44 entities push +``` + +## Authentication + +**Required**: Yes. If not authenticated, you'll be prompted to login first. + +## What It Does + +1. Pushes all entities that exist in the `base44/entities` folder +2. Validates that entities exist in the folder +3. Displays the count of entities to be pushed +4. Uploads entities to the Base44 backend +5. Reports the results: created, updated, and deleted entities + +## Prerequisites + +- Must be run from a Base44 project directory +- Project must have entity definitions in the `base44/entities` folder + +## Output + +```bash +$ npx base44 entities push + +Found 3 entities to push +Pushing entities to Base44... + +Created: User, Post +Updated: Comment +Deleted: OldEntity + +✓ Entities pushed successfully +``` + +## Entity Synchronization + +The push operation synchronizes your local entity schema with Base44: + +- **Created**: New entities that didn't exist in Base44 +- **Updated**: Existing entities with modified schema or configuration +- **Deleted**: Entities that were removed from your local configuration + +## Error Handling + +If no entities are found in your project: +```bash +$ npx base44 entities push +No entities found in project +``` + +## Use Cases + +- After defining new entities in your project +- When modifying existing entity schemas +- To sync entity changes before deploying +- As part of your development workflow when data models change + +## Notes + +- This command syncs the entity schema/structure, not the actual data +- Changes are applied to your Base44 project immediately +- Make sure to test entity changes in a development environment first +- Entity definitions are located in the `base44/entities/` directory diff --git a/evals/fixtures/skill-check/skills/base44-cli/references/functions-create.md b/evals/fixtures/skill-check/skills/base44-cli/references/functions-create.md new file mode 100644 index 0000000..c523cdc --- /dev/null +++ b/evals/fixtures/skill-check/skills/base44-cli/references/functions-create.md @@ -0,0 +1,229 @@ +# Creating Functions + +Base44 functions are serverless backend functions that run on Deno. They are defined locally in your project and deployed to the Base44 backend. + +## Function Directory + +All function definitions must be placed in the `base44/functions/` folder in your project. Each function lives in its own subdirectory with a configuration file and entry point. + +Example structure: +``` +my-app/ + base44/ + functions/ + process-order/ + function.jsonc + index.ts + send-notification/ + function.jsonc + index.ts +``` + +## How to Create a Function + +1. Create a new directory in `base44/functions/` with your function name (use kebab-case) +2. Create a `function.jsonc` configuration file in the directory +3. Create the entry point file (e.g., `index.ts`) +4. Deploy the function using the CLI + +## Function Configuration + +Each function requires a `function.jsonc` configuration file: + +```jsonc +{ + "name": "my-function", + "entry": "index.ts" +} +``` + +### Configuration Properties + +| Property | Description | Required | +|----------|-------------|----------| +| `name` | Function name (must match `/^[^.]+$/` - no dots allowed) | Yes | +| `entry` | Entry point file path relative to the function directory (min 1 char) | Yes | + +## Entry Point File + +Functions run on Deno and must export using `Deno.serve()`. Use `npm:` prefix for npm packages. + +```typescript +import { createClientFromRequest } from "npm:@base44/sdk"; + +Deno.serve(async (req) => { + // Get authenticated client from request + const base44 = createClientFromRequest(req); + + // Parse input + const { orderId, action } = await req.json(); + + // Your logic here + const order = await base44.entities.Orders.get(orderId); + + // Return response + return Response.json({ + success: true, + order: order + }); +}); +``` + +### Request Object + +The function receives a standard Deno `Request` object: +- `req.json()` - Parse JSON body +- `req.text()` - Get raw text body +- `req.headers` - Access request headers +- `req.method` - HTTP method + +### Response Object + +Return using `Response.json()` for JSON responses: + +```typescript +// Success response +return Response.json({ data: result }); + +// Error response with status code +return Response.json({ error: "Something went wrong" }, { status: 400 }); + +// Not found +return Response.json({ error: "Order not found" }, { status: 404 }); +``` + +## Complete Example + +### Directory Structure +``` +base44/ + functions/ + process-order/ + function.jsonc + index.ts +``` + +### function.jsonc +```jsonc +{ + "name": "process-order", + "entry": "index.ts" +} +``` + +### index.ts +```typescript +import { createClientFromRequest } from "npm:@base44/sdk"; + +Deno.serve(async (req) => { + try { + const base44 = createClientFromRequest(req); + const { orderId } = await req.json(); + + // Validate input + if (!orderId) { + return Response.json( + { error: "Order ID is required" }, + { status: 400 } + ); + } + + // Fetch and process the order + const order = await base44.entities.Orders.get(orderId); + if (!order) { + return Response.json( + { error: "Order not found" }, + { status: 404 } + ); + } + + return Response.json({ + success: true, + orderId: order.id, + processedAt: new Date().toISOString() + }); + + } catch (error) { + return Response.json( + { error: error.message }, + { status: 500 } + ); + } +}); +``` + +## Using Service Role Access + +For admin-level operations, use `asServiceRole`: + +```typescript +import { createClientFromRequest } from "npm:@base44/sdk"; + +Deno.serve(async (req) => { + const base44 = createClientFromRequest(req); + + // Check user is authenticated + const user = await base44.auth.me(); + if (!user) { + return Response.json({ error: "Unauthorized" }, { status: 401 }); + } + + // Use service role for admin operations + const allOrders = await base44.asServiceRole.entities.Orders.list(); + + return Response.json({ orders: allOrders }); +}); +``` + +## Using Secrets + +Access environment variables configured in the app dashboard: + +```typescript +Deno.serve(async (req) => { + // Access environment variables (configured in app settings) + const apiKey = Deno.env.get("STRIPE_API_KEY"); + + const response = await fetch("https://api.stripe.com/v1/charges", { + headers: { + "Authorization": `Bearer ${apiKey}` + } + }); + + return Response.json(await response.json()); +}); +``` + +## Naming Conventions + +- **Directory name**: Use kebab-case (e.g., `process-order`, `send-notification`) +- **Function name**: Match the directory name, must match pattern `/^[^.]+$/` (no dots allowed) + - Valid: `process-order`, `send_notification`, `myFunction` + - Invalid: `process.order`, `send.notification.v2` +- **Entry file**: Typically `index.ts` or `index.js` + +## Deploying Functions + +After creating your function, deploy it to Base44: + +```bash +npx base44 functions deploy +``` + +For more details on deploying, see [functions-deploy.md](functions-deploy.md). + +## Notes + +- Functions run on Deno runtime, not Node.js +- Use `npm:` prefix for npm packages (e.g., `npm:@base44/sdk`) +- Use `createClientFromRequest(req)` to get a client that inherits the caller's auth context +- Configure secrets via app dashboard for API keys +- Make sure to handle errors gracefully and return appropriate HTTP status codes + +## Common Mistakes + +| Wrong | Correct | Why | +|-------|---------|-----| +| `functions/myFunction.js` (single file) | `functions/my-function/index.ts` + `function.jsonc` | Functions require subdirectory with config | +| `import { ... } from "@base44/sdk"` | `import { ... } from "npm:@base44/sdk"` | Deno requires `npm:` prefix for npm packages | +| `MyFunction` or `myFunction` directory | `my-function` directory | Use kebab-case for directory names | diff --git a/evals/fixtures/skill-check/skills/base44-cli/references/functions-deploy.md b/evals/fixtures/skill-check/skills/base44-cli/references/functions-deploy.md new file mode 100644 index 0000000..f89d796 --- /dev/null +++ b/evals/fixtures/skill-check/skills/base44-cli/references/functions-deploy.md @@ -0,0 +1,78 @@ +# base44 functions deploy + +Deploy local function definitions to Base44. + +## Syntax + +```bash +npx base44 functions deploy +``` + +## Authentication + +**Required**: Yes. If not authenticated, you'll be prompted to login first. + +## What It Does + +1. Scans the `base44/functions/` directory for function definitions +2. Validates that functions exist and have valid configurations +3. Displays the count of functions to be deployed +4. Uploads function code and configuration to Base44 +5. Reports the results: deployed and deleted functions + +## Prerequisites + +- Must be run from a Base44 project directory +- Project must have function definitions in the `base44/functions/` folder +- Each function must have a valid `function.jsonc` config file + +## Output + +```bash +$ npx base44 functions deploy + +Found 2 functions to deploy +Deploying functions to Base44... + +Deployed: process-order, send-notification +Deleted: old-function + +✓ Functions deployed successfully +``` + +## Function Synchronization + +The deploy operation synchronizes your local functions with Base44: + +- **Deployed**: Functions that were created or updated +- **Deleted**: Functions that were removed from your local configuration + +## Error Handling + +If no functions are found in your project: +```bash +$ npx base44 functions deploy +No functions found. Create functions in the 'functions' directory. +``` + +If a function has configuration errors: +```bash +$ npx base44 functions deploy +Function deployment errors: +'my-function' function: Entry point cannot be empty +``` + +## Use Cases + +- After creating new functions in your project +- When modifying existing function code or configuration +- To sync function changes before testing +- As part of your development workflow when backend logic changes + +## Notes + +- This command deploys the function code and configuration +- Changes are applied to your Base44 project immediately +- Make sure to test functions in a development environment first +- Function definitions are located in the `base44/functions/` directory +- For how to create functions, see [functions-create.md](functions-create.md) diff --git a/evals/fixtures/skill-check/skills/base44-cli/references/link.md b/evals/fixtures/skill-check/skills/base44-cli/references/link.md new file mode 100644 index 0000000..0b009ce --- /dev/null +++ b/evals/fixtures/skill-check/skills/base44-cli/references/link.md @@ -0,0 +1,81 @@ +# base44 link + +Links an existing local Base44 project to a Base44 app in the cloud. Use this when you have a `base44/config.jsonc` but haven't connected it to a Base44 app yet. + +## Critical: When to Use Link vs Create + +| Scenario | Command | +|----------|---------| +| Starting fresh, no `base44/` folder | `npx base44 create` | +| Have `base44/config.jsonc` but no `.app.jsonc` | `npx base44 link` | +| Project already linked (has `.app.jsonc`) | Already done, use `deploy` | + +## Syntax + +```bash +npx base44 link [options] +``` + +## Options + +| Option | Description | Required | +|--------|-------------|----------| +| `-c, --create` | Create a new project (skip selection prompt) | No | +| `-n, --name ` | Project name (required when `--create` is used) | With `--create` | +| `-d, --description ` | Project description | No | +| `-p, --projectId ` | Project ID to link to an existing project (skip selection prompt) | No | + +## Non-Interactive Mode + +For CI/CD or agent use: + +**Create a new project:** +```bash +npx base44 link --create --name my-app +``` + +**Link to an existing project:** +```bash +npx base44 link --projectId +``` + +WRONG: `npx base44 link --create` (missing --name) +WRONG: `npx base44 link --create --projectId ` (cannot use both) +RIGHT: `npx base44 link --create --name my-app` +RIGHT: `npx base44 link --projectId ` + +## Examples + +```bash +# Interactive mode - prompts for project details +npx base44 link + +# Non-interactive - create and link in one step +npx base44 link --create --name my-app + +# With description +npx base44 link --create --name my-app --description "My awesome app" + +# Link to a specific existing project by ID +npx base44 link --projectId abc123 +``` + +## What It Does + +1. Finds the `base44/config.jsonc` in the current directory (or parent directories) +2. Verifies no `.app.jsonc` exists (project not already linked) +3. Either: + - Creates a new Base44 app in the cloud (with `--create`), OR + - Links to an existing project (with `--projectId` or interactive selection) +4. Writes the app ID to `base44/.app.jsonc` + +## Requirements + +- Must have `base44/config.jsonc` in the project +- Must NOT have `base44/.app.jsonc` (use `deploy` if already linked) +- Must be authenticated (run `npx base44 login` first) + +## Notes + +- After linking, you can deploy resources with `npx base44 deploy` +- The `.app.jsonc` file should be git-ignored (contains your app ID) diff --git a/evals/fixtures/skill-check/skills/base44-cli/references/rls-examples.md b/evals/fixtures/skill-check/skills/base44-cli/references/rls-examples.md new file mode 100644 index 0000000..7d3ef42 --- /dev/null +++ b/evals/fixtures/skill-check/skills/base44-cli/references/rls-examples.md @@ -0,0 +1,463 @@ +# RLS Examples + +Practical Row-Level Security patterns for common application types. + +**Important:** Base44 RLS supports: +- **Logical operators:** `$or`, `$and`, `$nor` for combining conditions +- **Field operators (for `data.*` fields):** `$in`, `$nin`, `$ne`, `$all` +- **user_condition:** Equality only (no operators) + +## Contents +- [Simple Patterns (JSON Schema)](#simple-patterns-json-schema) +- [Using Operators](#using-operators) +- [Field-Level Security Examples](#field-level-security-examples) +- [Complex Patterns (Dashboard UI or Backend)](#complex-patterns-dashboard-ui-or-backend) +- [Best Practices](#best-practices) + +--- + +## Simple Patterns (JSON Schema) + +These patterns work with the JSON schema RLS format. + +### Todo App - Owner-only access + +Users see and manage only their own tasks. + +```jsonc +{ + "name": "Task", + "type": "object", + "properties": { + "title": { "type": "string" }, + "description": { "type": "string" }, + "completed": { "type": "boolean" }, + "priority": { "type": "string", "enum": ["low", "medium", "high"] }, + "due_date": { "type": "string", "format": "date" } + }, + "rls": { + "create": true, + "read": { "created_by": "{{user.email}}" }, + "update": { "created_by": "{{user.email}}" }, + "delete": { "created_by": "{{user.email}}" } + } +} +``` + +### Contact Form - Public create, admin-only read + +Anyone can submit, only admins can view submissions. + +```jsonc +{ + "name": "ContactSubmission", + "type": "object", + "properties": { + "name": { "type": "string" }, + "email": { "type": "string", "format": "email" }, + "message": { "type": "string" } + }, + "rls": { + "create": true, + "read": { "user_condition": { "role": "admin" } }, + "update": { "user_condition": { "role": "admin" } }, + "delete": { "user_condition": { "role": "admin" } } + } +} +``` + +### User Profile - Self-management + +Users can only access their own profile. + +```jsonc +{ + "name": "UserProfile", + "type": "object", + "properties": { + "name": { "type": "string" }, + "avatar_url": { "type": "string" }, + "bio": { "type": "string" }, + "preferences": { "type": "object" } + }, + "rls": { + "create": true, + "read": { "created_by": "{{user.email}}" }, + "update": { "created_by": "{{user.email}}" }, + "delete": { "created_by": "{{user.email}}" } + } +} +``` + +### Department Data - Same department access + +Users can only see records from their department. + +```jsonc +{ + "name": "DepartmentAnnouncement", + "type": "object", + "properties": { + "title": { "type": "string" }, + "content": { "type": "string" }, + "department": { "type": "string" } + }, + "rls": { + "create": { "user_condition": { "role": "manager" } }, + "read": { "data.department": "{{user.data.department}}" }, + "update": { "user_condition": { "role": "manager" } }, + "delete": { "user_condition": { "role": "admin" } } + } +} +``` + +### Subscription - Admin-managed, user-readable via email field + +```jsonc +{ + "name": "Subscription", + "type": "object", + "properties": { + "user_email": { "type": "string" }, + "tier": { "type": "string", "enum": ["free", "basic", "pro", "enterprise"] }, + "credits": { "type": "number" }, + "renewal_date": { "type": "string", "format": "date" } + }, + "rls": { + "create": { "user_condition": { "role": "admin" } }, + "read": { "data.user_email": "{{user.email}}" }, + "update": { "user_condition": { "role": "admin" } }, + "delete": { "user_condition": { "role": "admin" } } + } +} +``` + +**Note:** This pattern only allows users to read their own subscription. Admins need to use the Dashboard UI to configure additional read access for themselves. + +### Private Data - Owner-only + +```jsonc +{ + "name": "PrivateNotes", + "type": "object", + "properties": { + "title": { "type": "string" }, + "content": { "type": "string" }, + "tags": { "type": "array", "items": { "type": "string" } } + }, + "rls": { + "create": true, + "read": { "created_by": "{{user.email}}" }, + "update": { "created_by": "{{user.email}}" }, + "delete": { "created_by": "{{user.email}}" } + } +} +``` + +### Public Read, Authenticated Write + +Anyone can read, only logged-in users can create/edit their own records. + +```jsonc +{ + "name": "BlogPost", + "type": "object", + "properties": { + "title": { "type": "string" }, + "content": { "type": "string" }, + "author_email": { "type": "string" } + }, + "rls": { + "create": true, + "read": true, + "update": { "created_by": "{{user.email}}" }, + "delete": { "created_by": "{{user.email}}" } + } +} +``` + +--- + +## Using Operators + +### Logical Operators + +Combine multiple conditions using `$or`, `$and`, or `$nor`: + +**Owner OR Admin access:** +```jsonc +{ + "name": "Document", + "type": "object", + "properties": { + "title": { "type": "string" }, + "content": { "type": "string" } + }, + "rls": { + "create": true, + "read": { + "$or": [ + { "created_by": "{{user.email}}" }, + { "user_condition": { "role": "admin" } } + ] + }, + "update": { + "$or": [ + { "created_by": "{{user.email}}" }, + { "user_condition": { "role": "admin" } } + ] + }, + "delete": { "user_condition": { "role": "admin" } } + } +} +``` + +**Multiple roles with $or:** +```jsonc +{ + "rls": { + "read": { + "$or": [ + { "user_condition": { "role": "admin" } }, + { "user_condition": { "role": "manager" } }, + { "user_condition": { "role": "hr" } } + ] + } + } +} +``` + +### Field Operators for data.* Fields + +Use `$in`, `$nin`, `$ne`, `$all` for comparing entity data fields: + +**Access based on tags ($in):** +```jsonc +{ + "rls": { + "read": { + "data.category": { "$in": ["public", "shared"] } + } + } +} +``` + +**Exclude specific statuses ($nin):** +```jsonc +{ + "rls": { + "read": { + "data.status": { "$nin": ["archived", "deleted"] } + } + } +} +``` + +**Not equal ($ne):** +```jsonc +{ + "rls": { + "read": { + "data.visibility": { "$ne": "private" } + } + } +} +``` + +**All tags must match ($all):** +```jsonc +{ + "rls": { + "read": { + "data.required_tags": { "$all": ["approved", "reviewed"] } + } + } +} +``` + +### Combining Logical and Field Operators + +```jsonc +{ + "rls": { + "read": { + "$and": [ + { "data.status": { "$ne": "draft" } }, + { + "$or": [ + { "created_by": "{{user.email}}" }, + { "data.visibility": "public" } + ] + } + ] + } + } +} +``` + +--- + +## Field-Level Security Examples + +Control access to specific fields within an entity. + +### Sensitive Salary Field + +```jsonc +{ + "name": "Employee", + "type": "object", + "properties": { + "name": { "type": "string" }, + "email": { "type": "string", "format": "email" }, + "salary": { + "type": "number", + "description": "Annual salary", + "rls": { + "read": { "user_condition": { "role": "hr" } }, + "write": { "user_condition": { "role": "hr" } } + } + }, + "performance_notes": { + "type": "string", + "description": "Manager notes", + "rls": { + "read": { + "$or": [ + { "user_condition": { "role": "manager" } }, + { "user_condition": { "role": "hr" } } + ] + }, + "write": { "user_condition": { "role": "manager" } } + } + } + } +} +``` + +### Admin-Only Internal Fields + +```jsonc +{ + "name": "Order", + "type": "object", + "properties": { + "order_number": { "type": "string" }, + "total": { "type": "number" }, + "internal_notes": { + "type": "string", + "description": "Internal processing notes", + "rls": { + "read": { "user_condition": { "role": "admin" } }, + "write": { "user_condition": { "role": "admin" } } + } + }, + "profit_margin": { + "type": "number", + "description": "Profit margin percentage", + "rls": { + "read": { "user_condition": { "role": "admin" } }, + "write": false + } + } + } +} +``` + +--- + +## Complex Patterns (Dashboard UI or Backend) + +Some patterns may still require the Dashboard UI or backend functions. + +### Bidirectional Relationships (e.g., Friendships, Matches) + +**Requirement:** Either party in a relationship should have access. + +**Now possible with $or:** +```jsonc +{ + "rls": { + "read": { + "$or": [ + { "data.user_a_email": "{{user.email}}" }, + { "data.user_b_email": "{{user.email}}" } + ] + } + } +} +``` + +**Alternative solutions:** +1. **Entity redesign:** Store two records per relationship (one for each party) +2. **Backend function:** Query with custom logic + +### Complex Business Logic + +**Requirement:** Access depends on multiple entity fields with complex conditions. + +**JSON Schema limitation:** While operators help, very complex business logic may still be hard to express. + +**Solution options:** +1. **Backend function:** Implement custom access logic +2. **Combine simpler rules:** Break complex rules into simpler entity-level and field-level rules + +--- + +## Best Practices + +### Security Strategy + +Use a combination of entity-level RLS and field-level security: + +| Data Type | Approach | Example | +|-----------|----------|---------| +| User-editable | Entity RLS: Owner-only | UserProfile with `created_by` check | +| Sensitive fields | Field-level RLS | Salary field with HR role check | +| Multi-role access | `$or` with user_condition | Admin OR Manager access | +| Conditional access | Field operators | `$in`, `$ne` on data fields | +| Public content | Entity RLS: `read: true` | PublicPost | +| Private content | Entity RLS: Owner-only | PrivateNote | + +### When to Use Each Approach + +| Requirement | Approach | +|-------------|----------| +| Single condition (owner, admin, department) | JSON Schema RLS | +| Multiple OR/AND conditions | JSON Schema RLS with `$or`/`$and` | +| Field value checks with `$in`/`$ne`/etc. | JSON Schema RLS for `data.*` fields | +| Field-level access control | JSON Schema FLS (field-level `rls`) | +| Complex comparison operators (`$gt`, `$lt`) | Backend functions | +| Very complex business logic | Backend functions | + +### Common Role Patterns + +| Role | Typical Access | +|------|----------------| +| `admin` | Full access to all records | +| `moderator` | Read/update access, limited delete | +| `manager` | Department-scoped access | +| `user` | Own records only | + +### Supported Operators Summary + +| Operator | Supported | Notes | +|----------|-----------|-------| +| `$or` | Yes | Combine multiple conditions | +| `$and` | Yes | All conditions must match | +| `$nor` | Yes | None of the conditions match | +| `$in` | Yes | For `data.*` fields only | +| `$nin` | Yes | For `data.*` fields only | +| `$ne` | Yes | For `data.*` fields only | +| `$all` | Yes | For `data.*` fields only | +| `$gt`, `$lt`, `$gte`, `$lte` | No | Use backend functions | +| `$regex` | No | Use backend functions | + +### Limitations Summary + +| Not Supported | Alternative | +|---------------|-------------| +| Operators on `user_condition` | Use equality only for user checks | +| Comparison operators (`$gt`, `$lt`) | Backend functions | +| Regex matching (`$regex`) | Backend functions | +| Cross-entity relationships | Backend functions | diff --git a/evals/fixtures/skill-check/skills/base44-cli/references/site-deploy.md b/evals/fixtures/skill-check/skills/base44-cli/references/site-deploy.md new file mode 100644 index 0000000..929d302 --- /dev/null +++ b/evals/fixtures/skill-check/skills/base44-cli/references/site-deploy.md @@ -0,0 +1,118 @@ +# base44 site deploy + +Deploy built site files to Base44 hosting. + +## Table of Contents + +- [Syntax](#syntax) +- [Authentication](#authentication) +- [Prerequisites](#prerequisites) +- [How It Works](#how-it-works) +- [Interactive Flow](#interactive-flow) +- [Typical Workflow](#typical-workflow) +- [Configuration](#configuration) +- [Error Handling](#error-handling) +- [Use Cases](#use-cases) +- [Notes](#notes) + +## Syntax + +```bash +npx base44 site deploy [options] +``` + +## Options + +| Option | Description | +| ------------ | ------------------------- | +| `-y, --yes` | Skip confirmation prompt | + +Use `-y` flag for non-interactive/automated deployments: + +```bash +npx base44 site deploy -y +``` + +## Authentication + +**Required**: Yes. If not authenticated, you'll be prompted to login first. + +## Prerequisites + +- Must be run from a Base44 project directory +- Project must have `site.outputDirectory` configured in project config +- Site must be built before deploying (run your build command first) +- **SPA only**: Base44 hosting supports Single Page Applications with a single `index.html` entry point. All routes are served from `index.html` (client-side routing). + +## How It Works + +1. Reads project configuration +2. Validates that site configuration exists +3. Prompts for deployment confirmation showing the output directory +4. Creates an archive of site files from the output directory +5. Deploys to Base44 hosting +6. Returns the app URL + +## Interactive Flow + +```bash +$ npx base44 site deploy + +Deploy site from ./dist? (yes/no) yes + +Creating archive... +Uploading to Base44... +Deploying... + +✓ Deployment successful! + +Visit your site at: https://my-app.base44.app +``` + +## Typical Workflow + +```bash +# 1. Build your site using your framework's build command +npm run build + +# 2. Deploy to Base44 +npx base44 site deploy +``` + +## Configuration + +The `site.outputDirectory` in your project configuration should point to where your framework outputs built files: + +- Vite: typically `./dist` +- Next.js: typically `./.next` or `./out` +- Create React App: typically `./build` +- Custom: whatever your build tool outputs to + +## Error Handling + +If site configuration is missing: +```bash +$ npx base44 site deploy +Error: No site configuration found in project +``` + +If you cancel the deployment: +```bash +Deploy site from ./dist? (yes/no) no +Deployment cancelled +``` + +## Use Cases + +- Deploy your site after making changes +- Push new versions of your application +- Deploy after updating content or functionality +- Part of your CI/CD pipeline + +## Notes + +- Always build your site before deploying +- The command deploys whatever is in your output directory +- Make sure your build completed successfully before deploying +- Previous deployments are preserved (versioned) in Base44 +- Deployment is immediate and updates your live site diff --git a/evals/fixtures/skill-check/skills/base44-sdk/SKILL.md b/evals/fixtures/skill-check/skills/base44-sdk/SKILL.md new file mode 100644 index 0000000..01ddd7f --- /dev/null +++ b/evals/fixtures/skill-check/skills/base44-sdk/SKILL.md @@ -0,0 +1,296 @@ +--- +name: base44-sdk +description: "The base44 SDK is the library to communicate with base44 services. In projects, you use it to communicate with remote resources (entities, backend functions, ai agents) and to write backend functions. This skill is the place for learning about available modules and types. When you plan or implement a feature, you must learn this skill" +--- + +# Base44 Coder + +Build apps on the Base44 platform using the Base44 JavaScript SDK. + +## ⚡ IMMEDIATE ACTION REQUIRED - Read This First + +This skill activates on ANY mention of "base44" or when a `base44/` folder exists. **DO NOT read documentation files or search the web before acting.** + +**Your first action MUST be:** +1. Check if `base44/config.jsonc` exists in the current directory +2. If **YES** (existing project scenario): + - This skill (base44-sdk) handles the request + - Implement features using Base44 SDK + - Do NOT use base44-cli unless user explicitly requests CLI commands +3. If **NO** (new project scenario): + - Transfer to base44-cli skill for project initialization + - This skill cannot help until project is initialized + +## When to Use This Skill vs base44-cli + +**Use base44-sdk when:** +- Building features in an **EXISTING** Base44 project +- `base44/config.jsonc` already exists in the project +- Base44 SDK imports are present (`@base44/sdk`) +- Writing JavaScript/TypeScript code using Base44 SDK modules +- Implementing functionality, components, or features +- User mentions: "implement", "build a feature", "add functionality", "write code for" +- User says "create a [type] app" **and** a Base44 project already exists + +**DO NOT USE base44-sdk for:** +- ❌ Initializing new Base44 projects (use `base44-cli` instead) +- ❌ Empty directories without Base44 configuration +- ❌ When user says "create a new Base44 project/app/site" and no project exists +- ❌ CLI commands like `npx base44 create`, `npx base44 deploy`, `npx base44 login` (use `base44-cli`) + +**Skill Dependencies:** +- `base44-sdk` assumes a Base44 project is **already initialized** +- `base44-cli` is a **prerequisite** for `base44-sdk` in new projects +- If user wants to "create an app" and no Base44 project exists, use `base44-cli` first + +**State Check Logic:** +Before selecting this skill, verify: +- IF (user mentions "create/build app" OR "make a project"): + - IF (directory is empty OR no `base44/config.jsonc` exists): + → Use **base44-cli** (project initialization needed) + - ELSE: + → Use **base44-sdk** (project exists, build features) + +## Quick Start + +```javascript +// In Base44-generated apps, base44 client is pre-configured and available + +// CRUD operations +const task = await base44.entities.Task.create({ title: "New task", status: "pending" }); +const tasks = await base44.entities.Task.list(); +await base44.entities.Task.update(task.id, { status: "done" }); + +// Get current user +const user = await base44.auth.me(); +``` + +```javascript +// External apps +import { createClient } from "@base44/sdk"; + +// IMPORTANT: Use 'appId' (NOT 'clientId' or 'id') +const base44 = createClient({ appId: "your-app-id" }); +await base44.auth.loginViaEmailPassword("user@example.com", "password"); +``` + +## ⚠️ CRITICAL: Do Not Hallucinate APIs + +**Before writing ANY Base44 code, verify method names against this table or [QUICK_REFERENCE.md](references/QUICK_REFERENCE.md).** + +Base44 SDK has unique method names. Do NOT assume patterns from Firebase, Supabase, or other SDKs. + +### Authentication - WRONG vs CORRECT + +| ❌ WRONG (hallucinated) | ✅ CORRECT | +|------------------------|-----------| +| `signInWithGoogle()` | `loginWithProvider('google')` | +| `signInWithProvider('google')` | `loginWithProvider('google')` | +| `auth.google()` | `loginWithProvider('google')` | +| `signInWithEmailAndPassword(email, pw)` | `loginViaEmailPassword(email, pw)` | +| `signIn(email, pw)` | `loginViaEmailPassword(email, pw)` | +| `createUser()` / `signUp()` | `register({email, password})` | +| `onAuthStateChanged()` | `me()` (no listener, call when needed) | +| `currentUser` | `await auth.me()` | + +### Functions - WRONG vs CORRECT + +| ❌ WRONG (hallucinated) | ✅ CORRECT | +|------------------------|-----------| +| `functions.call('name', data)` | `functions.invoke('name', data)` | +| `functions.run('name', data)` | `functions.invoke('name', data)` | +| `callFunction('name', data)` | `functions.invoke('name', data)` | +| `httpsCallable('name')(data)` | `functions.invoke('name', data)` | + +### Integrations - WRONG vs CORRECT + +| ❌ WRONG (hallucinated) | ✅ CORRECT | +|------------------------|-----------| +| `ai.generate(prompt)` | `integrations.Core.InvokeLLM({prompt})` | +| `openai.chat(prompt)` | `integrations.Core.InvokeLLM({prompt})` | +| `llm(prompt)` | `integrations.Core.InvokeLLM({prompt})` | +| `sendEmail(to, subject, body)` | `integrations.Core.SendEmail({to, subject, body})` | +| `email.send()` | `integrations.Core.SendEmail({to, subject, body})` | +| `uploadFile(file)` | `integrations.Core.UploadFile({file})` | +| `storage.upload(file)` | `integrations.Core.UploadFile({file})` | + +### Entities - WRONG vs CORRECT + +| ❌ WRONG (hallucinated) | ✅ CORRECT | +|------------------------|-----------| +| `entities.Task.find({...})` | `entities.Task.filter({...})` | +| `entities.Task.findOne(id)` | `entities.Task.get(id)` | +| `entities.Task.insert(data)` | `entities.Task.create(data)` | +| `entities.Task.remove(id)` | `entities.Task.delete(id)` | +| `entities.Task.onChange(cb)` | `entities.Task.subscribe(cb)` | + +## SDK Modules + +| Module | Purpose | Reference | +|--------|---------|-----------| +| `entities` | CRUD operations on data models | [entities.md](references/entities.md) | +| `auth` | Login, register, user management | [auth.md](references/auth.md) | +| `agents` | AI conversations and messages | [base44-agents.md](references/base44-agents.md) | +| `functions` | Backend function invocation | [functions.md](references/functions.md) | +| `integrations` | AI, email, file uploads, custom APIs | [integrations.md](references/integrations.md) | +| `connectors` | OAuth tokens (service role only) | [connectors.md](references/connectors.md) | +| `analytics` | Track custom events and user activity | [analytics.md](references/analytics.md) | +| `appLogs` | Log user activity in app | [app-logs.md](references/app-logs.md) | +| `users` | Invite users to the app | [users.md](references/users.md) | + +For client setup and authentication modes, see [client.md](references/client.md). + +**TypeScript Support:** Each reference file includes a "Type Definitions" section with TypeScript interfaces and types for the module's methods, parameters, and return values. + +## Installation + +Install the Base44 SDK: + +```bash +npm install @base44/sdk +``` + +**Important:** Never assume or hardcode the `@base44/sdk` package version. Always install without a version specifier to get the latest version. + +## Creating a Client (External Apps) + +When creating a client in external apps, **ALWAYS use `appId` as the parameter name**: + +```javascript +import { createClient } from "@base44/sdk"; + +// ✅ CORRECT +const base44 = createClient({ appId: "your-app-id" }); + +// ❌ WRONG - Do NOT use these: +// const base44 = createClient({ clientId: "your-app-id" }); // WRONG +// const base44 = createClient({ id: "your-app-id" }); // WRONG +``` + +**Required parameter:** `appId` (string) - Your Base44 application ID + +**Optional parameters:** +- `token` (string) - Pre-authenticated user token +- `options` (object) - Configuration options + - `options.onError` (function) - Global error handler + +**Example with error handler:** +```javascript +const base44 = createClient({ + appId: "your-app-id", + options: { + onError: (error) => { + console.error("Base44 error:", error); + } + } +}); +``` + +## Module Selection + +**Working with app data?** +- Create/read/update/delete records → `entities` +- Import data from file → `entities.importEntities()` +- Realtime updates → `entities.EntityName.subscribe()` + +**User management?** +- Login/register/logout → `auth` +- Get current user → `auth.me()` +- Update user profile → `auth.updateMe()` +- Invite users → `users.inviteUser()` + +**AI features?** +- Chat with AI agents → `agents` (requires logged-in user) +- Create new conversation → `agents.createConversation()` +- Manage conversations → `agents.getConversations()` +- Generate text/JSON with AI → `integrations.Core.InvokeLLM()` +- Generate images → `integrations.Core.GenerateImage()` + +**Custom backend logic?** +- Run server-side code → `functions.invoke()` +- Need admin access → `base44.asServiceRole.functions.invoke()` + +**External services?** +- Send emails → `integrations.Core.SendEmail()` +- Upload files → `integrations.Core.UploadFile()` +- Custom APIs → `integrations.custom.call()` +- OAuth tokens (Google, Slack) → `connectors` (backend only) + +**Tracking and analytics?** +- Track custom events → `analytics.track()` +- Log page views/activity → `appLogs.logUserInApp()` + +## Common Patterns + +### Filter and Sort Data + +```javascript +const pendingTasks = await base44.entities.Task.filter( + { status: "pending", assignedTo: userId }, // query + "-created_date", // sort (descending) + 10, // limit + 0 // skip +); +``` + +### Protected Routes (check auth) + +```javascript +const user = await base44.auth.me(); +if (!user) { + // Navigate to your custom login page + navigate('/login', { state: { returnTo: window.location.pathname } }); + return; +} +``` + +### Backend Function Call + +```javascript +// Frontend +const result = await base44.functions.invoke("processOrder", { + orderId: "123", + action: "ship" +}); + +// Backend function (Deno) +import { createClientFromRequest } from "npm:@base44/sdk"; + +Deno.serve(async (req) => { + const base44 = createClientFromRequest(req); + const { orderId, action } = await req.json(); + // Process with service role for admin access + const order = await base44.asServiceRole.entities.Orders.get(orderId); + return Response.json({ success: true }); +}); +``` + +### Service Role Access + +Use `asServiceRole` in backend functions for admin-level operations: + +```javascript +// User mode - respects permissions +const myTasks = await base44.entities.Task.list(); + +// Service role - full access (backend only) +const allTasks = await base44.asServiceRole.entities.Task.list(); +const token = await base44.asServiceRole.connectors.getAccessToken("slack"); +``` + +## Frontend vs Backend + +| Capability | Frontend | Backend | +|------------|----------|---------| +| `entities` (user's data) | Yes | Yes | +| `auth` | Yes | Yes | +| `agents` | Yes | Yes | +| `functions.invoke()` | Yes | Yes | +| `integrations` | Yes | Yes | +| `analytics` | Yes | Yes | +| `appLogs` | Yes | Yes | +| `users` | Yes | Yes | +| `asServiceRole.*` | No | Yes | +| `connectors` | No | Yes | + +Backend functions use `Deno.serve()` and `createClientFromRequest(req)` to get a properly authenticated client. diff --git a/evals/fixtures/skill-check/skills/base44-sdk/references/QUICK_REFERENCE.md b/evals/fixtures/skill-check/skills/base44-sdk/references/QUICK_REFERENCE.md new file mode 100644 index 0000000..d357384 --- /dev/null +++ b/evals/fixtures/skill-check/skills/base44-sdk/references/QUICK_REFERENCE.md @@ -0,0 +1,155 @@ +# Base44 SDK Quick Reference + +Compact method signatures for all SDK modules. **Verify against this before writing code.** + +--- + +## Auth (`base44.auth.*`) + +``` +loginViaEmailPassword(email, password, turnstileToken?) → Promise<{access_token, user}> +loginWithProvider('google' | 'microsoft' | 'facebook', fromUrl?) → void +me() → Promise +updateMe(data) → Promise +isAuthenticated() → Promise +logout(redirectUrl?) → void +redirectToLogin(nextUrl) → void # ⚠️ Avoid - prefer custom login UI +register({email, password, turnstile_token?, referral_code?}) → Promise +verifyOtp({email, otpCode}) → Promise +resendOtp(email) → Promise +inviteUser(userEmail, role) → Promise +resetPasswordRequest(email) → Promise +resetPassword({resetToken, newPassword}) → Promise +changePassword({userId, currentPassword, newPassword}) → Promise +setToken(token, saveToStorage?) → void +``` + +--- + +## Entities (`base44.entities.EntityName.*`) + +``` +create(data) → Promise +bulkCreate(dataArray) → Promise +list(sort?, limit?, skip?, fields?) → Promise +filter(query, sort?, limit?, skip?, fields?) → Promise +get(id) → Promise +update(id, data) → Promise +delete(id) → Promise +deleteMany(query) → Promise +importEntities(file) → Promise // frontend only +subscribe(callback) → () => void // returns unsubscribe fn +``` + +**Sort:** Use `-fieldName` for descending (e.g., `-created_date`) + +--- + +## Functions (`base44.functions.*`) + +``` +invoke(functionName, data) → Promise +``` + +**Backend:** Use `base44.asServiceRole.functions.invoke()` for admin access. + +--- + +## Integrations (`base44.integrations.Core.*`) + +``` +InvokeLLM({prompt, add_context_from_internet?, response_json_schema?, file_urls?}) → Promise +GenerateImage({prompt}) → Promise<{url}> +SendEmail({to, subject, body, from_name?}) → Promise +UploadFile({file}) → Promise<{file_url}> +UploadPrivateFile({file}) → Promise<{file_uri}> +CreateFileSignedUrl({file_uri, expires_in?}) → Promise<{signed_url}> +ExtractDataFromUploadedFile({file_url, json_schema}) → Promise +``` + +### Custom Integrations (`base44.integrations.custom.*`) + +``` +call(slug, operationId, {payload?, pathParams?, queryParams?, headers?}?) → Promise<{success, status_code, data}> +``` + +**operationId format:** `"method:/path"` (e.g., `"get:/contacts"`, `"post:/users/{id}"`) + +--- + +## Analytics (`base44.analytics.*`) + +``` +track({eventName, properties?}) → void +``` + +--- + +## App Logs (`base44.appLogs.*`) + +``` +logUserInApp(pageName) → Promise +``` + +--- + +## Users (`base44.users.*`) + +``` +inviteUser(userEmail, role) → Promise // role: 'user' | 'admin' +``` + +--- + +## Connectors (`base44.asServiceRole.connectors.*`) + +**Backend only, service role required.** + +``` +getAccessToken(integrationType) → Promise +``` + +**Types:** `'googlecalendar'`, `'googledrive'`, `'slack'`, `'notion'`, `'salesforce'`, `'hubspot'`, `'linkedin'`, `'tiktok'`, `'github'` + +--- + +## Service Role Access + +**Backend functions only.** Prefix any module with `asServiceRole` for admin access: + +```javascript +base44.asServiceRole.entities.Task.list() +base44.asServiceRole.functions.invoke('name', data) +base44.asServiceRole.connectors.getAccessToken('slack') +``` + +--- + +## Backend Function Template + +```javascript +import { createClientFromRequest } from "@base44/sdk"; + +Deno.serve(async (req) => { + const base44 = createClientFromRequest(req); + const data = await req.json(); + + // User context + const user = await base44.auth.me(); + + // Service role for admin operations + const allRecords = await base44.asServiceRole.entities.Task.list(); + + return Response.json({ success: true }); +}); +``` + +--- + +## Client Initialization (External Apps) + +```javascript +import { createClient } from "@base44/sdk"; + +const base44 = createClient({ appId: "your-app-id" }); // MUST use 'appId' +``` diff --git a/evals/fixtures/skill-check/skills/base44-sdk/references/analytics.md b/evals/fixtures/skill-check/skills/base44-sdk/references/analytics.md new file mode 100644 index 0000000..d1b2c69 --- /dev/null +++ b/evals/fixtures/skill-check/skills/base44-sdk/references/analytics.md @@ -0,0 +1,113 @@ +# Analytics Module + +Track custom events and user activity via `base44.analytics`. + +## Contents +- [Methods](#methods) +- [Examples](#examples) +- [Automatic Tracking](#automatic-tracking) +- [Best Practices](#best-practices) + +## Methods + +| Method | Signature | Description | +|--------|-----------|-------------| +| `track(params)` | `void` | Track a custom event | + +## Examples + +### Track Custom Event + +```javascript +// Track a simple event +base44.analytics.track({ + eventName: "button_clicked" +}); + +// Track event with properties +base44.analytics.track({ + eventName: "purchase_completed", + properties: { + product_id: "prod-123", + amount: 99.99, + currency: "USD" + } +}); +``` + +### Track User Actions + +```javascript +// Track page view +base44.analytics.track({ + eventName: "page_view", + properties: { + page: "/dashboard", + referrer: document.referrer + } +}); + +// Track feature usage +base44.analytics.track({ + eventName: "feature_used", + properties: { + feature: "export_data", + format: "csv" + } +}); +``` + +## Automatic Tracking + +The analytics module automatically tracks: +- **Initialization events**: When the app loads +- **Heartbeat events**: Periodic activity signals +- **Session duration**: Time spent in the app + +These internal events help measure user engagement without manual instrumentation. + +## Best Practices + +1. **Use descriptive event names**: `order_completed` instead of `click` +2. **Include relevant properties**: Add context that helps analyze the event +3. **Be consistent**: Use the same event names and property keys across your app +4. **Don't track sensitive data**: Avoid PII in event properties + +```javascript +// Good: Descriptive with relevant properties +base44.analytics.track({ + eventName: "subscription_started", + properties: { + plan: "pro", + billing_cycle: "annual" + } +}); + +// Avoid: Vague event name, no context +base44.analytics.track({ + eventName: "click" +}); +``` + +## Type Definitions + +```typescript +/** Properties that can be attached to a tracked event. */ +type TrackEventProperties = { + [key: string]: string | number | boolean | null | undefined; +}; + +/** Parameters for the track() method. */ +interface TrackEventParams { + /** The name of the event to track. */ + eventName: string; + /** Optional properties to attach to the event. */ + properties?: TrackEventProperties; +} + +/** The analytics module interface. */ +interface AnalyticsModule { + /** Track a custom event with optional properties. */ + track(params: TrackEventParams): void; +} +``` diff --git a/evals/fixtures/skill-check/skills/base44-sdk/references/app-logs.md b/evals/fixtures/skill-check/skills/base44-sdk/references/app-logs.md new file mode 100644 index 0000000..0439805 --- /dev/null +++ b/evals/fixtures/skill-check/skills/base44-sdk/references/app-logs.md @@ -0,0 +1,78 @@ +# App Logs Module + +Log user activity in your app via `base44.appLogs`. + +## Contents +- [Methods](#methods) +- [Examples](#examples) +- [Use Cases](#use-cases) + +## Methods + +| Method | Signature | Description | +|--------|-----------|-------------| +| `logUserInApp(pageName)` | `Promise` | Log user activity on a page | + +## Examples + +### Log User Activity + +```javascript +// Log when user visits a page +await base44.appLogs.logUserInApp("dashboard"); + +// Log specific page visits +await base44.appLogs.logUserInApp("settings"); +await base44.appLogs.logUserInApp("profile"); + +// Log feature usage +await base44.appLogs.logUserInApp("export-button-click"); +``` + +The page name doesn't have to be an actual page - it can be any string you want to track. + +## Use Cases + +### Track Page Views in React + +```javascript +// Log page views on route change +useEffect(() => { + base44.appLogs.logUserInApp(window.location.pathname); +}, [location.pathname]); +``` + +### Track Feature Usage + +```javascript +// Log when user uses specific features +function handleExport() { + base44.appLogs.logUserInApp("export-data"); + // ... export logic +} + +function handleSettingsChange() { + base44.appLogs.logUserInApp("settings-updated"); + // ... save settings +} +``` + +## Notes + +- Logs appear in the Analytics page of your app dashboard +- App logs track page-level and feature-level activity +- Use `analytics.track()` for custom events with properties, `appLogs.logUserInApp()` for simple page/feature tracking + +## Type Definitions + +```typescript +/** App Logs module for tracking and analyzing app usage. */ +interface AppLogsModule { + /** + * Log user activity in the app. + * @param pageName - Name of the page or section being visited. + * @returns Promise that resolves when the log is recorded. + */ + logUserInApp(pageName: string): Promise; +} +``` diff --git a/evals/fixtures/skill-check/skills/base44-sdk/references/auth.md b/evals/fixtures/skill-check/skills/base44-sdk/references/auth.md new file mode 100644 index 0000000..5195b3a --- /dev/null +++ b/evals/fixtures/skill-check/skills/base44-sdk/references/auth.md @@ -0,0 +1,761 @@ +# Auth Module + +User authentication, registration, and session management via `base44.auth`. + +## Contents +- [TypeScript Types](#typescript-types) +- [Methods](#methods) +- [Examples](#examples) +- [Error Handling](#error-handling) +- [Auth Providers](#auth-providers) +- [Environment Availability](#environment-availability) +- [App Visibility](#app-visibility) +- [Limitations](#limitations) + +--- + +## TypeScript Types + +### User Interface +```typescript +interface User { + id: string; + created_date: string; + updated_date: string; + email: string; + full_name: string | null; + disabled: boolean | null; + is_verified: boolean; + app_id: string; + is_service: boolean; + role: string; + [key: string]: any; // Custom schema fields +} +``` + +### LoginResponse Interface +```typescript +interface LoginResponse { + access_token: string; // JWT token + user: User; // Complete user object +} +``` + +### Parameter Interfaces + +#### RegisterParams +```typescript +interface RegisterParams { + email: string; // Required + password: string; // Required + turnstile_token?: string | null; // Optional: Cloudflare Turnstile for bot protection + referral_code?: string | null; // Optional: Referral code +} +``` + +#### VerifyOtpParams +```typescript +interface VerifyOtpParams { + email: string; // User's email + otpCode: string; // OTP code from email +} +``` + +#### ResetPasswordParams +```typescript +interface ResetPasswordParams { + resetToken: string; // Token from password reset email + newPassword: string; // New password to set +} +``` + +#### ChangePasswordParams +```typescript +interface ChangePasswordParams { + userId: string; // User ID + currentPassword: string; // Current password for verification + newPassword: string; // New password to set +} +``` + +### Provider Type +```typescript +type Provider = 'google' | 'microsoft' | 'facebook'; +``` + +--- + +## Methods + +### Module Interface +```typescript +interface AuthModule { + // User Info + me(): Promise; + updateMe(data: Partial>): Promise; + isAuthenticated(): Promise; + + // Login/Logout + loginViaEmailPassword(email: string, password: string, turnstileToken?: string): Promise; + loginWithProvider(provider: Provider, fromUrl?: string): void; + logout(redirectUrl?: string): void; + redirectToLogin(nextUrl: string): void; + + // Token Management + setToken(token: string, saveToStorage?: boolean): void; + + // Registration + register(params: RegisterParams): Promise; + verifyOtp(params: VerifyOtpParams): Promise; + resendOtp(email: string): Promise; + + // User Management + inviteUser(userEmail: string, role: string): Promise; + + // Password Management + resetPasswordRequest(email: string): Promise; + resetPassword(params: ResetPasswordParams): Promise; + changePassword(params: ChangePasswordParams): Promise; +} +``` + +### Method Reference Table + +| Method | Parameters | Return Type | Description | +|--------|-----------|-------------|-------------| +| `register()` | `params: RegisterParams` | `Promise` | Create new user account | +| `loginViaEmailPassword()` | `email: string, password: string, turnstileToken?: string` | `Promise` | Authenticate with email/password | +| `loginWithProvider()` | `provider: Provider, fromUrl?: string` | `void` | Initiate OAuth login flow | +| `me()` | None | `Promise` | Get current authenticated user | +| `updateMe()` | `data: Partial` | `Promise` | Update current user's profile | +| `logout()` | `redirectUrl?: string` | `void` | Clear session, optionally redirect | +| `redirectToLogin()` | `nextUrl: string` | `void` | ⚠️ **Avoid** - Prefer custom login UI with `loginViaEmailPassword()` or `loginWithProvider()` | +| `isAuthenticated()` | None | `Promise` | Check if user is logged in | +| `setToken()` | `token: string, saveToStorage?: boolean` | `void` | Manually set auth token | +| `inviteUser()` | `userEmail: string, role: string` | `Promise` | Send invitation email | +| `verifyOtp()` | `params: VerifyOtpParams` | `Promise` | Verify OTP code | +| `resendOtp()` | `email: string` | `Promise` | Resend OTP code | +| `resetPasswordRequest()` | `email: string` | `Promise` | Request password reset | +| `resetPassword()` | `params: ResetPasswordParams` | `Promise` | Reset password with token | +| `changePassword()` | `params: ChangePasswordParams` | `Promise` | Change user password | + +--- + +## Examples + +### Register New User (Complete Flow) + +Registration requires email verification before login. Complete flow: + +1. **Register** - Create the user account +2. **Verification email sent** - User receives an OTP code +3. **Verify OTP** - User enters code to verify email +4. **Login** - User can now log in + +```javascript +try { + // Step 1: Register the user + await base44.auth.register({ + email: "user@example.com", + password: "securePassword123", + referral_code: "OPTIONAL_CODE", // optional + turnstile_token: "CAPTCHA_TOKEN" // optional, for bot protection + }); + console.log('Registration successful. Check email for OTP code.'); + + // Step 2: User receives email with OTP code (e.g., "123456") + + // Step 3: Verify the OTP code + await base44.auth.verifyOtp({ + email: "user@example.com", + otpCode: "123456" // code from verification email + }); + console.log('Email verified successfully.'); + + // Step 4: Now the user can log in + const loginResponse = await base44.auth.loginViaEmailPassword( + "user@example.com", + "securePassword123" + ); + console.log('Login successful:', loginResponse.user); + +} catch (error) { + console.error('Registration flow failed:', error.message); + // Handle specific errors (see Error Handling section) +} +``` + +> **Important**: Users cannot log in until they complete OTP verification. Attempting to call `loginViaEmailPassword` before verification will fail. + +### Login with Email/Password + +```javascript +try { + const response = await base44.auth.loginViaEmailPassword( + "user@example.com", + "password123", + turnstileToken // optional: for bot protection + ); + + console.log('Login successful'); + console.log('User:', response.user); + console.log('Token:', response.access_token); + + // JWT is automatically stored for subsequent requests + +} catch (error) { + console.error('Login failed:', error.message); + if (error.status === 401) { + console.error('Invalid credentials'); + } else if (error.status === 403) { + console.error('Email not verified. Please check your email for OTP.'); + } +} +``` + +### Login with OAuth Provider + +```javascript +// Redirect to Google OAuth +base44.auth.loginWithProvider('google'); + +// Redirect to Google OAuth and return to current page after +base44.auth.loginWithProvider('google', window.location.href); + +// Supported providers: 'google', 'microsoft', 'facebook' +base44.auth.loginWithProvider('microsoft'); +``` + +### Get Current User + +```javascript +try { + const user = await base44.auth.me(); + + if (user) { + console.log('User ID:', user.id); + console.log('Email:', user.email); + console.log('Name:', user.full_name); + console.log('Role:', user.role); + console.log('Verified:', user.is_verified); + } else { + console.log('User not authenticated'); + } + +} catch (error) { + console.error('Failed to fetch user:', error.message); + if (error.status === 401) { + // Token expired or invalid - navigate to your custom login page + navigate('/login'); + } +} +``` + +### Update User Profile + +```javascript +try { + const updatedUser = await base44.auth.updateMe({ + full_name: "John Doe", + // Custom schema fields can be updated here + phone: "+1234567890", + preferences: { theme: "dark" } + }); + + console.log('Profile updated:', updatedUser); + +} catch (error) { + console.error('Profile update failed:', error.message); + if (error.status === 400) { + console.error('Invalid data provided'); + } else if (error.status === 401) { + console.error('Authentication required'); + navigate('/login'); + } +} +``` + +### Check Authentication Status + +```javascript +try { + const isLoggedIn = await base44.auth.isAuthenticated(); + + if (isLoggedIn) { + // Show authenticated UI + console.log('User is authenticated'); + } else { + // Show login button + console.log('User is not authenticated'); + } + +} catch (error) { + console.error('Failed to check authentication:', error.message); + // On error, treat as not authenticated +} +``` + +### Logout + +```javascript +// Simple logout +base44.auth.logout(); + +// Logout and redirect to goodbye page +base44.auth.logout("/goodbye"); + +// Logout and redirect to homepage +base44.auth.logout("/"); +``` + +### Protected Route Pattern + +```javascript +// Using a navigation function (e.g., React Router's useNavigate, Next.js router) +async function requireAuth(navigate) { + try { + const user = await base44.auth.me(); + + if (!user) { + // Navigate to your custom login page + navigate('/login', { state: { returnTo: window.location.pathname } }); + return null; + } + + return user; + + } catch (error) { + console.error('Authentication check failed:', error.message); + navigate('/login', { state: { returnTo: window.location.pathname } }); + return null; + } +} + +// Usage in your app +async function loadProtectedPage(navigate) { + const user = await requireAuth(navigate); + if (!user) { + // Will navigate to login + return; + } + + // Continue with authenticated logic + console.log('Welcome,', user.full_name); +} +``` + +### Set Authentication Token + +```javascript +// SECURITY WARNING: Never hardcode tokens or expose them in client code +// Tokens should only be received from secure authentication flows + +// Set token and save to localStorage (default) +base44.auth.setToken(receivedToken); + +// Set token without saving to localStorage (temporary session) +base44.auth.setToken(receivedToken, false); + +// Verify token was set +try { + const isAuthenticated = await base44.auth.isAuthenticated(); + if (!isAuthenticated) { + console.error('Token validation failed'); + } +} catch (error) { + console.error('Failed to set token:', error.message); +} +``` + +### Invite User to Application + +```javascript +try { + // Note: Typically requires admin privileges + const response = await base44.auth.inviteUser( + "newuser@example.com", + "user" // or "admin" + ); + + console.log('Invitation sent successfully'); + +} catch (error) { + console.error('Failed to invite user:', error.message); + if (error.status === 403) { + console.error('Insufficient permissions to invite users'); + } else if (error.status === 400) { + console.error('Invalid email or role'); + } +} +``` + +### OTP Verification + +```javascript +try { + // Verify OTP code sent to user's email + await base44.auth.verifyOtp({ + email: "user@example.com", + otpCode: "123456" + }); + + console.log('OTP verified successfully'); + +} catch (error) { + console.error('OTP verification failed:', error.message); + if (error.status === 400) { + console.error('Invalid or expired OTP code'); + } else if (error.status === 429) { + console.error('Too many attempts. Please try again later.'); + } +} + +// Resend OTP if needed +try { + await base44.auth.resendOtp("user@example.com"); + console.log('OTP resent successfully'); + +} catch (error) { + console.error('Failed to resend OTP:', error.message); + if (error.status === 429) { + console.error('Too many requests. Please wait before trying again.'); + } +} +``` + +### Password Reset Flow + +```javascript +// Step 1: Request password reset +try { + await base44.auth.resetPasswordRequest("user@example.com"); + console.log('Password reset email sent. Check your inbox.'); + +} catch (error) { + console.error('Password reset request failed:', error.message); + if (error.status === 429) { + console.error('Too many requests. Please try again later.'); + } + // Note: For security, don't reveal if email exists +} + +// Step 2: Reset password with token from email +try { + await base44.auth.resetPassword({ + resetToken: "token-from-email", + newPassword: "newSecurePassword123" + }); + + console.log('Password reset successfully. You can now log in.'); + +} catch (error) { + console.error('Password reset failed:', error.message); + if (error.status === 400) { + console.error('Invalid or expired reset token'); + } else if (error.status === 422) { + console.error('Password does not meet requirements'); + } +} +``` + +### Change Password + +```javascript +try { + const currentUser = await base44.auth.me(); + + if (!currentUser) { + throw new Error('User must be authenticated to change password'); + } + + await base44.auth.changePassword({ + userId: currentUser.id, + currentPassword: "oldPassword123", + newPassword: "newSecurePassword456" + }); + + console.log('Password changed successfully'); + +} catch (error) { + console.error('Password change failed:', error.message); + if (error.status === 401) { + console.error('Current password is incorrect'); + } else if (error.status === 422) { + console.error('New password does not meet requirements'); + } else if (error.status === 403) { + console.error('Not authorized to change this password'); + } +} +``` + +--- + +## Error Handling + +### Common Error Scenarios + +The auth module can throw various errors. Here are common scenarios and how to handle them: + +#### Authentication Errors (401/403) +```javascript +try { + const user = await base44.auth.me(); +} catch (error) { + if (error.status === 401) { + // Token expired or invalid - navigate to your custom login page + navigate('/login'); + } else if (error.status === 403) { + // Email not verified or insufficient permissions + console.error('Access denied:', error.message); + } +} +``` + +#### Validation Errors (400/422) +```javascript +try { + await base44.auth.register({ + email: "invalid-email", + password: "weak" + }); +} catch (error) { + if (error.status === 400) { + console.error('Invalid input:', error.message); + // Handle validation errors + } else if (error.status === 422) { + console.error('Data validation failed:', error.details); + } +} +``` + +#### Rate Limiting (429) +```javascript +try { + await base44.auth.resendOtp("user@example.com"); +} catch (error) { + if (error.status === 429) { + console.error('Too many requests. Please wait before trying again.'); + // Show countdown or disable button temporarily + } +} +``` + +#### Generic Error Handler +```javascript +function handleAuthError(error) { + switch (error.status) { + case 400: + return 'Invalid input. Please check your data.'; + case 401: + return 'Authentication required. Redirecting to login...'; + case 403: + return 'Access denied. You may need to verify your email.'; + case 404: + return 'Resource not found.'; + case 422: + return 'Validation failed. Please check your input.'; + case 429: + return 'Too many requests. Please try again later.'; + case 500: + return 'Server error. Please try again.'; + default: + return 'An unexpected error occurred.'; + } +} + +// Usage +try { + await base44.auth.loginViaEmailPassword(email, password); +} catch (error) { + console.error(handleAuthError(error)); +} +``` + +--- + +## Auth Providers + +Configure authentication providers in your app dashboard: + +### Available Providers + +**Built-in (All Plans):** +- **Email/Password** - Default, always enabled +- **Google** - OAuth authentication +- **Microsoft** - OAuth authentication +- **Facebook** - OAuth authentication + +**SSO Providers (Elite Plan):** +- **Okta** +- **Azure AD** +- **GitHub** + +### Using OAuth Providers + +```javascript +// Initiate OAuth login flow +base44.auth.loginWithProvider('google'); + +// Return to specific page after authentication +base44.auth.loginWithProvider('microsoft', '/dashboard'); + +// Supported values: 'google', 'microsoft', 'facebook' +``` + +--- + +## Environment Availability + +| Environment | Availability | Notes | +|------------|--------------|-------| +| **Frontend** | ✅ Yes | All methods available | +| **Backend Functions** | ✅ Yes | Use `createClientFromRequest(req)` for authenticated client | +| **Service Role** | ❌ No | Auth methods not available in service role context | + +### Frontend Usage +```javascript +// Standard browser usage +const user = await base44.auth.me(); +``` + +### Backend Functions Usage +```javascript +import { createClientFromRequest } from "@base44/sdk"; + +Deno.serve(async (req) => { + const base44 = createClientFromRequest(req); + + // Get user from request context + const user = await base44.auth.me(); + + // User operations here... + return Response.json({ user }); +}); +``` + +--- + +## App Visibility + +Control who can access your app in the app settings: + +### Public Apps +- No login required for basic access +- Users can view public content without authentication +- Authenticated users get additional features/data + +### Private Apps +- Login required to access any content +- Unauthenticated users are automatically redirected to login +- All content is protected by default + +--- + +## Limitations + +### Authentication UI Options +- **Recommended:** Build custom login/signup UI using `loginViaEmailPassword()` and `loginWithProvider()` for full control over user experience and branding +- **Alternative:** `redirectToLogin()` uses Base44's hosted authentication pages with limited customization + +### Hosted Login (via redirectToLogin) +- `redirectToLogin()` shows both login and signup options on the same page +- No separate `redirectToSignup()` method +- Users can switch between login/signup on the hosted page +- ⚠️ **Note:** Prefer building custom login UI for better user experience + +### Password Requirements +- Minimum length and complexity requirements enforced +- Requirements not exposed via API +- Validation errors returned when requirements not met + +### Rate Limiting +- OTP requests are rate-limited to prevent abuse +- Password reset requests are rate-limited +- Login attempts may be rate-limited with Turnstile protection + +### Token Management +- JWTs are automatically stored in localStorage by default +- Token expiration and refresh not exposed in API +- Call `me()` or `isAuthenticated()` to verify token validity + +--- + +## Best Practices + +### 1. Always Handle Errors +```javascript +try { + await base44.auth.loginViaEmailPassword(email, password); +} catch (error) { + // Always handle authentication errors + console.error('Login failed:', error.message); +} +``` + +### 2. Verify Authentication Before Protected Actions +```javascript +const user = await requireAuth(); +if (!user) return; // Will redirect to login +// Proceed with authenticated action +``` + +### 3. Use Type Safety with TypeScript +```typescript +import type { User, LoginResponse } from '@base44/sdk'; + +const user: User = await base44.auth.me(); +const response: LoginResponse = await base44.auth.loginViaEmailPassword(email, password); +``` + +### 4. Don't Hardcode Credentials +```javascript +// ❌ BAD +base44.auth.setToken("hardcoded-token-here"); + +// ✅ GOOD +const token = await secureAuthFlow(); +base44.auth.setToken(token); +``` + +### 5. Provide User Feedback +```javascript +try { + await base44.auth.register(params); + showSuccessMessage('Registration successful! Check your email for verification code.'); +} catch (error) { + showErrorMessage('Registration failed: ' + error.message); +} +``` + +### 6. Handle Token Expiration Gracefully +```javascript +try { + const user = await base44.auth.me(); +} catch (error) { + if (error.status === 401) { + // Clear local state and navigate to login + localStorage.clear(); + navigate('/login'); + } +} +``` + +### 7. Build Custom Login UI (Recommended) +```javascript +// ✅ RECOMMENDED - Custom login form with direct methods +const handleLogin = async (email, password) => { + try { + const { user } = await base44.auth.loginViaEmailPassword(email, password); + navigate('/dashboard'); + } catch (error) { + setError(error.message); + } +}; + +const handleGoogleLogin = () => { + base44.auth.loginWithProvider('google', '/dashboard'); +}; + +// ❌ AVOID - Redirecting to hosted login page +// base44.auth.redirectToLogin(window.location.href); +``` diff --git a/evals/fixtures/skill-check/skills/base44-sdk/references/base44-agents.md b/evals/fixtures/skill-check/skills/base44-sdk/references/base44-agents.md new file mode 100644 index 0000000..ee55dee --- /dev/null +++ b/evals/fixtures/skill-check/skills/base44-sdk/references/base44-agents.md @@ -0,0 +1,364 @@ +# Agents Module + +AI agent conversations and messages via `base44.agents`. + +> **Note:** This module requires a logged-in user. All agent methods work in the context of the authenticated user. + +## Contents +- [Concepts](#concepts) +- [Methods](#methods) +- [Examples](#examples) (Create, Get Conversations, List, Subscribe, Send Message, WhatsApp) +- [Message Structure](#message-structure) +- [Conversation Structure](#conversation-structure) +- [Common Patterns](#common-patterns) + +## Concepts + +- **Conversation**: A dialogue between user and an AI agent. Has unique ID, agent name, user reference, and metadata. +- **Message**: Single message in a conversation. Has role (`user`, `assistant`, `system`), content, timestamps, and optional metadata. + +## Methods + +| Method | Signature | Description | +|--------|-----------|-------------| +| `createConversation(params)` | `Promise` | Create a new conversation with an agent | +| `getConversations()` | `Promise` | Get all user's conversations | +| `getConversation(id)` | `Promise` | Get conversation with messages | +| `listConversations(filterParams)` | `Promise` | Filter/sort/paginate conversations | +| `subscribeToConversation(id, onUpdate?)` | `() => void` | Real-time updates via WebSocket (returns unsubscribe function) | +| `addMessage(conversation, message)` | `Promise` | Send a message | +| `getWhatsAppConnectURL(agentName)` | `string` | Get WhatsApp connection URL for agent | + +## Examples + +### Create Conversation + +```javascript +const conversation = await base44.agents.createConversation({ + agent_name: "support-agent", + metadata: { + order_id: "ORD-123", + category: "billing" + } +}); + +console.log(conversation.id); +``` + +### Get All Conversations + +```javascript +const conversations = await base44.agents.getConversations(); + +conversations.forEach(conv => { + console.log(conv.id, conv.agent_name, conv.created_date); +}); +``` + +### Get Single Conversation (with messages) + +```javascript +const conversation = await base44.agents.getConversation("conv-id-123"); + +console.log(conversation.messages); +``` + +### List with Filters + +```javascript +// Using filterParams object with q, sort, limit, skip, fields +const recent = await base44.agents.listConversations({ + q: { agent_name: "support-agent" }, + sort: "-created_date", + limit: 10, + skip: 0 +}); + +// Filter by metadata +const highPriority = await base44.agents.listConversations({ + q: { + agent_name: "support-agent", + "metadata.priority": "high" + }, + sort: "-updated_date", + limit: 20 +}); +``` + +### Subscribe to Updates (Real-time) + +```javascript +const unsubscribe = base44.agents.subscribeToConversation( + "conv-id-123", + (updatedConversation) => { + // Called when new messages arrive + console.log("New messages:", updatedConversation.messages); + } +); + +// Later: unsubscribe +unsubscribe(); +``` + +### Send a Message + +```javascript +const conversation = await base44.agents.getConversation("conv-id-123"); + +await base44.agents.addMessage(conversation, { + role: "user", + content: "What's the weather like today?" +}); +``` + +### Get WhatsApp Connection URL + +```javascript +const whatsappUrl = base44.agents.getWhatsAppConnectURL("support-agent"); +// Returns URL for users to connect with agent via WhatsApp +console.log(whatsappUrl); +``` + +## Message Structure + +```javascript +{ + role: "user" | "assistant" | "system", + content: "Message text or structured object", + created_date: "2024-01-15T10:30:00Z", + updated_date: "2024-01-15T10:30:00Z", + + // Optional fields + reasoning: { + content: "Agent's reasoning process", + timing: 1500 + }, + tool_calls: [{ + name: "search", + arguments: { query: "weather" }, + result: { ... }, + status: "success" + }], + file_urls: ["https://..."], + usage: { + prompt_tokens: 150, + completion_tokens: 50 + }, + metadata: { ... }, + custom_context: { ... } +} +``` + +## Conversation Structure + +```javascript +{ + id: "conv-id-123", + app_id: "app-id", + agent_name: "support-agent", + created_by_id: "user-id", + created_date: "2024-01-15T10:00:00Z", + updated_date: "2024-01-15T10:30:00Z", + messages: [ ... ], + metadata: { ... } +} +``` + +## Common Patterns + +### Chat Interface + +```javascript +// Load conversation +const conv = await base44.agents.getConversation(conversationId); +setMessages(conv.messages); + +// Subscribe to updates +const unsubscribe = base44.agents.subscribeToConversation(conversationId, (updated) => { + setMessages(updated.messages); +}); + +// Send message +async function sendMessage(text) { + await base44.agents.addMessage(conv, { role: "user", content: text }); +} + +// Cleanup on unmount +return () => unsubscribe(); +``` + +## Type Definitions + +### AgentConversation + +```typescript +/** An agent conversation containing messages exchanged with an AI agent. */ +interface AgentConversation { + /** Unique identifier for the conversation. */ + id: string; + /** Application ID. */ + app_id: string; + /** Name of the agent in this conversation. */ + agent_name: string; + /** ID of the user who created the conversation. */ + created_by_id: string; + /** When the conversation was created. */ + created_date: string; + /** When the conversation was last updated. */ + updated_date: string; + /** Array of messages in the conversation. */ + messages: AgentMessage[]; + /** Optional metadata associated with the conversation. */ + metadata?: Record; +} +``` + +### AgentMessage + +```typescript +/** A message in an agent conversation. */ +interface AgentMessage { + /** Unique identifier for the message. */ + id: string; + /** Role of the message sender. */ + role: "user" | "assistant" | "system"; + /** When the message was created. */ + created_date: string; + /** When the message was last updated. */ + updated_date: string; + /** Message content. */ + content?: string | Record; + /** Optional reasoning information for the message. */ + reasoning?: AgentMessageReasoning | null; + /** URLs to files attached to the message. */ + file_urls?: string[]; + /** Tool calls made by the agent. */ + tool_calls?: AgentMessageToolCall[]; + /** Token usage statistics. */ + usage?: AgentMessageUsage; + /** Whether the message is hidden from the user. */ + hidden?: boolean; + /** Custom context provided with the message. */ + custom_context?: AgentMessageCustomContext[]; + /** Model used to generate the message. */ + model?: string; + /** Checkpoint ID for the message. */ + checkpoint_id?: string; + /** Metadata about when and by whom the message was created. */ + metadata?: AgentMessageMetadata; +} +``` + +### Supporting Types + +```typescript +/** Reasoning information for an agent message. */ +interface AgentMessageReasoning { + /** When reasoning started. */ + start_date: string; + /** When reasoning ended. */ + end_date?: string; + /** Reasoning content. */ + content: string; +} + +/** A tool call made by the agent. */ +interface AgentMessageToolCall { + /** Tool call ID. */ + id: string; + /** Name of the tool called. */ + name: string; + /** Arguments passed to the tool as JSON string. */ + arguments_string: string; + /** Status of the tool call. */ + status: "running" | "success" | "error" | "stopped"; + /** Results from the tool call. */ + results?: string; +} + +/** Token usage statistics for an agent message. */ +interface AgentMessageUsage { + /** Number of tokens in the prompt. */ + prompt_tokens?: number; + /** Number of tokens in the completion. */ + completion_tokens?: number; +} + +/** Custom context provided with an agent message. */ +interface AgentMessageCustomContext { + /** Context message. */ + message: string; + /** Associated data for the context. */ + data: Record; + /** Type of context. */ + type: string; +} + +/** Metadata about when and by whom a message was created. */ +interface AgentMessageMetadata { + /** When the message was created. */ + created_date: string; + /** Email of the user who created the message. */ + created_by_email: string; + /** Full name of the user who created the message. */ + created_by_full_name: string; +} +``` + +### CreateConversationParams + +```typescript +/** Parameters for creating a new conversation. */ +interface CreateConversationParams { + /** The name of the agent to create a conversation with. */ + agent_name: string; + /** Optional metadata to attach to the conversation. */ + metadata?: Record; +} +``` + +### ModelFilterParams + +```typescript +/** Parameters for filtering, sorting, and paginating conversations. */ +interface ModelFilterParams { + /** Query object with field-value pairs for filtering. */ + q?: Record; + /** Sort parameter (e.g., "-created_date" for descending). */ + sort?: string | null; + /** Maximum number of results to return. */ + limit?: number | null; + /** Number of results to skip for pagination. */ + skip?: number | null; + /** Array of field names to include in the response. */ + fields?: string[] | null; +} +``` + +### AgentsModule + +```typescript +/** Agents module for managing AI agent conversations. */ +interface AgentsModule { + /** Gets all conversations from all agents in the app. */ + getConversations(): Promise; + + /** Gets a specific conversation by ID. */ + getConversation(conversationId: string): Promise; + + /** Lists conversations with filtering, sorting, and pagination. */ + listConversations(filterParams: ModelFilterParams): Promise; + + /** Creates a new conversation with an agent. */ + createConversation(conversation: CreateConversationParams): Promise; + + /** Adds a message to a conversation. */ + addMessage(conversation: AgentConversation, message: Partial): Promise; + + /** Subscribes to real-time updates for a conversation. Returns unsubscribe function. */ + subscribeToConversation(conversationId: string, onUpdate?: (conversation: AgentConversation) => void): () => void; + + /** Gets WhatsApp connection URL for an agent. */ + getWhatsAppConnectURL(agentName: string): string; +} +``` diff --git a/evals/fixtures/skill-check/skills/base44-sdk/references/client.md b/evals/fixtures/skill-check/skills/base44-sdk/references/client.md new file mode 100644 index 0000000..554bf2b --- /dev/null +++ b/evals/fixtures/skill-check/skills/base44-sdk/references/client.md @@ -0,0 +1,257 @@ +# Client Setup + +How to create and configure the Base44 client. + +## Contents +- [In Base44-Generated Apps](#in-base44-generated-apps) +- [In External Apps](#in-external-apps) +- [In Backend Functions](#in-backend-functions) +- [Authentication Modes](#authentication-modes) (Anonymous, User, Service Role) +- [Available Modules](#available-modules) +- [Client Methods](#client-methods) +- [Client Configuration Options](#client-configuration-options) + +## In Base44-Generated Apps + +The client is pre-configured and available as `base44`. Just use it: + +```javascript +const tasks = await base44.entities.Task.list(); +``` + +## In External Apps + +Install the SDK and create a client: + +```bash +npm install @base44/sdk +``` + +```javascript +import { createClient } from "@base44/sdk"; + +// IMPORTANT: The parameter name is 'appId' (NOT 'clientId', NOT 'id') +// IMPORTANT: onError must be nested inside 'options' object +const base44 = createClient({ + appId: "your-app-id", // Required: Use 'appId' parameter + token: "optional-user-token", // Optional: for pre-authenticated requests + options: { // Optional: configuration options + onError: (error) => { // Optional: error handler (must be in options) + console.error("Base44 error:", error); + } + } +}); +``` + +**Common Mistakes:** +- ❌ `createClient({ clientId: "..." })` - WRONG parameter name +- ❌ `createClient({ id: "..." })` - WRONG parameter name +- ❌ `createClient({ appId: "...", onError: ... })` - WRONG: onError must be in options +- ✅ `createClient({ appId: "..." })` - CORRECT parameter name +- ✅ `createClient({ appId: "...", options: { onError: ... } })` - CORRECT: onError in options + +## In Backend Functions + +Use `createClientFromRequest` to get a client with the caller's auth context: + +```javascript +import { createClientFromRequest } from "@base44/sdk"; + +Deno.serve(async (req) => { + const base44 = createClientFromRequest(req); + + // Client inherits authentication from the request + const user = await base44.auth.me(); + + return Response.json({ user }); +}); +``` + +## Authentication Modes + +| Mode | How to Get | Permissions | +|------|-----------|-------------| +| **Anonymous** | `createClient({ appId })` without token | Public data only | +| **User** | After `loginViaEmailPassword()` or via `createClientFromRequest` | User's own data | +| **Service Role** | `base44.asServiceRole.*` in backend | Full admin access | + +## Anonymous Mode + +No authentication. Can only access public resources. + +```javascript +const base44 = createClient({ appId: "your-app-id" }); + +// Only works if Task entity allows anonymous read +const publicTasks = await base44.entities.Task.list(); +``` + +## User Mode + +After user logs in, the client automatically includes their token. + +```javascript +const base44 = createClient({ appId: "your-app-id" }); + +// Login sets the token +await base44.auth.loginViaEmailPassword("user@example.com", "password"); + +// Subsequent requests are authenticated +const user = await base44.auth.me(); +const myTasks = await base44.entities.Task.list(); // filtered by permissions +``` + +## Service Role Mode + +Admin-level access. **Backend only.** + +```javascript +// Inside a backend function +Deno.serve(async (req) => { + const base44 = createClientFromRequest(req); + + // User mode - respects permissions + const myTasks = await base44.entities.Task.list(); + + // Service role - bypasses permissions + const allTasks = await base44.asServiceRole.entities.Task.list(); + const allUsers = await base44.asServiceRole.entities.User.list(); + const oauthToken = await base44.asServiceRole.connectors.getAccessToken("slack"); + + return Response.json({ myTasks, allTasks }); +}); +``` + +## Available Modules + +The client exposes these modules: + +```javascript +base44.entities // CRUD operations +base44.auth // Authentication +base44.agents // AI conversations +base44.functions // Backend function invocation +base44.integrations // Third-party services +base44.analytics // Event tracking +base44.appLogs // App usage logging +base44.users // User invitations + +// Service role only (backend) +base44.asServiceRole.entities +base44.asServiceRole.agents +base44.asServiceRole.functions +base44.asServiceRole.integrations +base44.asServiceRole.appLogs +base44.asServiceRole.connectors +``` + +## Client Methods + +The client provides these methods: + +```javascript +// Set authentication token for all subsequent requests +base44.setToken(newToken); + +// Cleanup WebSocket connections (call when done with client) +base44.cleanup(); +``` + +### setToken + +Updates the authentication token for all subsequent API requests and WebSocket connections. + +```javascript +// After receiving a token (e.g., from external auth) +base44.setToken("new-jwt-token"); +``` + +### cleanup + +Disconnects WebSocket connections. Call when you're done with the client or when the component unmounts. + +```javascript +// Cleanup on component unmount (React example) +useEffect(() => { + return () => base44.cleanup(); +}, []); +``` + +## Client Configuration Options + +```javascript +createClient({ + appId: "your-app-id", // Required: MUST use 'appId' (not 'clientId' or 'id') + token: "jwt-token", // Optional: pre-set auth token + options: { // Optional: configuration options + onError: (error) => {} // Optional: global error handler (must be in options) + } +}); +``` + +**⚠️ Critical:** +- The parameter name is `appId`, not `clientId` or `id`. Using the wrong parameter name will cause errors. +- The `onError` handler must be nested inside the `options` object, not at the top level. + +## Type Definitions + +### CreateClientConfig + +```typescript +/** Configuration for creating a Base44 client. */ +interface CreateClientConfig { + /** The Base44 app ID (required). */ + appId: string; + /** User authentication token. Used to authenticate as a specific user. */ + token?: string; + /** Service role authentication token (backend only). */ + serviceToken?: string; + /** Additional client options. */ + options?: CreateClientOptions; +} + +/** Options for creating a Base44 client. */ +interface CreateClientOptions { + /** Optional error handler called whenever an API error occurs. */ + onError?: (error: Error) => void; +} +``` + +### Base44Client + +```typescript +/** The Base44 client instance. */ +interface Base44Client { + /** Entities module for CRUD operations on your data models. */ + entities: EntitiesModule; + /** Integrations module for calling pre-built integration endpoints. */ + integrations: IntegrationsModule; + /** Auth module for user authentication and management. */ + auth: AuthModule; + /** Functions module for invoking custom backend functions. */ + functions: FunctionsModule; + /** Agents module for managing AI agent conversations. */ + agents: AgentsModule; + /** App logs module for tracking app usage. */ + appLogs: AppLogsModule; + /** Analytics module for tracking custom events. */ + analytics: AnalyticsModule; + + /** Cleanup function to disconnect WebSocket connections. */ + cleanup(): void; + + /** Sets a new authentication token for all subsequent requests. */ + setToken(newToken: string): void; + + /** Provides access to modules with elevated service role permissions (backend only). */ + readonly asServiceRole: { + entities: EntitiesModule; + integrations: IntegrationsModule; + connectors: ConnectorsModule; + functions: FunctionsModule; + agents: AgentsModule; + appLogs: AppLogsModule; + cleanup(): void; + }; +} +``` diff --git a/evals/fixtures/skill-check/skills/base44-sdk/references/connectors.md b/evals/fixtures/skill-check/skills/base44-sdk/references/connectors.md new file mode 100644 index 0000000..f3fbcb4 --- /dev/null +++ b/evals/fixtures/skill-check/skills/base44-sdk/references/connectors.md @@ -0,0 +1,113 @@ +# Connectors Module + +OAuth token management for external services. **Service role only** (backend functions). + +## Method + +```javascript +base44.asServiceRole.connectors.getAccessToken(integrationType): Promise +``` + +Returns a raw OAuth access token for the specified service. + +## Supported Services + +| Integration Type | Service | +|-----------------|---------| +| `"googlecalendar"` | Google Calendar | +| `"googledrive"` | Google Drive | +| `"slack"` | Slack | +| `"notion"` | Notion | +| `"salesforce"` | Salesforce | +| `"hubspot"` | HubSpot | +| `"linkedin"` | LinkedIn | +| `"tiktok"` | TikTok | +| `"github"` | GitHub | + +## Example Usage + +```javascript +// Backend function only +Deno.serve(async (req) => { + const base44 = createClientFromRequest(req); + + // Get OAuth token for Slack + const slackToken = await base44.asServiceRole.connectors.getAccessToken("slack"); + + // Use token directly with Slack API + const response = await fetch("https://slack.com/api/chat.postMessage", { + method: "POST", + headers: { + "Authorization": `Bearer ${slackToken}`, + "Content-Type": "application/json" + }, + body: JSON.stringify({ + channel: "#general", + text: "Hello from Base44!" + }) + }); + + return Response.json(await response.json()); +}); +``` + +## Google Calendar Example + +```javascript +Deno.serve(async (req) => { + const base44 = createClientFromRequest(req); + + const token = await base44.asServiceRole.connectors.getAccessToken("googlecalendar"); + + // List upcoming events + const response = await fetch( + "https://www.googleapis.com/calendar/v3/calendars/primary/events?" + + new URLSearchParams({ + maxResults: "10", + orderBy: "startTime", + singleEvents: "true", + timeMin: new Date().toISOString() + }), + { + headers: { "Authorization": `Bearer ${token}` } + } + ); + + const events = await response.json(); + return Response.json(events); +}); +``` + +## Setup Requirements + +1. **Builder plan** or higher +2. **Backend functions** enabled +3. **Connector configured** in Base44 dashboard (OAuth flow completed) + +## Important Notes + +- **One account per connector per app**: All users share the same connected account +- **Backend only**: `connectors` module not available in frontend code +- **Service role required**: Must use `base44.asServiceRole.connectors` +- **You handle the API calls**: Base44 only provides the token; you make the actual API requests +- **Token refresh**: Base44 handles token refresh automatically + +## Type Definitions + +```typescript +/** + * The type of external integration/connector. + * Examples: 'googlecalendar', 'slack', 'github', 'notion', etc. + */ +type ConnectorIntegrationType = string; + +/** Connectors module for managing OAuth tokens for external services. */ +interface ConnectorsModule { + /** + * Retrieves an OAuth access token for a specific external integration type. + * @param integrationType - The type of integration (e.g., 'googlecalendar', 'slack'). + * @returns Promise resolving to the access token string. + */ + getAccessToken(integrationType: ConnectorIntegrationType): Promise; +} +``` diff --git a/evals/fixtures/skill-check/skills/base44-sdk/references/entities.md b/evals/fixtures/skill-check/skills/base44-sdk/references/entities.md new file mode 100644 index 0000000..a7bdf35 --- /dev/null +++ b/evals/fixtures/skill-check/skills/base44-sdk/references/entities.md @@ -0,0 +1,253 @@ +# Entities Module + +CRUD operations on data models. Access via `base44.entities.EntityName.method()`. + +## Contents +- [Methods](#methods) +- [Examples](#examples) (Create, Bulk Create, List, Filter, Get, Update, Delete, Subscribe) +- [User Entity](#user-entity) +- [Service Role Access](#service-role-access) +- [Permissions](#permissions) + +## Methods + +| Method | Signature | Description | +|--------|-----------|-------------| +| `create(data)` | `Promise` | Create one record | +| `bulkCreate(dataArray)` | `Promise` | Create multiple records | +| `list(sort?, limit?, skip?, fields?)` | `Promise` | Get all records (paginated) | +| `filter(query, sort?, limit?, skip?, fields?)` | `Promise` | Get records matching conditions | +| `get(id)` | `Promise` | Get single record by ID | +| `update(id, data)` | `Promise` | Update record (partial update) | +| `delete(id)` | `Promise` | Delete record by ID | +| `deleteMany(query)` | `Promise` | Delete all matching records | +| `importEntities(file)` | `Promise` | Import from CSV (frontend only) | +| `subscribe(callback)` | `() => void` | Subscribe to realtime updates (returns unsubscribe function) | + +## Examples + +### Create + +```javascript +const task = await base44.entities.Task.create({ + title: "Complete documentation", + status: "pending", + dueDate: "2024-12-31" +}); +``` + +### Bulk Create + +```javascript +const tasks = await base44.entities.Task.bulkCreate([ + { title: "Task 1", status: "pending" }, + { title: "Task 2", status: "pending" } +]); +``` + +### List with Pagination + +```javascript +// Get first 10 records, sorted by created_date descending +const tasks = await base44.entities.Task.list( + "-created_date", // sort (string: prefix with - for descending) + 10, // limit + 0 // skip +); + +// Get next page +const page2 = await base44.entities.Task.list("-created_date", 10, 10); +``` + +### Filter + +```javascript +// Simple filter +const pending = await base44.entities.Task.filter({ status: "pending" }); + +// Multiple conditions +const myPending = await base44.entities.Task.filter({ + status: "pending", + assignedTo: userId +}); + +// With sort, limit, skip +const recent = await base44.entities.Task.filter( + { status: "pending" }, + "-created_date", // sort (string: prefix with - for descending) + 5, + 0 +); + +// Select specific fields +const titles = await base44.entities.Task.filter( + { status: "pending" }, + null, + null, + null, + ["id", "title"] +); +``` + +### Get by ID + +```javascript +const task = await base44.entities.Task.get("task-id-123"); +``` + +### Update + +```javascript +// Partial update - only specified fields change +await base44.entities.Task.update("task-id-123", { + status: "completed", + completedAt: new Date().toISOString() +}); +``` + +### Delete + +```javascript +// Single record +await base44.entities.Task.delete("task-id-123"); + +// Multiple records matching query +await base44.entities.Task.deleteMany({ status: "archived" }); +``` + +### Subscribe to Realtime Updates + +```javascript +// Subscribe to all changes on Task entity +const unsubscribe = base44.entities.Task.subscribe((event) => { + console.log(`Task ${event.id} was ${event.type}:`, event.data); + // event.type is "create", "update", or "delete" +}); + +// Later: unsubscribe to stop receiving updates +unsubscribe(); +``` + +**Event structure:** +```javascript +{ + type: "create" | "update" | "delete", + data: { ... }, // the entity data + id: "entity-id", // the affected entity's ID + timestamp: "2024-01-15T10:30:00Z" +} +``` + +## User Entity + +Every app has a built-in `User` entity with special rules: + +- Regular users can only read/update **their own** record +- Cannot create users via `entities.create()` - use `auth.register()` instead +- Service role has full access to all user records + +```javascript +// Get current user's record +const me = await base44.entities.User.get(currentUserId); + +// Service role: get any user +const anyUser = await base44.asServiceRole.entities.User.get(userId); +``` + +## Service Role Access + +For admin-level operations (bypass user permissions): + +```javascript +// Backend only +const allTasks = await base44.asServiceRole.entities.Task.list(); +const allUsers = await base44.asServiceRole.entities.User.list(); +``` + +## Permissions (RLS & FLS) + +Data access is controlled by **Row Level Security (RLS)** and **Field Level Security (FLS)** rules defined in entity schemas. + +1. **Authentication level**: anonymous, authenticated, or service role +2. **RLS rules**: Control which records (rows) users can create/read/update/delete +3. **FLS rules**: Control which fields users can read/write within accessible records + +Operations succeed or fail based on these rules - no partial results. + +RLS and FLS are configured in entity schema files (`base44/entities/*.jsonc`). See [entities-create.md](../../base44-cli/references/entities-create.md#row-level-security-rls) for configuration details. + +**Note:** `asServiceRole` sets the user's role to `"admin"` but does NOT bypass RLS. Your RLS rules must include admin access (e.g., `{ "user_condition": { "role": "admin" } }`) for service role operations to succeed. + +## Type Definitions + +### RealtimeEvent + +```typescript +/** Event types for realtime entity updates. */ +type RealtimeEventType = "create" | "update" | "delete"; + +/** Payload received when a realtime event occurs. */ +interface RealtimeEvent { + /** The type of change that occurred. */ + type: RealtimeEventType; + /** The entity data. */ + data: any; + /** The unique identifier of the affected entity. */ + id: string; + /** ISO 8601 timestamp of when the event occurred. */ + timestamp: string; +} + +/** Callback function invoked when a realtime event occurs. */ +type RealtimeCallback = (event: RealtimeEvent) => void; + +/** Function returned from subscribe, call it to unsubscribe. */ +type Subscription = () => void; +``` + +### EntityHandler + +```typescript +/** Entity handler providing CRUD operations for a specific entity type. */ +interface EntityHandler { + /** Lists records with optional pagination and sorting. */ + list(sort?: string, limit?: number, skip?: number, fields?: string[]): Promise; + + /** Filters records based on a query. */ + filter(query: Record, sort?: string, limit?: number, skip?: number, fields?: string[]): Promise; + + /** Gets a single record by ID. */ + get(id: string): Promise; + + /** Creates a new record. */ + create(data: Record): Promise; + + /** Updates an existing record. */ + update(id: string, data: Record): Promise; + + /** Deletes a single record by ID. */ + delete(id: string): Promise; + + /** Deletes multiple records matching a query. */ + deleteMany(query: Record): Promise; + + /** Creates multiple records in a single request. */ + bulkCreate(data: Record[]): Promise; + + /** Imports records from a file (frontend only). */ + importEntities(file: File): Promise; + + /** Subscribes to realtime updates. Returns unsubscribe function. */ + subscribe(callback: RealtimeCallback): Subscription; +} +``` + +### EntitiesModule + +```typescript +/** Entities module for managing app data. */ +interface EntitiesModule { + /** Access any entity by name dynamically. */ + [entityName: string]: EntityHandler; +} +``` diff --git a/evals/fixtures/skill-check/skills/base44-sdk/references/functions.md b/evals/fixtures/skill-check/skills/base44-sdk/references/functions.md new file mode 100644 index 0000000..8072465 --- /dev/null +++ b/evals/fixtures/skill-check/skills/base44-sdk/references/functions.md @@ -0,0 +1,216 @@ +# Functions Module + +Invoke custom backend functions via `base44.functions`. + +## Contents +- [Method](#method) +- [Invoking Functions](#invoking-functions) (Frontend, File Upload, Service Role, REST API) +- [Writing Backend Functions](#writing-backend-functions) (Basic, Service Role, Secrets, Errors) +- [Setup Requirements](#setup-requirements) +- [Authentication Modes](#authentication-modes) + +## Method + +```javascript +base44.functions.invoke(functionName, data): Promise +``` + +- `functionName`: Name of the backend function +- `data`: Object of parameters (sent as JSON, or multipart if contains File objects) +- Returns: Whatever the function returns + +## Invoking Functions + +### From Frontend + +```javascript +const result = await base44.functions.invoke("processOrder", { + orderId: "order-123", + action: "ship" +}); + +console.log(result); +``` + +### With File Upload + +```javascript +const fileInput = document.querySelector('input[type="file"]'); +const file = fileInput.files[0]; + +// Automatically uses multipart/form-data when File objects present +const result = await base44.functions.invoke("uploadDocument", { + file: file, + category: "invoices" +}); +``` + +### With Service Role (Backend) + +```javascript +// Inside another backend function +const result = await base44.asServiceRole.functions.invoke("adminTask", { + userId: "user-123" +}); +``` + +### Via REST API (curl) + +Functions can be called via HTTP POST to your app domain: + +```bash +curl -X POST "https:///functions/" \ + -H "Content-Type: application/json" \ + -d '{"key": "value"}' +``` + +## Writing Backend Functions + +Backend functions run on Deno. Must export using `Deno.serve()`. + +### Required Directory Structure + +Each function must be in its own subdirectory under `base44/functions/` with a configuration file: + +``` +base44/ + functions/ + process-order/ # kebab-case directory name + function.jsonc # required configuration + index.ts # entry point +``` + +**function.jsonc:** +```jsonc +{ + "name": "process-order", + "entry": "index.ts" +} +``` + +For complete setup and deployment instructions, see [functions-create.md](../../base44-cli/references/functions-create.md) in base44-cli. + +### Basic Structure + +```javascript +// base44/functions/process-order/index.ts +import { createClientFromRequest } from "npm:@base44/sdk"; + +Deno.serve(async (req) => { + // Get authenticated client from request + const base44 = createClientFromRequest(req); + + // Parse input + const { orderId, action } = await req.json(); + + // Your logic here + const order = await base44.entities.Orders.get(orderId); + + // Return response + return Response.json({ + success: true, + order: order + }); +}); +``` + +### With Service Role Access + +```javascript +import { createClientFromRequest } from "npm:@base44/sdk"; + +Deno.serve(async (req) => { + const base44 = createClientFromRequest(req); + + // Check user is authenticated + const user = await base44.auth.me(); + if (!user) { + return Response.json({ error: "Unauthorized" }, { status: 401 }); + } + + // Use service role for admin operations + const allOrders = await base44.asServiceRole.entities.Orders.list(); + + return Response.json({ orders: allOrders }); +}); +``` + +### Using Secrets + +```javascript +Deno.serve(async (req) => { + // Access environment variables (configured in app settings) + const apiKey = Deno.env.get("STRIPE_API_KEY"); + + const response = await fetch("https://api.stripe.com/v1/charges", { + headers: { + "Authorization": `Bearer ${apiKey}` + } + }); + + return Response.json(await response.json()); +}); +``` + +### Error Handling + +```javascript +import { createClientFromRequest } from "npm:@base44/sdk"; + +Deno.serve(async (req) => { + try { + const base44 = createClientFromRequest(req); + const { orderId } = await req.json(); + + const order = await base44.entities.Orders.get(orderId); + if (!order) { + return Response.json( + { error: "Order not found" }, + { status: 404 } + ); + } + + return Response.json({ order }); + + } catch (error) { + return Response.json( + { error: error.message }, + { status: 500 } + ); + } +}); +``` + +## Setup Requirements + +1. Enable Backend Functions in app settings (requires appropriate plan) +2. Create function files in `/functions` folder +3. Configure secrets via app dashboard for API keys + +## Authentication Modes + +| Mode | Context | Permissions | +|------|---------|-------------| +| User | `base44.functions.invoke()` | Runs under calling user's permissions | +| Service Role | `base44.asServiceRole.functions.invoke()` | Admin-level access | + +Inside the function, use `createClientFromRequest(req)` to get a client that inherits the caller's auth context. + +## Type Definitions + +```typescript +/** Functions module for invoking custom backend functions. */ +interface FunctionsModule { + /** + * Invokes a custom backend function by name. + * + * If any parameter is a File object, the request will automatically be + * sent as multipart/form-data. Otherwise, it will be sent as JSON. + * + * @param functionName - The name of the function to invoke. + * @param data - An object containing named parameters for the function. + * @returns Promise resolving to the function's response. + */ + invoke(functionName: string, data: Record): Promise; +} +``` diff --git a/evals/fixtures/skill-check/skills/base44-sdk/references/integrations.md b/evals/fixtures/skill-check/skills/base44-sdk/references/integrations.md new file mode 100644 index 0000000..f356a18 --- /dev/null +++ b/evals/fixtures/skill-check/skills/base44-sdk/references/integrations.md @@ -0,0 +1,377 @@ +# Integrations Module + +Access third-party services via `base44.integrations`. + +## Types of Integrations + +1. **Core/Built-in**: AI, email, file uploads (available by default) +2. **Catalog integrations**: Pre-built connectors from Base44 catalog +3. **Custom integrations**: Your own OpenAPI-based integrations + +## Accessing Integrations + +```javascript +// Core integrations +base44.integrations.Core.FunctionName(params) + +// Custom integrations +base44.integrations.custom.call(slug, operationId, params) +``` + +## Core Integrations + +### InvokeLLM (AI Text Generation) + +Generate text or structured JSON data using AI models. + +```javascript +// Basic prompt - returns string +const response = await base44.integrations.Core.InvokeLLM({ + prompt: "Summarize this text: ..." +}); + +// With internet context (uses Google Search, Maps, News) +const response = await base44.integrations.Core.InvokeLLM({ + prompt: "What's the current weather in NYC?", + add_context_from_internet: true +}); + +// Structured JSON response - returns object +const response = await base44.integrations.Core.InvokeLLM({ + prompt: "Analyze the sentiment of: 'Great product but slow shipping'", + response_json_schema: { + type: "object", + properties: { + sentiment: { type: "string", enum: ["positive", "negative", "mixed"] }, + score: { type: "number", description: "Score from 1-10" }, + key_points: { type: "array", items: { type: "string" } } + } + } +}); +// Returns: { sentiment: "mixed", score: 7, key_points: ["great product", "slow shipping"] } + +// With file attachments (uploaded via UploadFile) +const response = await base44.integrations.Core.InvokeLLM({ + prompt: "Describe what's in this image", + file_urls: ["https://...uploaded_image.png"] +}); +``` + +**Parameters:** +- `prompt` (string, required): The prompt text to send to the model +- `add_context_from_internet` (boolean, optional): If true, uses Google Search/Maps/News for real-time context +- `response_json_schema` (object, optional): JSON schema for structured output +- `file_urls` (string[], optional): URLs of uploaded files for context + +### GenerateImage + +Create AI-generated images from text prompts. + +```javascript +const { url } = await base44.integrations.Core.GenerateImage({ + prompt: "A serene mountain landscape with a lake" +}); +console.log(url); // https://...generated_image.png +``` + +### SendEmail + +Send emails to registered users. Every app gets this integration (no plan upgrade required). + +```javascript +await base44.integrations.Core.SendEmail({ + to: "user@example.com", + subject: "Welcome!", + body: "

Hello

Welcome to our app.

", + from_name: "My App" // optional, defaults to app name +}); +``` + +**Parameters:** +- `to` (string, required): Recipient email address +- `subject` (string, required): Email subject line +- `body` (string, required): Plain text or HTML email body +- `from_name` (string, optional): Sender name displayed to recipient + +**Limitations:** +- 1 credit per email (2 credits with custom domain) + +### UploadFile (Public) + +Upload files to public storage. + +```javascript +const fileInput = document.querySelector('input[type="file"]'); +const { file_url } = await base44.integrations.Core.UploadFile({ + file: fileInput.files[0] +}); +console.log(file_url); // https://...uploaded_file.pdf +``` + +### UploadPrivateFile + +Upload files to private storage that requires a signed URL to access. + +```javascript +const { file_uri } = await base44.integrations.Core.UploadPrivateFile({ + file: fileInput.files[0] +}); +console.log(file_uri); // "private/user123/document.pdf" + +// Create a signed URL to access the file +const { signed_url } = await base44.integrations.Core.CreateFileSignedUrl({ + file_uri, + expires_in: 3600 // URL expires in 1 hour (default: 300 seconds) +}); +console.log(signed_url); // Temporary URL to access the private file +``` + +### CreateFileSignedUrl + +Generate temporary access links for private files. + +```javascript +const { signed_url } = await base44.integrations.Core.CreateFileSignedUrl({ + file_uri: "private/user123/document.pdf", + expires_in: 7200 // 2 hours +}); +``` + +**Parameters:** +- `file_uri` (string, required): URI from UploadPrivateFile +- `expires_in` (number, optional): Expiration time in seconds (default: 300) + +### ExtractDataFromUploadedFile + +Extract structured data from uploaded files using AI. + +```javascript +// First upload the file +const { file_url } = await base44.integrations.Core.UploadFile({ file }); + +// Then extract structured data +const result = await base44.integrations.Core.ExtractDataFromUploadedFile({ + file_url, + json_schema: { + type: "object", + properties: { + invoice_number: { type: "string" }, + total_amount: { type: "number" }, + date: { type: "string" }, + vendor_name: { type: "string" } + } + } +}); +console.log(result); // { invoice_number: "INV-12345", total_amount: 1250.00, ... } +``` + +## Custom Integrations + +Custom integrations allow workspace administrators to connect any external API by importing an OpenAPI specification. Use `base44.integrations.custom.call()` to invoke them. + +### Syntax + +```javascript +const response = await base44.integrations.custom.call( + slug, // Integration identifier (set by admin) + operationId, // Endpoint in "method:path" format + params // Optional: payload, pathParams, queryParams +); +``` + +### Examples + +```javascript +// GET request with query params +const response = await base44.integrations.custom.call( + "my-crm", + "get:/contacts", + { queryParams: { limit: 10, status: "active" } } +); + +// POST request with body +const response = await base44.integrations.custom.call( + "my-crm", + "post:/contacts", + { payload: { name: "John Doe", email: "john@example.com" } } +); + +// Request with path parameters +const response = await base44.integrations.custom.call( + "github", + "post:/repos/{owner}/{repo}/issues", + { + pathParams: { owner: "myorg", repo: "myrepo" }, + payload: { title: "Bug report", body: "Something is broken" } + } +); +``` + +### Response Structure + +```javascript +{ + success: true, // Whether external API returned 2xx + status_code: 200, // HTTP status code + data: { ... } // Response from external API +} +``` + +## Requirements + +- **Core integrations**: Available on all plans +- **Catalog/Custom integrations**: Require Builder plan or higher + +## Type Definitions + +### Core Integration Parameters + +```typescript +/** Parameters for the InvokeLLM function. */ +interface InvokeLLMParams { + /** The prompt text to send to the model. */ + prompt: string; + /** If true, uses Google Search/Maps/News for real-time context. */ + add_context_from_internet?: boolean; + /** JSON schema for structured output. If provided, returns object instead of string. */ + response_json_schema?: object; + /** File URLs (from UploadFile) to provide as context. */ + file_urls?: string[]; +} + +/** Parameters for the GenerateImage function. */ +interface GenerateImageParams { + /** Description of the image to generate. */ + prompt: string; +} + +/** Result from GenerateImage. */ +interface GenerateImageResult { + /** URL of the generated image. */ + url: string; +} + +/** Parameters for the UploadFile function. */ +interface UploadFileParams { + /** The file object to upload. */ + file: File; +} + +/** Result from UploadFile. */ +interface UploadFileResult { + /** URL of the uploaded file. */ + file_url: string; +} + +/** Parameters for the SendEmail function. */ +interface SendEmailParams { + /** Recipient email address. */ + to: string; + /** Email subject line. */ + subject: string; + /** Plain text or HTML email body. */ + body: string; + /** Sender name (defaults to app name). */ + from_name?: string; +} + +/** Parameters for ExtractDataFromUploadedFile. */ +interface ExtractDataFromUploadedFileParams { + /** URL of the uploaded file. */ + file_url: string; + /** JSON schema defining fields to extract. */ + json_schema: object; +} + +/** Parameters for UploadPrivateFile. */ +interface UploadPrivateFileParams { + /** The file object to upload. */ + file: File; +} + +/** Result from UploadPrivateFile. */ +interface UploadPrivateFileResult { + /** URI of the private file (used for signed URLs). */ + file_uri: string; +} + +/** Parameters for CreateFileSignedUrl. */ +interface CreateFileSignedUrlParams { + /** URI from UploadPrivateFile. */ + file_uri: string; + /** Expiration time in seconds (default: 300). */ + expires_in?: number; +} + +/** Result from CreateFileSignedUrl. */ +interface CreateFileSignedUrlResult { + /** Temporary signed URL to access the file. */ + signed_url: string; +} +``` + +### CoreIntegrations + +```typescript +/** Core package containing built-in Base44 integration functions. */ +interface CoreIntegrations { + InvokeLLM(params: InvokeLLMParams): Promise; + GenerateImage(params: GenerateImageParams): Promise; + UploadFile(params: UploadFileParams): Promise; + SendEmail(params: SendEmailParams): Promise; + ExtractDataFromUploadedFile(params: ExtractDataFromUploadedFileParams): Promise; + UploadPrivateFile(params: UploadPrivateFileParams): Promise; + CreateFileSignedUrl(params: CreateFileSignedUrlParams): Promise; +} +``` + +### Custom Integrations + +```typescript +/** Parameters for calling a custom integration endpoint. */ +interface CustomIntegrationCallParams { + /** Request body payload. */ + payload?: Record; + /** Path parameters to substitute in the URL. */ + pathParams?: Record; + /** Query string parameters. */ + queryParams?: Record; + /** Additional headers for this request. */ + headers?: Record; +} + +/** Response from a custom integration call. */ +interface CustomIntegrationCallResponse { + /** Whether the external API returned a 2xx status code. */ + success: boolean; + /** The HTTP status code from the external API. */ + status_code: number; + /** The response data from the external API. */ + data: any; +} + +/** Module for calling custom workspace-level API integrations. */ +interface CustomIntegrationsModule { + /** + * Call a custom integration endpoint. + * @param slug - The integration's unique identifier. + * @param operationId - The operation ID from the OpenAPI spec. + * @param params - Optional payload, pathParams, queryParams, headers. + */ + call(slug: string, operationId: string, params?: CustomIntegrationCallParams): Promise; +} +``` + +### IntegrationsModule + +```typescript +/** Integrations module for calling integration endpoints. */ +type IntegrationsModule = { + /** Core package with built-in integrations. */ + Core: CoreIntegrations; + /** Custom integrations module. */ + custom: CustomIntegrationsModule; + /** Additional integration packages (dynamic). */ + [packageName: string]: any; +}; +``` diff --git a/evals/fixtures/skill-check/skills/base44-sdk/references/users.md b/evals/fixtures/skill-check/skills/base44-sdk/references/users.md new file mode 100644 index 0000000..4205b06 --- /dev/null +++ b/evals/fixtures/skill-check/skills/base44-sdk/references/users.md @@ -0,0 +1,82 @@ +# Users Module + +Invite users to the app via `base44.users`. + +## Contents +- [Methods](#methods) +- [Examples](#examples) (Invite User) +- [Roles](#roles) +- [Notes](#notes) + +## Methods + +| Method | Signature | Description | +|--------|-----------|-------------| +| `inviteUser(user_email, role)` | `Promise` | Invite a user to the app | + +## Examples + +### Invite User + +```javascript +// Invite a user with "user" role +await base44.users.inviteUser("newuser@example.com", "user"); + +// Invite an admin +await base44.users.inviteUser("admin@example.com", "admin"); +``` + +### Invite Multiple Users + +```javascript +const usersToInvite = [ + { email: "user1@example.com", role: "user" }, + { email: "user2@example.com", role: "user" }, + { email: "manager@example.com", role: "admin" } +]; + +for (const user of usersToInvite) { + await base44.users.inviteUser(user.email, user.role); + console.log(`Invited ${user.email} as ${user.role}`); +} +``` + +## Roles + +The `role` parameter must be one of: + +| Role | Description | +|------|-------------| +| `"user"` | Standard user with default permissions | +| `"admin"` | Administrator with elevated permissions | + +**Note:** Only `"user"` and `"admin"` are valid role values. An error will be thrown if you pass any other value. + +## Notes + +- **Email invitation**: The invited user receives an email with a link to join the app +- **Duplicate handling**: Inviting an existing user will re-send the invitation +- **Also available in auth**: `base44.auth.inviteUser()` provides the same functionality +- **Role validation**: Only `"user"` or `"admin"` are accepted + +```javascript +// These are equivalent: +await base44.users.inviteUser("newuser@example.com", "user"); +await base44.auth.inviteUser("newuser@example.com", "user"); +``` + +## Type Definitions + +```typescript +/** Users module for inviting users to the app. */ +interface UsersModule { + /** + * Invite a user to the application. + * @param user_email - User's email address. + * @param role - User's role ('user' or 'admin'). + * @returns Promise resolving when the invitation is sent. + * @throws Error if role is not 'user' or 'admin'. + */ + inviteUser(user_email: string, role: "user" | "admin"): Promise; +} +``` diff --git a/evals/package-lock.json b/evals/package-lock.json new file mode 100644 index 0000000..eebd3cd --- /dev/null +++ b/evals/package-lock.json @@ -0,0 +1,891 @@ +{ + "name": "skills-evals", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "skills-evals", + "version": "1.0.0", + "dependencies": { + "execa": "^9.5.2", + "zod": "^3.24.2" + }, + "devDependencies": { + "@types/node": "^22.13.1", + "tsx": "^4.19.2", + "typescript": "^5.7.3" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.2.tgz", + "integrity": "sha512-GZMB+a0mOMZs4MpDbj8RJp4cw+w1WV5NYD6xzgvzUJ5Ek2jerwfO2eADyI6ExDSUED+1X8aMbegahsJi+8mgpw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.2.tgz", + "integrity": "sha512-DVNI8jlPa7Ujbr1yjU2PfUSRtAUZPG9I1RwW4F4xFB1Imiu2on0ADiI/c3td+KmDtVKNbi+nffGDQMfcIMkwIA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.2.tgz", + "integrity": "sha512-pvz8ZZ7ot/RBphf8fv60ljmaoydPU12VuXHImtAs0XhLLw+EXBi2BLe3OYSBslR4rryHvweW5gmkKFwTiFy6KA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.2.tgz", + "integrity": "sha512-z8Ank4Byh4TJJOh4wpz8g2vDy75zFL0TlZlkUkEwYXuPSgX8yzep596n6mT7905kA9uHZsf/o2OJZubl2l3M7A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.2.tgz", + "integrity": "sha512-davCD2Zc80nzDVRwXTcQP/28fiJbcOwvdolL0sOiOsbwBa72kegmVU0Wrh1MYrbuCL98Omp5dVhQFWRKR2ZAlg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.2.tgz", + "integrity": "sha512-ZxtijOmlQCBWGwbVmwOF/UCzuGIbUkqB1faQRf5akQmxRJ1ujusWsb3CVfk/9iZKr2L5SMU5wPBi1UWbvL+VQA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.2.tgz", + "integrity": "sha512-lS/9CN+rgqQ9czogxlMcBMGd+l8Q3Nj1MFQwBZJyoEKI50XGxwuzznYdwcav6lpOGv5BqaZXqvBSiB/kJ5op+g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.2.tgz", + "integrity": "sha512-tAfqtNYb4YgPnJlEFu4c212HYjQWSO/w/h/lQaBK7RbwGIkBOuNKQI9tqWzx7Wtp7bTPaGC6MJvWI608P3wXYA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.2.tgz", + "integrity": "sha512-vWfq4GaIMP9AIe4yj1ZUW18RDhx6EPQKjwe7n8BbIecFtCQG4CfHGaHuh7fdfq+y3LIA2vGS/o9ZBGVxIDi9hw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.2.tgz", + "integrity": "sha512-hYxN8pr66NsCCiRFkHUAsxylNOcAQaxSSkHMMjcpx0si13t1LHFphxJZUiGwojB1a/Hd5OiPIqDdXONia6bhTw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.2.tgz", + "integrity": "sha512-MJt5BRRSScPDwG2hLelYhAAKh9imjHK5+NE/tvnRLbIqUWa+0E9N4WNMjmp/kXXPHZGqPLxggwVhz7QP8CTR8w==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.2.tgz", + "integrity": "sha512-lugyF1atnAT463aO6KPshVCJK5NgRnU4yb3FUumyVz+cGvZbontBgzeGFO1nF+dPueHD367a2ZXe1NtUkAjOtg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.2.tgz", + "integrity": "sha512-nlP2I6ArEBewvJ2gjrrkESEZkB5mIoaTswuqNFRv/WYd+ATtUpe9Y09RnJvgvdag7he0OWgEZWhviS1OTOKixw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.2.tgz", + "integrity": "sha512-C92gnpey7tUQONqg1n6dKVbx3vphKtTHJaNG2Ok9lGwbZil6DrfyecMsp9CrmXGQJmZ7iiVXvvZH6Ml5hL6XdQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.2.tgz", + "integrity": "sha512-B5BOmojNtUyN8AXlK0QJyvjEZkWwy/FKvakkTDCziX95AowLZKR6aCDhG7LeF7uMCXEJqwa8Bejz5LTPYm8AvA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.2.tgz", + "integrity": "sha512-p4bm9+wsPwup5Z8f4EpfN63qNagQ47Ua2znaqGH6bqLlmJ4bx97Y9JdqxgGZ6Y8xVTixUnEkoKSHcpRlDnNr5w==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.2.tgz", + "integrity": "sha512-uwp2Tip5aPmH+NRUwTcfLb+W32WXjpFejTIOWZFw/v7/KnpCDKG66u4DLcurQpiYTiYwQ9B7KOeMJvLCu/OvbA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.2.tgz", + "integrity": "sha512-Kj6DiBlwXrPsCRDeRvGAUb/LNrBASrfqAIok+xB0LxK8CHqxZ037viF13ugfsIpePH93mX7xfJp97cyDuTZ3cw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.2.tgz", + "integrity": "sha512-HwGDZ0VLVBY3Y+Nw0JexZy9o/nUAWq9MlV7cahpaXKW6TOzfVno3y3/M8Ga8u8Yr7GldLOov27xiCnqRZf0tCA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.2.tgz", + "integrity": "sha512-DNIHH2BPQ5551A7oSHD0CKbwIA/Ox7+78/AWkbS5QoRzaqlev2uFayfSxq68EkonB+IKjiuxBFoV8ESJy8bOHA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.2.tgz", + "integrity": "sha512-/it7w9Nb7+0KFIzjalNJVR5bOzA9Vay+yIPLVHfIQYG/j+j9VTH84aNB8ExGKPU4AzfaEvN9/V4HV+F+vo8OEg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.2.tgz", + "integrity": "sha512-LRBbCmiU51IXfeXk59csuX/aSaToeG7w48nMwA6049Y4J4+VbWALAuXcs+qcD04rHDuSCSRKdmY63sruDS5qag==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.2.tgz", + "integrity": "sha512-kMtx1yqJHTmqaqHPAzKCAkDaKsffmXkPHThSfRwZGyuqyIeBvf08KSsYXl+abf5HDAPMJIPnbBfXvP2ZC2TfHg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.2.tgz", + "integrity": "sha512-Yaf78O/B3Kkh+nKABUF++bvJv5Ijoy9AN1ww904rOXZFLWVc5OLOfL56W+C8F9xn5JQZa3UX6m+IktJnIb1Jjg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.2.tgz", + "integrity": "sha512-Iuws0kxo4yusk7sw70Xa2E2imZU5HoixzxfGCdxwBdhiDgt9vX9VUCBhqcwY7/uh//78A1hMkkROMJq9l27oLQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.2.tgz", + "integrity": "sha512-sRdU18mcKf7F+YgheI/zGf5alZatMUTKj/jNS6l744f9u3WFu4v7twcUI9vu4mknF4Y9aDlblIie0IM+5xxaqQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@sec-ant/readable-stream": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@sec-ant/readable-stream/-/readable-stream-0.4.1.tgz", + "integrity": "sha512-831qok9r2t8AlxLko40y2ebgSDhenenCatLVeW/uBtnHPyhHOvG0C7TvfgecV+wHzIm5KUICgzmVpWS+IMEAeg==", + "license": "MIT" + }, + "node_modules/@sindresorhus/merge-streams": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/merge-streams/-/merge-streams-4.0.0.tgz", + "integrity": "sha512-tlqY9xq5ukxTUZBmoOp+m61cqwQD5pHJtFY3Mn8CA8ps6yghLH/Hw8UPdqg4OLmFW3IFlcXnQNmo/dh8HzXYIQ==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@types/node": { + "version": "22.19.7", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.7.tgz", + "integrity": "sha512-MciR4AKGHWl7xwxkBa6xUGxQJ4VBOmPTF7sL+iGzuahOFaO0jHCsuEfS80pan1ef4gWId1oWOweIhrDEYLuaOw==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/esbuild": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.2.tgz", + "integrity": "sha512-HyNQImnsOC7X9PMNaCIeAm4ISCQXs5a5YasTXVliKv4uuBo1dKrG0A+uQS8M5eXjVMnLg3WgXaKvprHlFJQffw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.27.2", + "@esbuild/android-arm": "0.27.2", + "@esbuild/android-arm64": "0.27.2", + "@esbuild/android-x64": "0.27.2", + "@esbuild/darwin-arm64": "0.27.2", + "@esbuild/darwin-x64": "0.27.2", + "@esbuild/freebsd-arm64": "0.27.2", + "@esbuild/freebsd-x64": "0.27.2", + "@esbuild/linux-arm": "0.27.2", + "@esbuild/linux-arm64": "0.27.2", + "@esbuild/linux-ia32": "0.27.2", + "@esbuild/linux-loong64": "0.27.2", + "@esbuild/linux-mips64el": "0.27.2", + "@esbuild/linux-ppc64": "0.27.2", + "@esbuild/linux-riscv64": "0.27.2", + "@esbuild/linux-s390x": "0.27.2", + "@esbuild/linux-x64": "0.27.2", + "@esbuild/netbsd-arm64": "0.27.2", + "@esbuild/netbsd-x64": "0.27.2", + "@esbuild/openbsd-arm64": "0.27.2", + "@esbuild/openbsd-x64": "0.27.2", + "@esbuild/openharmony-arm64": "0.27.2", + "@esbuild/sunos-x64": "0.27.2", + "@esbuild/win32-arm64": "0.27.2", + "@esbuild/win32-ia32": "0.27.2", + "@esbuild/win32-x64": "0.27.2" + } + }, + "node_modules/execa": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-9.6.1.tgz", + "integrity": "sha512-9Be3ZoN4LmYR90tUoVu2te2BsbzHfhJyfEiAVfz7N5/zv+jduIfLrV2xdQXOHbaD6KgpGdO9PRPM1Y4Q9QkPkA==", + "license": "MIT", + "dependencies": { + "@sindresorhus/merge-streams": "^4.0.0", + "cross-spawn": "^7.0.6", + "figures": "^6.1.0", + "get-stream": "^9.0.0", + "human-signals": "^8.0.1", + "is-plain-obj": "^4.1.0", + "is-stream": "^4.0.1", + "npm-run-path": "^6.0.0", + "pretty-ms": "^9.2.0", + "signal-exit": "^4.1.0", + "strip-final-newline": "^4.0.0", + "yoctocolors": "^2.1.1" + }, + "engines": { + "node": "^18.19.0 || >=20.5.0" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/figures": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-6.1.0.tgz", + "integrity": "sha512-d+l3qxjSesT4V7v2fh+QnmFnUWv9lSpjarhShNTgBOfA0ttejbQUAlHLitbjkoRiDulW0OPoQPYIGhIC8ohejg==", + "license": "MIT", + "dependencies": { + "is-unicode-supported": "^2.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/get-stream": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-9.0.1.tgz", + "integrity": "sha512-kVCxPF3vQM/N0B1PmoqVUqgHP+EeVjmZSQn+1oCRPxd2P21P2F19lIgbR3HBosbB1PUhOAoctJnfEn2GbN2eZA==", + "license": "MIT", + "dependencies": { + "@sec-ant/readable-stream": "^0.4.1", + "is-stream": "^4.0.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/get-tsconfig": { + "version": "4.13.1", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.13.1.tgz", + "integrity": "sha512-EoY1N2xCn44xU6750Sx7OjOIT59FkmstNc3X6y5xpz7D5cBtZRe/3pSlTkDJgqsOk3WwZPkWfonhhUJfttQo3w==", + "dev": true, + "license": "MIT", + "dependencies": { + "resolve-pkg-maps": "^1.0.0" + }, + "funding": { + "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" + } + }, + "node_modules/human-signals": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-8.0.1.tgz", + "integrity": "sha512-eKCa6bwnJhvxj14kZk5NCPc6Hb6BdsU9DZcOnmQKSnO1VKrfV0zCvtttPZUsBvjmNDn8rpcJfpwSYnHBjc95MQ==", + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/is-plain-obj": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz", + "integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-stream": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-4.0.1.tgz", + "integrity": "sha512-Dnz92NInDqYckGEUJv689RbRiTSEHCQ7wOVeALbkOz999YpqT46yMRIGtSNl2iCL1waAZSx40+h59NV/EwzV/A==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-unicode-supported": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-2.1.0.tgz", + "integrity": "sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "license": "ISC" + }, + "node_modules/npm-run-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-6.0.0.tgz", + "integrity": "sha512-9qny7Z9DsQU8Ou39ERsPU4OZQlSTP47ShQzuKZ6PRXpYLtIFgl/DEBYEXKlvcEa+9tHVcK8CF81Y2V72qaZhWA==", + "license": "MIT", + "dependencies": { + "path-key": "^4.0.0", + "unicorn-magic": "^0.3.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/npm-run-path/node_modules/path-key": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz", + "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parse-ms": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/parse-ms/-/parse-ms-4.0.0.tgz", + "integrity": "sha512-TXfryirbmq34y8QBwgqCVLi+8oA3oWx2eAnSn62ITyEhEYaWRlVZ2DvMM9eZbMs/RfxPu/PK/aBLyGj4IrqMHw==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/pretty-ms": { + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/pretty-ms/-/pretty-ms-9.3.0.tgz", + "integrity": "sha512-gjVS5hOP+M3wMm5nmNOucbIrqudzs9v/57bWRHQWLYklXqoXKrVfYW2W9+glfGsqtPgpiz5WwyEEB+ksXIx3gQ==", + "license": "MIT", + "dependencies": { + "parse-ms": "^4.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/resolve-pkg-maps": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", + "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/strip-final-newline": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-4.0.0.tgz", + "integrity": "sha512-aulFJcD6YK8V1G7iRB5tigAP4TsHBZZrOV8pjV++zdUwmeV8uzbY7yn6h9MswN62adStNZFuCIx4haBnRuMDaw==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/tsx": { + "version": "4.21.0", + "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.21.0.tgz", + "integrity": "sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "~0.27.0", + "get-tsconfig": "^4.7.5" + }, + "bin": { + "tsx": "dist/cli.mjs" + }, + "engines": { + "node": ">=18.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/unicorn-magic": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.3.0.tgz", + "integrity": "sha512-+QBBXBCvifc56fsbuxZQ6Sic3wqqc3WWaqxs58gvJrcOuN83HGTCwz3oS5phzU9LthRNE9VrJCFCLUgHeeFnfA==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/yoctocolors": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/yoctocolors/-/yoctocolors-2.1.2.tgz", + "integrity": "sha512-CzhO+pFNo8ajLM2d2IW/R93ipy99LWjtwblvC1RsoSUMZgyLbYFr221TnSNT7GjGdYui6P459mw9JH/g/zW2ug==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/zod": { + "version": "3.25.76", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", + "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + } + } +} diff --git a/evals/package.json b/evals/package.json new file mode 100644 index 0000000..c97c6c9 --- /dev/null +++ b/evals/package.json @@ -0,0 +1,25 @@ +{ + "name": "skills-evals", + "version": "1.0.0", + "description": "Evaluation system for skills testing", + "type": "module", + "main": "dist/index.js", + "scripts": { + "build": "tsc", + "typecheck": "tsc --noEmit", + "setup": "tsx src/setup.ts", + "eval": "npm run typecheck && npm run setup && tsx src/index.ts", + "eval:only": "tsx src/index.ts", + "eval:compare": "npm run typecheck && tsx src/compare.ts", + "test": "npm run typecheck" + }, + "dependencies": { + "execa": "^9.5.2", + "zod": "^3.24.2" + }, + "devDependencies": { + "@types/node": "^22.13.1", + "tsx": "^4.19.2", + "typescript": "^5.7.3" + } +} diff --git a/evals/src/agents/claude-code.ts b/evals/src/agents/claude-code.ts new file mode 100644 index 0000000..5d2c635 --- /dev/null +++ b/evals/src/agents/claude-code.ts @@ -0,0 +1,133 @@ +import { execa } from 'execa'; +import type { CodingAgent, CodingAgentResponse } from '../types.js'; + +// Skill markers for detecting which skills were invoked +const skillMarkers: Record = { + 'base44-cli': [ + // CLI commands + /base44\s+(entities|functions|agents)\s+(push|deploy|pull|create)/i, + /npx\s+base44/i, + /yarn\s+base44/i, + /pnpm\s+base44/i, + // File paths (more flexible patterns) + /base44\/entities\/[\w-]+\.jsonc/i, + /base44\/functions\/[\w-]+\//i, + /base44\/agents\/[\w_]+\.jsonc/i, + /`base44\/entities\//i, + /`base44\/functions\//i, + /`base44\/agents\//i, + // Config files + /function\.jsonc/i, + /\.jsonc.*entity|entity.*\.jsonc/i, + // Deno/function patterns + /Deno\.serve/i, + /createClientFromRequest/i, + /npm:@base44\/sdk/i, + // Generic mentions + /entities\s+push/i, + /functions\s+deploy/i, + /agents\s+push/i, + // Agent config patterns + /tool_configs/i, + /entity_name.*allowed_operations|allowed_operations.*entity_name/i, + /function_name.*description|"function_name"/i, + ], + 'base44-sdk': [ + // SDK imports + /@base44\/sdk/i, + /from\s+['"]@base44\/sdk['"]/i, + /import.*createClient.*from.*@base44\/sdk/i, + // Entity operations (flexible) + /base44\.entities\.\w+\.(create|list|filter|update|delete|get|subscribe)/i, + /entities\.[A-Z]\w*\.(create|list|filter|update|delete|get|subscribe)/i, + /\.entities\.\w+\.list\(\)/i, + /\.entities\.\w+\.create\(/i, + /\.entities\.\w+\.update\(/i, + // Integrations + /integrations\.Core\./i, + /SendEmail|InvokeLLM|UploadFile|GenerateImage/i, + // Auth + /base44\.auth\./i, + // Functions invoke + /base44\.functions\.invoke/i, + /functions\.invoke/i, + // Agent SDK methods + /base44\.agents\./i, + /\.agents\.createConversation/i, + /\.agents\.addMessage/i, + /\.agents\.subscribeToConversation/i, + /\.agents\.getConversation/i, + /createConversation.*agent_name|agent_name.*createConversation/i, + /addMessage.*role.*content|role.*user.*content/i, + /subscribeToConversation/i, + // Service role + /asServiceRole/i, + ], +}; + +function detectSkills(output: string): string[] { + const detectedSkills: Set = new Set(); + + for (const [skill, patterns] of Object.entries(skillMarkers)) { + for (const pattern of patterns) { + if (pattern.test(output)) { + detectedSkills.add(skill); + break; + } + } + } + + return Array.from(detectedSkills); +} + +export class ClaudeCodeAgent implements CodingAgent { + name = 'claude-code'; + verbose = false; + + async run(prompt: string, workingDir: string): Promise { + if (this.verbose) { + console.log(`[ClaudeCode] Working dir: ${workingDir}`); + console.log(`[ClaudeCode] Running: claude -p ""`); + } + + const result = await execa('claude', ['-p', prompt], { + cwd: workingDir, + timeout: 900000, // 15 minute timeout + maxBuffer: 10 * 1024 * 1024, // 10MB buffer + reject: false, + }); + + const output = result.stdout || result.stderr || ''; + + if (this.verbose) { + console.log(`[ClaudeCode] Exit code: ${result.exitCode}`); + console.log(`[ClaudeCode] Output length: ${output.length}`); + console.log(`[ClaudeCode] Output preview: ${output.substring(0, 200)}`); + } + + const skillsInvoked = detectSkills(output); + + if (result.exitCode !== 0) { + const errorMessage = result.stderr || `Process exited with code ${result.exitCode}`; + if (this.verbose) { + console.log(`[ClaudeCode] Error: ${errorMessage}`); + } + return { + output: output || `Error running Claude Code: ${errorMessage}`, + skillsInvoked, + metadata: { + exitCode: result.exitCode, + error: errorMessage, + }, + }; + } + + return { + output, + skillsInvoked, + metadata: { + exitCode: 0, + }, + }; + } +} diff --git a/evals/src/agents/interface.ts b/evals/src/agents/interface.ts new file mode 100644 index 0000000..2e119d8 --- /dev/null +++ b/evals/src/agents/interface.ts @@ -0,0 +1,27 @@ +import type { CodingAgent, CodingAgentResponse } from '../types.js'; + +export type { CodingAgent, CodingAgentResponse }; + +export function createAgent(name: string): CodingAgent { + switch (name) { + case 'mock': + return { + name: 'mock', + async run(prompt: string, workingDir: string): Promise { + const { MockAgent } = await import('./mock.js'); + const agent = new MockAgent(); + return agent.run(prompt, workingDir); + } + }; + case 'claude-code': + default: + return { + name: 'claude-code', + async run(prompt: string, workingDir: string): Promise { + const { ClaudeCodeAgent } = await import('./claude-code.js'); + const agent = new ClaudeCodeAgent(); + return agent.run(prompt, workingDir); + } + }; + } +} diff --git a/evals/src/agents/mock.ts b/evals/src/agents/mock.ts new file mode 100644 index 0000000..f7fea0d --- /dev/null +++ b/evals/src/agents/mock.ts @@ -0,0 +1,145 @@ +import type { CodingAgent, CodingAgentResponse } from '../types.js'; + +/** + * Mock agent for testing the eval infrastructure. + * Returns a predefined response based on the prompt. + */ +export class MockAgent implements CodingAgent { + name = 'mock'; + + async run(prompt: string, workingDir: string): Promise { + console.log(`[MockAgent] Running in: ${workingDir}`); + console.log(`[MockAgent] Prompt: ${prompt.substring(0, 100)}...`); + + // Simulate different responses based on prompt content + let output = ''; + const skillsInvoked: string[] = []; + + if (prompt.toLowerCase().includes('entity') || prompt.toLowerCase().includes('todo')) { + output = `I'll create the entity files for you. + +Here's the Todo entity in \`base44/entities/todo.jsonc\`: + +\`\`\`jsonc +{ + "name": "Todo", + "type": "object", + "properties": { + "title": { + "type": "string", + "description": "The todo title" + }, + "completed": { + "type": "boolean", + "default": false + } + }, + "required": ["title"] +} +\`\`\` + +To push these entities to Base44, run: +\`\`\`bash +npx base44 entities push +\`\`\` +`; + skillsInvoked.push('base44-cli'); + } else if (prompt.toLowerCase().includes('function')) { + output = `I'll create the function for you. + +Creating \`base44/functions/send-order-notification/function.jsonc\`: + +\`\`\`jsonc +{ + "name": "send-order-notification", + "description": "Sends notification emails for order status changes" +} +\`\`\` + +And \`base44/functions/send-order-notification/index.ts\`: + +\`\`\`typescript +import { createClientFromRequest } from "npm:@base44/sdk"; + +Deno.serve(async (req: Request) => { + const base44 = createClientFromRequest(req); + const { order_id, new_status } = await req.json(); + + const order = await base44.asServiceRole.entities.Order.get(order_id); + const user = await base44.asServiceRole.entities.User.get(order.user_id); + + await base44.asServiceRole.integrations.Core.SendEmail({ + to: user.email, + subject: \`Order Status Update\`, + body: \`Your order status is now: \${new_status}\` + }); + + return Response.json({ success: true }); +}); +\`\`\` + +Deploy with: +\`\`\`bash +npx base44 functions deploy +\`\`\` +`; + skillsInvoked.push('base44-cli'); + } else if (prompt.toLowerCase().includes('tasklist') || prompt.toLowerCase().includes('sdk')) { + output = `I'll implement the TaskList component using the Base44 SDK. + +\`\`\`typescript +import { useState, useEffect } from 'react'; +import { base44 } from '../lib/base44'; + +interface Task { + id: string; + title: string; + status: string; + due_date?: string; +} + +export function TaskList() { + const [tasks, setTasks] = useState([]); + + useEffect(() => { + loadTasks(); + }, []); + + async function loadTasks() { + const data = await base44.entities.Task.list(); + setTasks(data); + } + + async function toggleComplete(task: Task) { + const newStatus = task.status === 'completed' ? 'pending' : 'completed'; + await base44.entities.Task.update(task.id, { status: newStatus }); + loadTasks(); + } + + return ( +
    + {tasks.map(task => ( +
  • toggleComplete(task)}> + {task.title} - {task.status} +
  • + ))} +
+ ); +} +\`\`\` +`; + skillsInvoked.push('base44-sdk'); + } else { + output = `I can help you with that. This is a Base44 project.`; + } + + return { + output, + skillsInvoked, + metadata: { + mock: true, + workingDir, + }, + }; + } +} diff --git a/evals/src/checks/agent-config.ts b/evals/src/checks/agent-config.ts new file mode 100644 index 0000000..57d9f97 --- /dev/null +++ b/evals/src/checks/agent-config.ts @@ -0,0 +1,115 @@ +import { z } from 'zod'; +import fs from 'fs/promises'; +import path from 'path'; +import type { Check, CheckConfig, CheckResult, EvalContext } from '../types.js'; + +// Agent configuration schema based on base44-cli agent structure + +// Entity tool config +const EntityToolSchema = z.object({ + entity_name: z.string().min(1), + allowed_operations: z.array(z.enum(['read', 'create', 'update', 'delete'])).min(1), +}); + +// Function tool config +const FunctionToolSchema = z.object({ + function_name: z.string().min(1), + description: z.string().optional(), +}); + +const ToolConfigSchema = z.union([EntityToolSchema, FunctionToolSchema]); + +const AgentConfigSchema = z.object({ + name: z.string().regex(/^[a-z0-9_]+$/), + description: z.string().min(1), + instructions: z.string().min(1), + model: z.string().optional(), + tool_configs: z.array(ToolConfigSchema).optional(), + temperature: z.number().min(0).max(2).optional(), + max_tokens: z.number().positive().optional(), + whatsapp_greeting: z.string().optional(), +}); + +function extractJsonFromOutput(output: string): object | null { + // Try to find JSON in code blocks first + const codeBlockMatch = output.match(/```(?:json|jsonc)?\s*([\s\S]*?)```/); + if (codeBlockMatch) { + try { + const jsonStr = codeBlockMatch[1].replace(/\/\/.*$/gm, '').trim(); + return JSON.parse(jsonStr); + } catch { + // Continue + } + } + + // Try to find raw JSON object with agent-like properties + const jsonMatch = output.match(/\{[\s\S]*"name"[\s\S]*\}/); + if (jsonMatch) { + try { + const jsonStr = jsonMatch[0].replace(/\/\/.*$/gm, ''); + return JSON.parse(jsonStr); + } catch { + // Continue + } + } + + return null; +} + +export class AgentConfigCheck implements Check { + type = 'agent-config'; + private config: CheckConfig; + + constructor(config: CheckConfig) { + this.config = config; + } + + async run(context: EvalContext): Promise { + const { agentResponse, projectDir } = context; + const expectedValid = this.config.expectedValid ?? true; + + let agentData: object | null = null; + + if (this.config.target === 'file' && this.config.filePath) { + try { + const filePath = path.join(projectDir, this.config.filePath); + const content = await fs.readFile(filePath, 'utf-8'); + const jsonStr = content.replace(/\/\/.*$/gm, '').replace(/\/\*[\s\S]*?\*\//g, ''); + agentData = JSON.parse(jsonStr); + } catch (error) { + return { + name: this.config.description, + passed: !expectedValid, + details: `Failed to read agent file: ${error instanceof Error ? error.message : String(error)}`, + }; + } + } else { + agentData = extractJsonFromOutput(agentResponse.output); + } + + if (!agentData) { + return { + name: this.config.description, + passed: !expectedValid, + details: 'No agent configuration found in output', + }; + } + + const result = AgentConfigSchema.safeParse(agentData); + + if (result.success) { + return { + name: this.config.description, + passed: expectedValid, + details: expectedValid ? 'Valid agent configuration' : 'Expected invalid but got valid configuration', + }; + } else { + const errors = result.error.errors.map(e => `${e.path.join('.')}: ${e.message}`).join(', '); + return { + name: this.config.description, + passed: !expectedValid, + details: expectedValid ? `Invalid agent configuration: ${errors}` : 'Correctly identified as invalid', + }; + } + } +} diff --git a/evals/src/checks/command-passes.ts b/evals/src/checks/command-passes.ts new file mode 100644 index 0000000..d536176 --- /dev/null +++ b/evals/src/checks/command-passes.ts @@ -0,0 +1,53 @@ +import { execa } from 'execa'; +import type { Check, CheckConfig, CheckResult, EvalContext } from '../types.js'; + +export class CommandPassesCheck implements Check { + type = 'command-passes'; + private config: CheckConfig; + + constructor(config: CheckConfig) { + this.config = config; + } + + async run(context: EvalContext): Promise { + const { projectDir } = context; + const command = this.config.command; + + if (!command) { + return { + name: this.config.description, + passed: false, + details: 'No command specified in check config', + }; + } + + try { + const [cmd, ...args] = command.split(' '); + const result = await execa(cmd, args, { + cwd: projectDir, + timeout: 120000, // 2 minute timeout + reject: false, + }); + + if (result.exitCode === 0) { + return { + name: this.config.description, + passed: true, + details: `Command "${command}" passed`, + }; + } else { + return { + name: this.config.description, + passed: false, + details: `Command "${command}" failed with exit code ${result.exitCode}: ${result.stderr || result.stdout}`.substring(0, 500), + }; + } + } catch (error) { + return { + name: this.config.description, + passed: false, + details: `Command failed: ${error instanceof Error ? error.message : String(error)}`, + }; + } + } +} diff --git a/evals/src/checks/contains.ts b/evals/src/checks/contains.ts new file mode 100644 index 0000000..35d861f --- /dev/null +++ b/evals/src/checks/contains.ts @@ -0,0 +1,31 @@ +import type { Check, CheckConfig, CheckResult, EvalContext } from '../types.js'; + +export class ContainsCheck implements Check { + type = 'contains'; + private config: CheckConfig; + + constructor(config: CheckConfig) { + this.config = config; + } + + async run(context: EvalContext): Promise { + const { agentResponse } = context; + const searchValue = this.config.value ?? ''; + + if (!searchValue) { + return { + name: this.config.description, + passed: false, + details: 'No search value specified in check config', + }; + } + + const found = agentResponse.output.toLowerCase().includes(searchValue.toLowerCase()); + + return { + name: this.config.description, + passed: found, + details: found ? 'Found in output' : `"${searchValue}" not found in output`, + }; + } +} diff --git a/evals/src/checks/entity-config.ts b/evals/src/checks/entity-config.ts new file mode 100644 index 0000000..62880bc --- /dev/null +++ b/evals/src/checks/entity-config.ts @@ -0,0 +1,131 @@ +import { z } from 'zod'; +import fs from 'fs/promises'; +import path from 'path'; +import type { Check, CheckConfig, CheckResult, EvalContext } from '../types.js'; + +// Entity schema based on base44-cli entity structure +const PropertySchema = z.object({ + type: z.enum(['string', 'number', 'integer', 'boolean', 'array', 'object', 'binary']), + description: z.string().optional(), + format: z.string().optional(), + enum: z.array(z.any()).optional(), + default: z.any().optional(), + items: z.object({ + type: z.string(), + }).optional(), + properties: z.record(z.any()).optional(), +}); + +const RLSConditionSchema = z.object({ + field: z.string(), + operator: z.enum(['eq', 'neq', 'in', 'nin', 'exists']), + value: z.union([z.string(), z.number(), z.boolean(), z.array(z.any())]), +}).or(z.object({ + any: z.array(z.any()), +})).or(z.object({ + all: z.array(z.any()), +})); + +const RLSSchema = z.object({ + read: RLSConditionSchema.optional(), + create: RLSConditionSchema.optional(), + update: RLSConditionSchema.optional(), + delete: RLSConditionSchema.optional(), +}); + +const EntitySchema = z.object({ + name: z.string().min(1), + type: z.literal('object').optional(), + description: z.string().optional(), + properties: z.record(PropertySchema), + required: z.array(z.string()).optional(), + rls: RLSSchema.optional(), +}); + +function extractJsonFromOutput(output: string): object | null { + // Try to find JSON in code blocks first + const codeBlockMatch = output.match(/```(?:json|jsonc)?\s*([\s\S]*?)```/); + if (codeBlockMatch) { + try { + // Remove comments for JSONC + const jsonStr = codeBlockMatch[1].replace(/\/\/.*$/gm, '').trim(); + return JSON.parse(jsonStr); + } catch { + // Continue to try other methods + } + } + + // Try to find raw JSON object + const jsonMatch = output.match(/\{[\s\S]*"name"[\s\S]*"properties"[\s\S]*\}/); + if (jsonMatch) { + try { + const jsonStr = jsonMatch[0].replace(/\/\/.*$/gm, ''); + return JSON.parse(jsonStr); + } catch { + // Continue + } + } + + return null; +} + +export class EntityConfigCheck implements Check { + type = 'entity-config'; + private config: CheckConfig; + + constructor(config: CheckConfig) { + this.config = config; + } + + async run(context: EvalContext): Promise { + const { agentResponse, projectDir } = context; + const expectedValid = this.config.expectedValid ?? true; + + let entityData: object | null = null; + + if (this.config.target === 'file' && this.config.filePath) { + // Read from file + try { + const filePath = path.join(projectDir, this.config.filePath); + const content = await fs.readFile(filePath, 'utf-8'); + // Remove JSONC comments + const jsonStr = content.replace(/\/\/.*$/gm, '').replace(/\/\*[\s\S]*?\*\//g, ''); + entityData = JSON.parse(jsonStr); + } catch (error) { + return { + name: this.config.description, + passed: !expectedValid, + details: `Failed to read entity file: ${error instanceof Error ? error.message : String(error)}`, + }; + } + } else { + // Extract from output + entityData = extractJsonFromOutput(agentResponse.output); + } + + if (!entityData) { + return { + name: this.config.description, + passed: !expectedValid, + details: 'No entity configuration found in output', + }; + } + + const result = EntitySchema.safeParse(entityData); + + if (result.success) { + return { + name: this.config.description, + passed: expectedValid, + details: expectedValid ? 'Valid entity schema' : 'Expected invalid but got valid schema', + }; + } else { + const errors = result.error.errors.map(e => `${e.path.join('.')}: ${e.message}`).join(', '); + return { + name: this.config.description, + passed: !expectedValid, + details: expectedValid ? `Invalid entity schema: ${errors}` : 'Correctly identified as invalid', + }; + } + } +} diff --git a/evals/src/checks/file-content-excluded.ts b/evals/src/checks/file-content-excluded.ts new file mode 100644 index 0000000..f47f167 --- /dev/null +++ b/evals/src/checks/file-content-excluded.ts @@ -0,0 +1,56 @@ +import fs from 'fs/promises'; +import path from 'path'; +import type { Check, CheckConfig, CheckResult, EvalContext } from '../types.js'; + +export class FileContentExcludedCheck implements Check { + type = 'file-content-excluded'; + private config: CheckConfig; + + constructor(config: CheckConfig) { + this.config = config; + } + + async run(context: EvalContext): Promise { + const { projectDir } = context; + const filePath = this.config.filePath; + const pattern = this.config.pattern; + + if (!filePath) { + return { + name: this.config.description, + passed: false, + details: 'No file path specified in check config', + }; + } + + if (!pattern) { + return { + name: this.config.description, + passed: false, + details: 'No pattern specified for excluded content check', + }; + } + + const fullPath = path.join(projectDir, filePath); + + try { + const content = await fs.readFile(fullPath, 'utf-8'); + const regex = new RegExp(pattern, 'i'); + const matches = regex.test(content); + + return { + name: this.config.description, + passed: !matches, + details: matches + ? `Pattern "${pattern}" was found in ${filePath} (should be absent)` + : `Pattern "${pattern}" correctly absent from ${filePath}`, + }; + } catch (error) { + return { + name: this.config.description, + passed: false, + details: `Failed to read file: ${error instanceof Error ? error.message : String(error)}`, + }; + } + } +} diff --git a/evals/src/checks/file-content.ts b/evals/src/checks/file-content.ts new file mode 100644 index 0000000..9f8d5bb --- /dev/null +++ b/evals/src/checks/file-content.ts @@ -0,0 +1,70 @@ +import fs from 'fs/promises'; +import path from 'path'; +import type { Check, CheckConfig, CheckResult, EvalContext } from '../types.js'; + +export class FileContentCheck implements Check { + type = 'file-content'; + private config: CheckConfig; + + constructor(config: CheckConfig) { + this.config = config; + } + + async run(context: EvalContext): Promise { + const { projectDir } = context; + const filePath = this.config.filePath; + const pattern = this.config.pattern; + const value = this.config.value; + + if (!filePath) { + return { + name: this.config.description, + passed: false, + details: 'No file path specified in check config', + }; + } + + const fullPath = path.join(projectDir, filePath); + + try { + const content = await fs.readFile(fullPath, 'utf-8'); + + // Check for regex pattern + if (pattern) { + const regex = new RegExp(pattern, 'i'); + const matches = regex.test(content); + return { + name: this.config.description, + passed: matches, + details: matches + ? `Pattern "${pattern}" found in ${filePath}` + : `Pattern "${pattern}" not found in ${filePath}`, + }; + } + + // Check for substring + if (value) { + const contains = content.includes(value); + return { + name: this.config.description, + passed: contains, + details: contains + ? `"${value}" found in ${filePath}` + : `"${value}" not found in ${filePath}`, + }; + } + + return { + name: this.config.description, + passed: false, + details: 'No pattern or value specified for content check', + }; + } catch (error) { + return { + name: this.config.description, + passed: false, + details: `Failed to read file: ${error instanceof Error ? error.message : String(error)}`, + }; + } + } +} diff --git a/evals/src/checks/file-exists.ts b/evals/src/checks/file-exists.ts new file mode 100644 index 0000000..549694d --- /dev/null +++ b/evals/src/checks/file-exists.ts @@ -0,0 +1,42 @@ +import fs from 'fs/promises'; +import path from 'path'; +import type { Check, CheckConfig, CheckResult, EvalContext } from '../types.js'; + +export class FileExistsCheck implements Check { + type = 'file-exists'; + private config: CheckConfig; + + constructor(config: CheckConfig) { + this.config = config; + } + + async run(context: EvalContext): Promise { + const { projectDir } = context; + const filePath = this.config.filePath; + + if (!filePath) { + return { + name: this.config.description, + passed: false, + details: 'No file path specified in check config', + }; + } + + const fullPath = path.join(projectDir, filePath); + + try { + await fs.access(fullPath); + return { + name: this.config.description, + passed: true, + details: `File exists: ${filePath}`, + }; + } catch { + return { + name: this.config.description, + passed: false, + details: `File not found: ${filePath}`, + }; + } + } +} diff --git a/evals/src/checks/function-def.ts b/evals/src/checks/function-def.ts new file mode 100644 index 0000000..c465c6f --- /dev/null +++ b/evals/src/checks/function-def.ts @@ -0,0 +1,97 @@ +import { z } from 'zod'; +import fs from 'fs/promises'; +import path from 'path'; +import type { Check, CheckConfig, CheckResult, EvalContext } from '../types.js'; + +// Function definition schema based on base44-cli function structure +const FunctionConfigSchema = z.object({ + name: z.string().min(1), + description: z.string().optional(), + entry: z.string().optional(), + env: z.array(z.string()).optional(), + envFrom: z.array(z.string()).optional(), +}); + +function extractJsonFromOutput(output: string): object | null { + // Try to find JSON in code blocks first + const codeBlockMatch = output.match(/```(?:json|jsonc)?\s*([\s\S]*?)```/); + if (codeBlockMatch) { + try { + const jsonStr = codeBlockMatch[1].replace(/\/\/.*$/gm, '').trim(); + return JSON.parse(jsonStr); + } catch { + // Continue + } + } + + // Try to find raw JSON object with function-like properties + const jsonMatch = output.match(/\{[\s\S]*"name"[\s\S]*\}/); + if (jsonMatch) { + try { + const jsonStr = jsonMatch[0].replace(/\/\/.*$/gm, ''); + return JSON.parse(jsonStr); + } catch { + // Continue + } + } + + return null; +} + +export class FunctionDefCheck implements Check { + type = 'function-def'; + private config: CheckConfig; + + constructor(config: CheckConfig) { + this.config = config; + } + + async run(context: EvalContext): Promise { + const { agentResponse, projectDir } = context; + const expectedValid = this.config.expectedValid ?? true; + + let functionData: object | null = null; + + if (this.config.target === 'file' && this.config.filePath) { + try { + const filePath = path.join(projectDir, this.config.filePath); + const content = await fs.readFile(filePath, 'utf-8'); + const jsonStr = content.replace(/\/\/.*$/gm, '').replace(/\/\*[\s\S]*?\*\//g, ''); + functionData = JSON.parse(jsonStr); + } catch (error) { + return { + name: this.config.description, + passed: !expectedValid, + details: `Failed to read function file: ${error instanceof Error ? error.message : String(error)}`, + }; + } + } else { + functionData = extractJsonFromOutput(agentResponse.output); + } + + if (!functionData) { + return { + name: this.config.description, + passed: !expectedValid, + details: 'No function configuration found in output', + }; + } + + const result = FunctionConfigSchema.safeParse(functionData); + + if (result.success) { + return { + name: this.config.description, + passed: expectedValid, + details: expectedValid ? 'Valid function definition' : 'Expected invalid but got valid definition', + }; + } else { + const errors = result.error.errors.map(e => `${e.path.join('.')}: ${e.message}`).join(', '); + return { + name: this.config.description, + passed: !expectedValid, + details: expectedValid ? `Invalid function definition: ${errors}` : 'Correctly identified as invalid', + }; + } + } +} diff --git a/evals/src/checks/interface.ts b/evals/src/checks/interface.ts new file mode 100644 index 0000000..7def0b9 --- /dev/null +++ b/evals/src/checks/interface.ts @@ -0,0 +1,47 @@ +import type { Check, CheckConfig, CheckResult, EvalContext } from '../types.js'; + +export type { Check, CheckConfig, CheckResult, EvalContext }; + +// Registry of check implementations +const checkRegistry: Map Check> = new Map(); + +export function registerCheck(type: string, factory: (config: CheckConfig) => Check): void { + checkRegistry.set(type, factory); +} + +export function createCheck(config: CheckConfig): Check { + const factory = checkRegistry.get(config.type); + if (!factory) { + throw new Error(`Unknown check type: ${config.type}`); + } + return factory(config); +} + +export function getRegisteredCheckTypes(): string[] { + return Array.from(checkRegistry.keys()); +} + +// Initialize checks - this will be called when the module is loaded +export async function initializeChecks(): Promise { + const { EntityConfigCheck } = await import('./entity-config.js'); + const { FunctionDefCheck } = await import('./function-def.js'); + const { AgentConfigCheck } = await import('./agent-config.js'); + const { ContainsCheck } = await import('./contains.js'); + const { JsonSchemaCheck } = await import('./json-schema.js'); + const { FileExistsCheck } = await import('./file-exists.js'); + const { FileContentCheck } = await import('./file-content.js'); + const { CommandPassesCheck } = await import('./command-passes.js'); + const { ValidJsonCheck } = await import('./valid-json.js'); + const { FileContentExcludedCheck } = await import('./file-content-excluded.js'); + + registerCheck('entity-config', (config) => new EntityConfigCheck(config)); + registerCheck('function-def', (config) => new FunctionDefCheck(config)); + registerCheck('agent-config', (config) => new AgentConfigCheck(config)); + registerCheck('contains', (config) => new ContainsCheck(config)); + registerCheck('json-schema', (config) => new JsonSchemaCheck(config)); + registerCheck('file-exists', (config) => new FileExistsCheck(config)); + registerCheck('file-content', (config) => new FileContentCheck(config)); + registerCheck('file-content-excluded', (config) => new FileContentExcludedCheck(config)); + registerCheck('command-passes', (config) => new CommandPassesCheck(config)); + registerCheck('valid-json', (config) => new ValidJsonCheck(config)); +} diff --git a/evals/src/checks/json-schema.ts b/evals/src/checks/json-schema.ts new file mode 100644 index 0000000..baa453c --- /dev/null +++ b/evals/src/checks/json-schema.ts @@ -0,0 +1,133 @@ +import { z } from 'zod'; +import type { Check, CheckConfig, CheckResult, EvalContext } from '../types.js'; + +function extractJsonFromOutput(output: string): unknown | null { + // Try to find JSON in code blocks first + const codeBlockMatch = output.match(/```(?:json|jsonc)?\s*([\s\S]*?)```/); + if (codeBlockMatch) { + try { + const jsonStr = codeBlockMatch[1].replace(/\/\/.*$/gm, '').trim(); + return JSON.parse(jsonStr); + } catch { + // Continue + } + } + + // Try to find raw JSON + const jsonMatch = output.match(/\{[\s\S]*\}/); + if (jsonMatch) { + try { + const jsonStr = jsonMatch[0].replace(/\/\/.*$/gm, ''); + return JSON.parse(jsonStr); + } catch { + // Continue + } + } + + // Try array + const arrayMatch = output.match(/\[[\s\S]*\]/); + if (arrayMatch) { + try { + return JSON.parse(arrayMatch[0]); + } catch { + // Continue + } + } + + return null; +} + +// Simple JSON schema to Zod converter for basic schemas +function jsonSchemaToZod(schema: Record): z.ZodType { + const type = schema.type as string; + + switch (type) { + case 'string': + return z.string(); + case 'number': + case 'integer': + return z.number(); + case 'boolean': + return z.boolean(); + case 'array': { + const items = schema.items as Record | undefined; + if (items) { + return z.array(jsonSchemaToZod(items)); + } + return z.array(z.unknown()); + } + case 'object': { + const properties = schema.properties as Record> | undefined; + const required = schema.required as string[] | undefined; + + if (properties) { + const shape: Record = {}; + for (const [key, propSchema] of Object.entries(properties)) { + const zodType = jsonSchemaToZod(propSchema); + shape[key] = required?.includes(key) ? zodType : zodType.optional(); + } + return z.object(shape); + } + return z.record(z.unknown()); + } + default: + return z.unknown(); + } +} + +export class JsonSchemaCheck implements Check { + type = 'json-schema'; + private config: CheckConfig; + + constructor(config: CheckConfig) { + this.config = config; + } + + async run(context: EvalContext): Promise { + const { agentResponse } = context; + + if (!this.config.schema) { + return { + name: this.config.description, + passed: false, + details: 'No schema specified in check config', + }; + } + + const data = extractJsonFromOutput(agentResponse.output); + + if (data === null) { + return { + name: this.config.description, + passed: false, + details: 'No JSON found in output', + }; + } + + try { + const zodSchema = jsonSchemaToZod(this.config.schema); + const result = zodSchema.safeParse(data); + + if (result.success) { + return { + name: this.config.description, + passed: true, + details: 'Output matches schema', + }; + } else { + const errors = result.error.errors.map(e => `${e.path.join('.')}: ${e.message}`).join(', '); + return { + name: this.config.description, + passed: false, + details: `Schema validation failed: ${errors}`, + }; + } + } catch (error) { + return { + name: this.config.description, + passed: false, + details: `Schema conversion error: ${error instanceof Error ? error.message : String(error)}`, + }; + } + } +} diff --git a/evals/src/checks/valid-json.ts b/evals/src/checks/valid-json.ts new file mode 100644 index 0000000..2383e97 --- /dev/null +++ b/evals/src/checks/valid-json.ts @@ -0,0 +1,49 @@ +import fs from 'fs/promises'; +import path from 'path'; +import type { Check, CheckConfig, CheckResult, EvalContext } from '../types.js'; + +export class ValidJsonCheck implements Check { + type = 'valid-json'; + private config: CheckConfig; + + constructor(config: CheckConfig) { + this.config = config; + } + + async run(context: EvalContext): Promise { + const { projectDir } = context; + const filePath = this.config.filePath; + + if (!filePath) { + return { + name: this.config.description, + passed: false, + details: 'No file path specified in check config', + }; + } + + const fullPath = path.join(projectDir, filePath); + + try { + const content = await fs.readFile(fullPath, 'utf-8'); + // Remove JSONC comments + const jsonStr = content + .replace(/\/\/.*$/gm, '') + .replace(/\/\*[\s\S]*?\*\//g, ''); + + JSON.parse(jsonStr); + + return { + name: this.config.description, + passed: true, + details: `Valid JSON in ${filePath}`, + }; + } catch (error) { + return { + name: this.config.description, + passed: false, + details: `Invalid JSON in ${filePath}: ${error instanceof Error ? error.message : String(error)}`, + }; + } + } +} diff --git a/evals/src/compare-runner.ts b/evals/src/compare-runner.ts new file mode 100644 index 0000000..c030979 --- /dev/null +++ b/evals/src/compare-runner.ts @@ -0,0 +1,202 @@ +import path from 'path'; +import { fileURLToPath } from 'url'; +import type { + CodingAgent, + ComparisonResult, + ExperimentRunResult, + FixtureResult, +} from './types.js'; +import { discoverExperiments, filterExperiments } from './experiments.js'; +import { setupFixtures } from './setup.js'; +import { runEvals, discoverFixtures, expandFixtures } from './runner.js'; + +const __dirname = path.dirname(fileURLToPath(import.meta.url)); +const rootDir = path.join(__dirname, '..'); + +export interface CompareOptions { + name?: string; + experimentsDir?: string; + fixturesDir?: string; + verbose?: boolean; + filterFixtures?: string; + filterExperiments?: string; + concurrency?: number; +} + +/** + * Run comparison across multiple experiments. + */ +export async function runComparison( + agent: CodingAgent, + options: CompareOptions = {} +): Promise { + const { + name = 'comparison', + experimentsDir = path.join(rootDir, 'experiments'), + fixturesDir = path.join(rootDir, 'fixtures'), + verbose = false, + filterFixtures, + filterExperiments: experimentFilter, + concurrency, + } = options; + + // Discover experiments + let experiments = await discoverExperiments(experimentsDir); + + if (experiments.length === 0) { + throw new Error(`No experiments found in ${experimentsDir}`); + } + + // Apply experiment filter + if (experimentFilter) { + experiments = filterExperiments(experiments, experimentFilter); + if (experiments.length === 0) { + throw new Error(`No experiments match filter: ${experimentFilter}`); + } + } + + if (verbose) { + console.log(`Found ${experiments.length} experiments: ${experiments.map(e => e.name).join(', ')}`); + } + + // Discover fixtures and expand multi-prompt entries + let fixturesDirs = await discoverFixtures(fixturesDir); + if (filterFixtures) { + const filterLower = filterFixtures.toLowerCase(); + fixturesDirs = fixturesDirs.filter(f => f.toLowerCase().includes(filterLower)); + } + + const expandedFixtures = await expandFixtures(fixturesDirs); + + // Extract fixture names from expanded entries (prompt-level names) + const fixtureNames = expandedFixtures.map(f => f.name); + + if (verbose) { + console.log(`Found ${fixtureNames.length} fixtures`); + } + + // Initialize result structures + const experimentResults: ExperimentRunResult[] = []; + const matrix: Record> = {}; + + // Run each experiment + for (const experiment of experiments) { + if (verbose) { + console.log(`\n${'='.repeat(50)}`); + console.log(`Running experiment: ${experiment.name}`); + console.log(`Skills: ${experiment.skills.join(', ')}`); + console.log('='.repeat(50)); + } + + // Setup fixtures with this experiment's CLAUDE.md (if any) + await setupFixtures({ + fixturesDir, + experimentDir: experiment.skillsDir, + verbose, + }); + + // Run evals + const evalResult = await runEvals(agent, { + name: experiment.name, + fixturesDir, + verbose, + filter: filterFixtures, + concurrency, + }); + + // Convert to ExperimentRunResult + const expResult: ExperimentRunResult = { + experiment: experiment.name, + date: evalResult.date, + agent: evalResult.agent, + suites: evalResult.suites, + totalPassed: evalResult.totalPassed, + totalFailed: evalResult.totalFailed, + }; + experimentResults.push(expResult); + + // Build matrix entry for this experiment + matrix[experiment.name] = {}; + for (const suite of evalResult.suites) { + for (const fixture of suite.fixtures) { + matrix[experiment.name][fixture.name] = fixture.passed; + } + } + + if (verbose) { + console.log(`\nExperiment ${experiment.name}: ${evalResult.totalPassed} passed, ${evalResult.totalFailed} failed`); + } + } + + // Build summary + const summary = buildSummary(experimentResults, matrix); + + // Get all unique fixture names from results + const allFixtureNames = new Set(); + for (const exp of experimentResults) { + for (const suite of exp.suites) { + for (const fixture of suite.fixtures) { + allFixtureNames.add(fixture.name); + } + } + } + + return { + name, + date: new Date(), + agent: agent.name, + experiments: experiments.map(e => e.name), + fixtures: Array.from(allFixtureNames).sort(), + matrix, + experimentResults, + summary, + }; +} + +function buildSummary( + experimentResults: ExperimentRunResult[], + matrix: Record> +): ComparisonResult['summary'] { + const byExperiment: Record = {}; + const byFixture: Record = {}; + + // Calculate per-experiment stats + for (const result of experimentResults) { + byExperiment[result.experiment] = { + passed: result.totalPassed, + failed: result.totalFailed, + total: result.totalPassed + result.totalFailed, + }; + } + + // Calculate per-fixture stats + for (const [expName, fixtures] of Object.entries(matrix)) { + for (const [fixtureName, passed] of Object.entries(fixtures)) { + if (!byFixture[fixtureName]) { + byFixture[fixtureName] = { passedExperiments: [], failedExperiments: [] }; + } + if (passed) { + byFixture[fixtureName].passedExperiments.push(expName); + } else { + byFixture[fixtureName].failedExperiments.push(expName); + } + } + } + + // Find best experiment + let bestExperiment = ''; + let bestPassRate = -1; + for (const [expName, stats] of Object.entries(byExperiment)) { + const passRate = stats.total > 0 ? stats.passed / stats.total : 0; + if (passRate > bestPassRate) { + bestPassRate = passRate; + bestExperiment = expName; + } + } + + return { + byExperiment, + byFixture, + bestExperiment, + }; +} diff --git a/evals/src/compare.ts b/evals/src/compare.ts new file mode 100644 index 0000000..f591b5d --- /dev/null +++ b/evals/src/compare.ts @@ -0,0 +1,272 @@ +#!/usr/bin/env node + +import path from 'path'; +import { createAgent } from './agents/interface.js'; +import { runComparison } from './compare-runner.js'; +import { writeComparisonReport } from './reporters/comparison-markdown.js'; +import type { ComparisonResult } from './types.js'; + +interface CliOptions { + name: string; + agent: string; + experimentsDir: string; + fixturesDir: string; + outputDir: string; + verbose: boolean; + runs: number; + concurrency: number; + filterFixtures?: string; + filterExperiments?: string; +} + +function parseArgs(args: string[]): CliOptions { + const options: CliOptions = { + name: 'comparison', + agent: 'claude-code', + experimentsDir: 'experiments', + fixturesDir: 'fixtures', + outputDir: 'results', + verbose: false, + runs: 1, + concurrency: 5, + }; + + for (let i = 0; i < args.length; i++) { + const arg = args[i]; + + switch (arg) { + case '--name': + case '-n': + options.name = args[++i] ?? 'comparison'; + break; + case '--agent': + case '-a': + options.agent = args[++i] ?? 'claude-code'; + break; + case '--experiments': + case '-e': + options.experimentsDir = args[++i] ?? 'experiments'; + break; + case '--fixtures': + case '-f': + options.fixturesDir = args[++i] ?? 'fixtures'; + break; + case '--output': + case '-o': + options.outputDir = args[++i] ?? 'results'; + break; + case '--verbose': + case '-v': + options.verbose = true; + break; + case '--runs': + case '-r': + options.runs = parseInt(args[++i] ?? '1', 10); + break; + case '--concurrency': + case '-c': + options.concurrency = parseInt(args[++i] ?? '5', 10); + break; + case '--filter-fixtures': + options.filterFixtures = args[++i]; + break; + case '--filter-experiments': + options.filterExperiments = args[++i]; + break; + case '--help': + case '-h': + printHelp(); + process.exit(0); + } + } + + return options; +} + +function printHelp(): void { + console.log(` +Experiment Comparison Runner + +Usage: npm run eval:compare -- [options] + +Options: + -n, --name Name for this comparison run (default: "comparison") + -r, --runs Number of times to run (default: 1) + -a, --agent Agent to use (default: "claude-code") + -e, --experiments Experiments directory (default: "experiments") + -f, --fixtures Fixtures directory (default: "fixtures") + -o, --output Output directory for reports (default: "results") + -c, --concurrency Max concurrent fixtures (default: 5) + -v, --verbose Verbose output + --filter-fixtures Filter fixtures by name pattern + --filter-experiments Filter experiments by name pattern + -h, --help Show this help + +Examples: + npm run eval:compare + npm run eval:compare -- --runs 3 # Run 3 times to check consistency + npm run eval:compare -- --name "skills-test" --verbose + npm run eval:compare -- --filter-experiments baseline + npm run eval:compare -- --filter-fixtures entities +`); +} + +interface RunResult { + runNumber: number; + result: ComparisonResult; + reportPath: string; +} + +interface AggregatedStats { + experiment: string; + runs: number; + totalPassed: number; + totalFailed: number; + perfectRuns: number; // Runs with 100% pass rate + passRates: number[]; // Pass rate per run + avgPassRate: number; + consistency: string; // "3/3" format +} + +function aggregateResults(runResults: RunResult[]): Map { + const stats = new Map(); + + for (const { runNumber, result } of runResults) { + for (const [expName, expStats] of Object.entries(result.summary.byExperiment)) { + if (!stats.has(expName)) { + stats.set(expName, { + experiment: expName, + runs: 0, + totalPassed: 0, + totalFailed: 0, + perfectRuns: 0, + passRates: [], + avgPassRate: 0, + consistency: '', + }); + } + + const s = stats.get(expName)!; + s.runs++; + s.totalPassed += expStats.passed; + s.totalFailed += expStats.failed; + + const passRate = expStats.total > 0 ? (expStats.passed / expStats.total) * 100 : 0; + s.passRates.push(passRate); + + if (expStats.failed === 0) { + s.perfectRuns++; + } + } + } + + // Calculate averages and consistency + for (const s of stats.values()) { + s.avgPassRate = s.passRates.reduce((a, b) => a + b, 0) / s.passRates.length; + s.consistency = `${s.perfectRuns}/${s.runs}`; + } + + return stats; +} + +async function main(): Promise { + const options = parseArgs(process.argv.slice(2)); + + console.log(`Starting comparison: ${options.name}`); + console.log(`Agent: ${options.agent}`); + console.log(`Runs: ${options.runs}`); + console.log(`Experiments: ${options.experimentsDir}`); + console.log(`Fixtures: ${options.fixturesDir}`); + console.log(''); + + const agent = createAgent(options.agent); + const runResults: RunResult[] = []; + + for (let run = 1; run <= options.runs; run++) { + if (options.runs > 1) { + console.log(''); + console.log('#'.repeat(50)); + console.log(`# Run ${run}/${options.runs}`); + console.log('#'.repeat(50)); + } + + const result = await runComparison(agent, { + name: `${options.name}-run${run}`, + experimentsDir: options.experimentsDir, + fixturesDir: options.fixturesDir, + verbose: options.verbose, + filterFixtures: options.filterFixtures, + filterExperiments: options.filterExperiments, + concurrency: options.concurrency, + }); + + const reportPath = await writeComparisonReport(result, options.outputDir); + runResults.push({ runNumber: run, result, reportPath }); + + // Print run summary + console.log(''); + console.log(`Run ${run} Results:`); + for (const [expName, stats] of Object.entries(result.summary.byExperiment)) { + const passRate = stats.total > 0 ? Math.round((stats.passed / stats.total) * 100) : 0; + const status = stats.failed === 0 ? '✅' : '❌'; + console.log(` ${status} ${expName}: ${stats.passed}/${stats.total} (${passRate}%)`); + } + } + + // Print aggregated summary for multiple runs + if (options.runs > 1) { + const aggregated = aggregateResults(runResults); + + console.log(''); + console.log('='.repeat(60)); + console.log(`AGGREGATED RESULTS (${options.runs} runs)`); + console.log('='.repeat(60)); + console.log(''); + console.log('| Experiment | Avg Pass Rate | Consistency | Perfect Runs |'); + console.log('|------------|---------------|-------------|--------------|'); + + // Sort by consistency (perfect runs) then by avg pass rate + const sorted = Array.from(aggregated.values()).sort((a, b) => { + if (b.perfectRuns !== a.perfectRuns) return b.perfectRuns - a.perfectRuns; + return b.avgPassRate - a.avgPassRate; + }); + + for (const s of sorted) { + const avgStr = `${Math.round(s.avgPassRate)}%`; + const marker = s.perfectRuns === s.runs ? ' ⭐' : ''; + console.log(`| ${s.experiment.padEnd(10)} | ${avgStr.padEnd(13)} | ${s.consistency.padEnd(11)} | ${s.perfectRuns}/${s.runs}${marker.padEnd(10)} |`); + } + + console.log(''); + console.log('Legend: ⭐ = 100% consistency (all runs passed)'); + console.log(''); + console.log('Reports:'); + for (const { runNumber, reportPath } of runResults) { + console.log(` Run ${runNumber}: ${path.relative(process.cwd(), reportPath)}`); + } + } else { + // Single run summary + const result = runResults[0].result; + console.log(''); + console.log('='.repeat(50)); + console.log('Comparison Complete'); + console.log('='.repeat(50)); + console.log(`Experiments: ${result.experiments.length}`); + console.log(`Fixtures: ${result.fixtures.length}`); + console.log(`Best: ${result.summary.bestExperiment}`); + console.log(''); + console.log(`Report: ${path.relative(process.cwd(), runResults[0].reportPath)}`); + } + + // Exit with error code if any run had failures in best experiment + const lastResult = runResults[runResults.length - 1].result; + const bestStats = lastResult.summary.byExperiment[lastResult.summary.bestExperiment]; + if (bestStats && bestStats.failed > 0) { + process.exit(1); + } +} + +main().catch(error => { + console.error('Fatal error:', error); + process.exit(1); +}); diff --git a/evals/src/experiments.ts b/evals/src/experiments.ts new file mode 100644 index 0000000..4746098 --- /dev/null +++ b/evals/src/experiments.ts @@ -0,0 +1,87 @@ +import fs from 'fs/promises'; +import path from 'path'; +import type { Experiment } from './types.js'; + +/** + * Discover experiments from the experiments directory. + * Each subdirectory is an experiment. Experiments can contain: + * - CLAUDE.md: Override the project's CLAUDE.md + * - AGENTS.md: Place an AGENTS.md in the project root + * - skills/: Directory of skills to use instead of fixture skills + */ +export async function discoverExperiments(experimentsDir: string): Promise { + const experiments: Experiment[] = []; + + try { + const entries = await fs.readdir(experimentsDir, { withFileTypes: true }); + + for (const entry of entries) { + if (!entry.isDirectory() || entry.name.startsWith('.')) { + continue; + } + + const experimentDir = path.join(experimentsDir, entry.name); + const skills = await getExperimentContents(experimentDir); + + experiments.push({ + name: entry.name, + skillsDir: experimentDir, + skills, + }); + } + } catch (error) { + if ((error as NodeJS.ErrnoException).code === 'ENOENT') { + return []; + } + throw error; + } + + return experiments.sort((a, b) => a.name.localeCompare(b.name)); +} + +/** + * Get contents of an experiment directory (CLAUDE.md, AGENTS.md, and skill names). + */ +async function getExperimentContents(experimentDir: string): Promise { + const contents: string[] = []; + + // Check for CLAUDE.md + try { + await fs.access(path.join(experimentDir, 'CLAUDE.md')); + contents.push('CLAUDE.md'); + } catch { + // No CLAUDE.md + } + + // Check for AGENTS.md + try { + await fs.access(path.join(experimentDir, 'AGENTS.md')); + contents.push('AGENTS.md'); + } catch { + // No AGENTS.md + } + + // Check for skills directory + try { + const skillsDir = path.join(experimentDir, 'skills'); + const skillEntries = await fs.readdir(skillsDir, { withFileTypes: true }); + const skillNames = skillEntries + .filter(d => d.isDirectory() && !d.name.startsWith('.')) + .map(d => d.name); + contents.push(...skillNames); + } catch { + // No skills directory + } + + return contents; +} + +/** + * Filter experiments by name pattern. + */ +export function filterExperiments(experiments: Experiment[], pattern: string): Experiment[] { + const patternLower = pattern.toLowerCase(); + return experiments.filter(exp => + exp.name.toLowerCase().includes(patternLower) + ); +} diff --git a/evals/src/index.ts b/evals/src/index.ts new file mode 100644 index 0000000..337a49a --- /dev/null +++ b/evals/src/index.ts @@ -0,0 +1,153 @@ +#!/usr/bin/env node + +import path from 'path'; +import { createAgent } from './agents/interface.js'; +import { runEvals } from './runner.js'; +import { writeReport } from './reporters/markdown.js'; + +interface CliOptions { + name: string; + agent: string; + fixturesDir: string; + outputDir: string; + verbose: boolean; + concurrency: number; + filter?: string; + fixtures?: string[]; +} + +function parseArgs(args: string[]): CliOptions { + const options: CliOptions = { + name: 'default', + agent: 'claude-code', + fixturesDir: 'fixtures', + outputDir: 'results', + verbose: false, + concurrency: 5, + }; + + const positionalArgs: string[] = []; + + for (let i = 0; i < args.length; i++) { + const arg = args[i]; + + if (!arg.startsWith('-')) { + positionalArgs.push(arg); + continue; + } + + switch (arg) { + case '--name': + case '-n': + options.name = args[++i] ?? 'default'; + break; + case '--agent': + case '-a': + options.agent = args[++i] ?? 'claude-code'; + break; + case '--fixtures': + case '-f': + options.fixturesDir = args[++i] ?? 'fixtures'; + break; + case '--output': + case '-o': + options.outputDir = args[++i] ?? 'results'; + break; + case '--verbose': + case '-v': + options.verbose = true; + break; + case '--concurrency': + case '-c': + options.concurrency = parseInt(args[++i] ?? '5', 10); + break; + case '--filter': + options.filter = args[++i]; + break; + case '--help': + case '-h': + printHelp(); + process.exit(0); + } + } + + if (positionalArgs.length > 0) { + options.fixtures = positionalArgs; + } + + return options; +} + +function printHelp(): void { + console.log(` +Skills Eval Runner + +Usage: npm run eval -- [options] [fixture-names...] + +Arguments: + fixture-names Specific fixtures to run (by name) + +Options: + -n, --name Name for this eval run (default: "default") + -a, --agent Agent to use (default: "claude-code") + -f, --fixtures Fixtures directory (default: "fixtures") + -o, --output Output directory for reports (default: "results") + -c, --concurrency Max concurrent fixtures (default: 5) + -v, --verbose Verbose output + --filter Filter fixtures by name pattern + -h, --help Show this help + +Examples: + npm run eval + npm run eval -- --name "baseline" + npm run eval -- --name "sonnet-test" --verbose + npm run eval -- --filter entities + npm run eval -- skill-check nextjs-todo + npm run eval -- agent-chat-component --verbose +`); +} + +async function main(): Promise { + const options = parseArgs(process.argv.slice(2)); + + console.log(`Starting eval run: ${options.name}`); + console.log(`Agent: ${options.agent}`); + console.log(`Fixtures: ${options.fixturesDir}`); + if (options.fixtures) { + console.log(`Running specific fixtures: ${options.fixtures.join(', ')}`); + } + console.log(''); + + const agent = createAgent(options.agent); + + const result = await runEvals(agent, { + name: options.name, + fixturesDir: options.fixturesDir, + verbose: options.verbose, + filter: options.filter, + fixtures: options.fixtures, + concurrency: options.concurrency, + }); + + // Write report + const reportPath = await writeReport(result, options.outputDir); + + // Print summary + console.log(''); + console.log('='.repeat(50)); + console.log('Eval Run Complete'); + console.log('='.repeat(50)); + console.log(`Passed: ${result.totalPassed}`); + console.log(`Failed: ${result.totalFailed}`); + console.log(`Report: ${path.relative(process.cwd(), reportPath)}`); + + // Exit with error code if any tests failed + if (result.totalFailed > 0) { + process.exit(1); + } +} + +main().catch(error => { + console.error('Fatal error:', error); + process.exit(1); +}); diff --git a/evals/src/reporters/comparison-markdown.ts b/evals/src/reporters/comparison-markdown.ts new file mode 100644 index 0000000..6400f02 --- /dev/null +++ b/evals/src/reporters/comparison-markdown.ts @@ -0,0 +1,202 @@ +import fs from 'fs/promises'; +import path from 'path'; +import type { ComparisonResult, ExperimentRunResult, FixtureResult } from '../types.js'; + +function formatDate(date: Date): string { + return date.toISOString().replace('T', ' ').substring(0, 19); +} + +function formatDateForFilename(date: Date): string { + return date.toISOString() + .replace('T', '-') + .replace(/:/g, '') + .substring(0, 17); +} + +function escapeMarkdown(text: string): string { + return text.replace(/[|]/g, '\\|'); +} + +function generateSummaryTable(result: ComparisonResult): string { + const lines: string[] = []; + + lines.push('## Summary'); + lines.push('| Experiment | Passed | Failed | Pass Rate |'); + lines.push('|------------|--------|--------|-----------|'); + + // Sort by pass rate descending + const sortedExperiments = Object.entries(result.summary.byExperiment) + .sort((a, b) => { + const rateA = a[1].total > 0 ? a[1].passed / a[1].total : 0; + const rateB = b[1].total > 0 ? b[1].passed / b[1].total : 0; + return rateB - rateA; + }); + + for (const [expName, stats] of sortedExperiments) { + const passRate = stats.total > 0 + ? Math.round((stats.passed / stats.total) * 100) + : 0; + const isBest = expName === result.summary.bestExperiment; + const name = isBest ? `**${expName}**` : expName; + lines.push(`| ${name} | ${stats.passed} | ${stats.failed} | ${passRate}% |`); + } + + lines.push(''); + return lines.join('\n'); +} + +function generateComparisonMatrix(result: ComparisonResult): string { + const lines: string[] = []; + + lines.push('## Comparison Matrix'); + + // Header row + const headerCells = ['Fixture', ...result.experiments]; + lines.push(`| ${headerCells.join(' | ')} |`); + lines.push(`|${headerCells.map(() => '---').join('|')}|`); + + // Fixture rows + for (const fixture of result.fixtures) { + const cells = [fixture]; + for (const exp of result.experiments) { + const passed = result.matrix[exp]?.[fixture]; + cells.push(passed === undefined ? '-' : passed ? '✅' : '❌'); + } + lines.push(`| ${cells.join(' | ')} |`); + } + + lines.push(''); + return lines.join('\n'); +} + +function generateInconsistentResults(result: ComparisonResult): string { + const lines: string[] = []; + + // Find fixtures with inconsistent results (some passed, some failed) + const inconsistent: Array<{ + fixture: string; + passed: string[]; + failed: string[]; + }> = []; + + for (const [fixtureName, stats] of Object.entries(result.summary.byFixture)) { + if (stats.passedExperiments.length > 0 && stats.failedExperiments.length > 0) { + inconsistent.push({ + fixture: fixtureName, + passed: stats.passedExperiments, + failed: stats.failedExperiments, + }); + } + } + + if (inconsistent.length === 0) { + return ''; + } + + lines.push('## Inconsistent Results'); + lines.push(''); + + for (const item of inconsistent) { + lines.push(`- **${item.fixture}**: Passed in ${item.passed.join(', ')}, Failed in ${item.failed.join(', ')}`); + } + + lines.push(''); + return lines.join('\n'); +} + +function generateExperimentDetails(expResult: ExperimentRunResult): string { + const lines: string[] = []; + const total = expResult.totalPassed + expResult.totalFailed; + + lines.push(`
`); + lines.push(`${expResult.experiment} (${expResult.totalPassed}/${total})`); + lines.push(''); + + for (const suite of expResult.suites) { + lines.push(`### ${suite.name}`); + lines.push(''); + + for (const fixture of suite.fixtures) { + const status = fixture.passed ? '✅' : '❌'; + lines.push(`- ${status} **${fixture.name}**: ${escapeMarkdown(fixture.description)}`); + + // Show failed checks + if (!fixture.passed) { + const failedChecks = fixture.checks.filter(c => !c.passed); + for (const check of failedChecks) { + lines.push(` - ❌ ${escapeMarkdown(check.name)}: ${escapeMarkdown(check.details ?? '')}`); + } + if (fixture.error) { + lines.push(` - Error: ${escapeMarkdown(fixture.error)}`); + } + if (!fixture.skillCheckPassed) { + lines.push(` - Skills: expected ${fixture.expectedSkills.join(', ')}, got ${fixture.skillsInvoked.join(', ') || '(none)'}`); + } + } + } + + lines.push(''); + } + + lines.push('
'); + lines.push(''); + + return lines.join('\n'); +} + +export function generateComparisonReport(result: ComparisonResult): string { + const lines: string[] = []; + + // Header + lines.push(`# Experiment Comparison: ${result.name}`); + lines.push(`**Date**: ${formatDate(result.date)}`); + lines.push(`**Agent**: ${result.agent}`); + lines.push(`**Experiments**: ${result.experiments.length} | **Fixtures**: ${result.fixtures.length}`); + lines.push(''); + + // Summary table + lines.push(generateSummaryTable(result)); + + // Comparison matrix + lines.push(generateComparisonMatrix(result)); + + // Inconsistent results + const inconsistent = generateInconsistentResults(result); + if (inconsistent) { + lines.push(inconsistent); + } + + // Detailed results per experiment + lines.push('## Detailed Results'); + lines.push(''); + + for (const expResult of result.experimentResults) { + lines.push(generateExperimentDetails(expResult)); + } + + return lines.join('\n'); +} + +export async function writeComparisonReport( + result: ComparisonResult, + outputDir: string +): Promise { + await fs.mkdir(outputDir, { recursive: true }); + + const report = generateComparisonReport(result); + const filename = `comparison-${formatDateForFilename(result.date)}-${result.name}.md`; + const filepath = path.join(outputDir, filename); + + await fs.writeFile(filepath, report, 'utf-8'); + + // Update latest comparison symlink + const latestPath = path.join(outputDir, 'latest-comparison.md'); + try { + await fs.unlink(latestPath); + } catch { + // Ignore if doesn't exist + } + await fs.symlink(filename, latestPath); + + return filepath; +} diff --git a/evals/src/reporters/markdown.ts b/evals/src/reporters/markdown.ts new file mode 100644 index 0000000..192dd98 --- /dev/null +++ b/evals/src/reporters/markdown.ts @@ -0,0 +1,139 @@ +import fs from 'fs/promises'; +import path from 'path'; +import type { EvalRunResult, FixtureResult, SuiteResult } from '../types.js'; + +function formatDate(date: Date): string { + return date.toISOString().replace('T', ' ').substring(0, 19); +} + +function formatDateForFilename(date: Date): string { + return date.toISOString() + .replace('T', '-') + .replace(/:/g, '') + .substring(0, 17); +} + +function escapeMarkdown(text: string): string { + return text.replace(/[|]/g, '\\|'); +} + +function generateFixtureSection(fixture: FixtureResult): string { + const status = fixture.passed ? '✅' : '❌'; + const lines: string[] = []; + + lines.push(`### ${status} ${fixture.name}`); + lines.push(`**Fixture**: \`${fixture.fixtureDir}\``); + lines.push(`**Prompt**: ${escapeMarkdown(fixture.prompt)}`); + lines.push(''); + + // Skills section + const skillStatus = fixture.skillCheckPassed ? '✓' : '✗'; + if (fixture.expectedSkills.length > 0) { + lines.push(`- **Expected Skills**: ${fixture.expectedSkills.join(', ')} ${skillStatus}`); + } + if (fixture.skillsInvoked.length > 0) { + lines.push(`- **Skills Invoked**: ${fixture.skillsInvoked.join(', ')}`); + } else { + lines.push('- **Skills Invoked**: (none detected)'); + } + lines.push(''); + + // Checks table + if (fixture.checks.length > 0) { + lines.push('- **Checks**:'); + lines.push(' | Check | Status | Details |'); + lines.push(' |-------|--------|---------|'); + for (const check of fixture.checks) { + const checkStatus = check.passed ? '✅' : '❌'; + const details = escapeMarkdown(check.details ?? ''); + lines.push(` | ${escapeMarkdown(check.name)} | ${checkStatus} | ${details} |`); + } + lines.push(''); + } + + // Error if present + if (fixture.error) { + lines.push(`> **Error**: ${escapeMarkdown(fixture.error)}`); + lines.push(''); + } + + // Agent output in details + lines.push('
'); + lines.push('Agent Output'); + lines.push(''); + lines.push('```'); + lines.push(fixture.agentOutput.substring(0, 5000)); // Truncate long outputs + if (fixture.agentOutput.length > 5000) { + lines.push('... (truncated)'); + } + lines.push('```'); + lines.push('
'); + lines.push(''); + + return lines.join('\n'); +} + +function generateSuiteSection(suite: SuiteResult): string { + const lines: string[] = []; + + lines.push(`## ${suite.name} Suite`); + lines.push(''); + + for (const fixture of suite.fixtures) { + lines.push(generateFixtureSection(fixture)); + } + + return lines.join('\n'); +} + +export function generateMarkdownReport(result: EvalRunResult): string { + const lines: string[] = []; + + // Header + lines.push(`# Eval Run: ${result.name}`); + lines.push(`**Date**: ${formatDate(result.date)}`); + lines.push(`**Agent**: ${result.agent}`); + + const totalFixtures = result.totalPassed + result.totalFailed; + lines.push(`**Fixtures**: ${totalFixtures}`); + lines.push(''); + + // Summary table + lines.push('## Summary'); + lines.push('| Status | Count |'); + lines.push('|--------|-------|'); + lines.push(`| ✅ Passed | ${result.totalPassed} |`); + lines.push(`| ❌ Failed | ${result.totalFailed} |`); + lines.push(''); + + // Suite sections + for (const suite of result.suites) { + lines.push(generateSuiteSection(suite)); + } + + return lines.join('\n'); +} + +export async function writeReport( + result: EvalRunResult, + outputDir: string +): Promise { + await fs.mkdir(outputDir, { recursive: true }); + + const report = generateMarkdownReport(result); + const filename = `run-${formatDateForFilename(result.date)}-${result.name}.md`; + const filepath = path.join(outputDir, filename); + + await fs.writeFile(filepath, report, 'utf-8'); + + // Update latest symlink + const latestPath = path.join(outputDir, 'latest.md'); + try { + await fs.unlink(latestPath); + } catch { + // Ignore if doesn't exist + } + await fs.symlink(filename, latestPath); + + return filepath; +} diff --git a/evals/src/run-repeated.ts b/evals/src/run-repeated.ts new file mode 100644 index 0000000..57f1a5d --- /dev/null +++ b/evals/src/run-repeated.ts @@ -0,0 +1,288 @@ +#!/usr/bin/env node + +/** + * Run evals N times and produce a consolidated report. + * Usage: npx tsx src/run-repeated.ts [--runs N] [--verbose] [--filter pattern] [fixture-names...] + */ + +import path from 'path'; +import fs from 'fs/promises'; +import { fileURLToPath } from 'url'; +import { createAgent } from './agents/interface.js'; +import { runEvals } from './runner.js'; +import type { EvalRunResult, FixtureResult } from './types.js'; + +const __dirname = path.dirname(fileURLToPath(import.meta.url)); +const rootDir = path.join(__dirname, '..'); + +interface Options { + runs: number; + verbose: boolean; + concurrency: number; + filter?: string; + fixtures?: string[]; +} + +function parseArgs(args: string[]): Options { + const options: Options = { runs: 3, verbose: false, concurrency: 5 }; + const positional: string[] = []; + + for (let i = 0; i < args.length; i++) { + const arg = args[i]; + if (arg === '--runs' || arg === '-r') { + options.runs = parseInt(args[++i] ?? '3', 10); + } else if (arg === '--concurrency' || arg === '-c') { + options.concurrency = parseInt(args[++i] ?? '5', 10); + } else if (arg === '--verbose' || arg === '-v') { + options.verbose = true; + } else if (arg === '--filter') { + options.filter = args[++i]; + } else if (!arg.startsWith('-')) { + positional.push(arg); + } + } + + if (positional.length > 0) { + options.fixtures = positional; + } + return options; +} + +interface PromptStats { + name: string; + description: string; + runs: number; + passed: number; + failed: number; + passRate: number; + checkResults: Map; + skillCheckResults: { passed: number; total: number }; + errors: string[]; +} + +async function main() { + const options = parseArgs(process.argv.slice(2)); + const fixturesDir = path.join(rootDir, 'fixtures'); + const agent = createAgent('claude-code'); + + console.log(`=== Repeated Eval Run ===`); + console.log(`Runs per prompt: ${options.runs}`); + console.log(`Agent: claude-code`); + console.log(''); + + // Collect results across all runs + const allResults: EvalRunResult[] = []; + + for (let run = 1; run <= options.runs; run++) { + console.log(`\n${'='.repeat(60)}`); + console.log(`RUN ${run} of ${options.runs}`); + console.log('='.repeat(60)); + + const result = await runEvals(agent, { + name: `run-${run}`, + fixturesDir, + verbose: options.verbose, + filter: options.filter, + fixtures: options.fixtures, + concurrency: options.concurrency, + }); + + allResults.push(result); + + console.log(`Run ${run}: ${result.totalPassed} passed, ${result.totalFailed} failed`); + } + + // Aggregate results per prompt + const promptStats = new Map(); + + for (const result of allResults) { + for (const suite of result.suites) { + for (const fixture of suite.fixtures) { + let stats = promptStats.get(fixture.name); + if (!stats) { + stats = { + name: fixture.name, + description: fixture.description, + runs: 0, + passed: 0, + failed: 0, + passRate: 0, + checkResults: new Map(), + skillCheckResults: { passed: 0, total: 0 }, + errors: [], + }; + promptStats.set(fixture.name, stats); + } + + stats.runs++; + if (fixture.passed) { + stats.passed++; + } else { + stats.failed++; + } + + // Track skill check + stats.skillCheckResults.total++; + if (fixture.skillCheckPassed) { + stats.skillCheckResults.passed++; + } + + // Track individual checks + for (const check of fixture.checks) { + let checkStat = stats.checkResults.get(check.name); + if (!checkStat) { + checkStat = { passed: 0, total: 0 }; + stats.checkResults.set(check.name, checkStat); + } + checkStat.total++; + if (check.passed) { + checkStat.passed++; + } + } + + // Track errors + if (fixture.error) { + stats.errors.push(fixture.error); + } + } + } + } + + // Calculate pass rates + for (const stats of promptStats.values()) { + stats.passRate = stats.runs > 0 ? stats.passed / stats.runs : 0; + } + + // Generate report + const report = generateReport(options.runs, promptStats, allResults); + + // Write report + const outputDir = path.join(rootDir, 'results'); + await fs.mkdir(outputDir, { recursive: true }); + const timestamp = new Date().toISOString().replace('T', '-').replace(/:/g, '').substring(0, 17); + const reportPath = path.join(outputDir, `repeated-${timestamp}-${options.runs}runs.md`); + await fs.writeFile(reportPath, report, 'utf-8'); + + // Print summary + console.log('\n' + '='.repeat(60)); + console.log('FINAL SUMMARY'); + console.log('='.repeat(60)); + + const sorted = Array.from(promptStats.values()).sort((a, b) => a.name.localeCompare(b.name)); + const totalPrompts = sorted.length; + const perfect = sorted.filter(s => s.passRate === 1).length; + const flaky = sorted.filter(s => s.passRate > 0 && s.passRate < 1).length; + const alwaysFail = sorted.filter(s => s.passRate === 0).length; + + console.log(`Total prompts: ${totalPrompts}`); + console.log(`Always pass (${options.runs}/${options.runs}): ${perfect}`); + console.log(`Flaky (some pass): ${flaky}`); + console.log(`Always fail (0/${options.runs}): ${alwaysFail}`); + console.log(`Report: ${path.relative(process.cwd(), reportPath)}`); +} + +function generateReport( + numRuns: number, + promptStats: Map, + allResults: EvalRunResult[] +): string { + const lines: string[] = []; + const sorted = Array.from(promptStats.values()).sort((a, b) => a.name.localeCompare(b.name)); + + lines.push(`# Eval Report: ${numRuns} Runs Per Prompt`); + lines.push(`**Date**: ${new Date().toISOString().replace('T', ' ').substring(0, 19)}`); + lines.push(`**Agent**: claude-code`); + lines.push(`**Runs per prompt**: ${numRuns}`); + lines.push(`**Total prompts**: ${sorted.length}`); + lines.push(''); + + // Overall summary + const perfect = sorted.filter(s => s.passRate === 1); + const flaky = sorted.filter(s => s.passRate > 0 && s.passRate < 1); + const alwaysFail = sorted.filter(s => s.passRate === 0); + + lines.push('## Summary'); + lines.push(''); + lines.push(`| Category | Count |`); + lines.push(`|----------|-------|`); + lines.push(`| Always pass (${numRuns}/${numRuns}) | ${perfect.length} |`); + lines.push(`| Flaky (some pass) | ${flaky.length} |`); + lines.push(`| Always fail (0/${numRuns}) | ${alwaysFail.length} |`); + lines.push(`| **Total** | **${sorted.length}** |`); + lines.push(''); + + // Pass rate table + lines.push('## Pass Rates'); + lines.push(''); + lines.push('| Prompt | Pass Rate | Passed | Failed | Skill Check |'); + lines.push('|--------|-----------|--------|--------|-------------|'); + + for (const stats of sorted) { + const pct = `${Math.round(stats.passRate * 100)}%`; + const bar = stats.passRate === 1 ? '🟢' : stats.passRate === 0 ? '🔴' : '🟡'; + const skillPct = stats.skillCheckResults.total > 0 + ? `${stats.skillCheckResults.passed}/${stats.skillCheckResults.total}` + : '-'; + lines.push(`| ${bar} ${stats.name} | ${pct} | ${stats.passed}/${stats.runs} | ${stats.failed}/${stats.runs} | ${skillPct} |`); + } + lines.push(''); + + // Group by category + const categories = new Map(); + for (const stats of sorted) { + const cat = stats.name.replace(/-[^-]+$/, '').replace(/^(sdk|cli|fullstack|anti-hallucination)-?/, (_, prefix) => prefix); + // Better: group by fixture directory prefix + const prefix = stats.name.split('-').slice(0, 2).join('-'); + if (!categories.has(prefix)) { + categories.set(prefix, []); + } + categories.get(prefix)!.push(stats); + } + + // Detailed check breakdown for non-perfect prompts + const imperfect = sorted.filter(s => s.passRate < 1); + if (imperfect.length > 0) { + lines.push('## Check-Level Details (Non-Perfect Prompts)'); + lines.push(''); + + for (const stats of imperfect) { + lines.push(`### ${stats.name} (${stats.passed}/${stats.runs} passed)`); + lines.push(''); + + if (stats.checkResults.size > 0) { + lines.push('| Check | Pass Rate |'); + lines.push('|-------|-----------|'); + for (const [checkName, checkStat] of stats.checkResults) { + const checkPct = checkStat.total > 0 ? `${checkStat.passed}/${checkStat.total}` : '-'; + const checkBar = checkStat.passed === checkStat.total ? '✅' : '❌'; + lines.push(`| ${checkBar} ${checkName} | ${checkPct} |`); + } + lines.push(''); + } + + if (stats.errors.length > 0) { + lines.push('**Errors:**'); + for (const err of stats.errors) { + lines.push(`- ${err.substring(0, 200)}`); + } + lines.push(''); + } + } + } + + // Per-run results + lines.push('## Per-Run Results'); + lines.push(''); + for (let i = 0; i < allResults.length; i++) { + const r = allResults[i]; + lines.push(`- **Run ${i + 1}**: ${r.totalPassed} passed, ${r.totalFailed} failed`); + } + lines.push(''); + + return lines.join('\n'); +} + +main().catch(error => { + console.error('Fatal error:', error); + process.exit(1); +}); diff --git a/evals/src/runner.ts b/evals/src/runner.ts new file mode 100644 index 0000000..133e029 --- /dev/null +++ b/evals/src/runner.ts @@ -0,0 +1,359 @@ +import fs from 'fs/promises'; +import path from 'path'; +import os from 'os'; +import type { + CodingAgent, + EvalConfig, + ExpandedFixture, + EvalRunResult, + FixtureResult, + SuiteResult, +} from './types.js'; +import { createCheck, initializeChecks } from './checks/interface.js'; + +async function copyDir(src: string, dest: string): Promise { + await fs.mkdir(dest, { recursive: true }); + const entries = await fs.readdir(src, { withFileTypes: true }); + + for (const entry of entries) { + const srcPath = path.join(src, entry.name); + const destPath = path.join(dest, entry.name); + + if (entry.isDirectory()) { + await copyDir(srcPath, destPath); + } else if (entry.isSymbolicLink()) { + const link = await fs.readlink(srcPath); + await fs.symlink(link, destPath); + } else { + await fs.copyFile(srcPath, destPath); + } + } +} + +async function loadEvalConfig(fixtureDir: string): Promise { + const configPath = path.join(fixtureDir, 'eval.json'); + const content = await fs.readFile(configPath, 'utf-8'); + return JSON.parse(content) as EvalConfig; +} + +export async function expandFixtures(fixtureDirs: string[]): Promise { + const expanded: ExpandedFixture[] = []; + + for (const fixtureDir of fixtureDirs) { + const config = await loadEvalConfig(fixtureDir); + + if (config.prompts && config.prompts.length > 0) { + // Multi-prompt fixture + for (const promptConfig of config.prompts) { + expanded.push({ + name: promptConfig.name, + description: promptConfig.description || config.description, + fixtureDir, + prompt: promptConfig.prompt, + expectedSkills: promptConfig.expectedSkills, + checks: promptConfig.checks, + }); + } + } else if (config.prompt) { + // Single-prompt fixture (backward compat) + expanded.push({ + name: config.name, + description: config.description, + fixtureDir, + prompt: config.prompt, + expectedSkills: config.expectedSkills || [], + checks: config.checks || [], + }); + } + } + + return expanded; +} + +async function runFixture( + agent: CodingAgent, + fixture: ExpandedFixture, + verbose: boolean +): Promise { + const { fixtureDir } = fixture; + + // Create temp directory and copy fixture + const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'eval-')); + await copyDir(fixtureDir, tempDir); + + // Determine project directory (default to fixture root if no project/ subdirectory) + let projectDir = tempDir; + try { + const projectSubdir = path.join(tempDir, 'project'); + const stat = await fs.stat(projectSubdir); + if (stat.isDirectory()) { + projectDir = projectSubdir; + } + } catch { + // No project/ subdirectory, use fixture root + } + + if (verbose) { + console.log(` Running fixture: ${fixture.name}`); + console.log(` Temp dir: ${tempDir}`); + console.log(` Project dir: ${projectDir}`); + } + + try { + // Run the agent + const agentResponse = await agent.run(fixture.prompt, projectDir); + + if (verbose) { + console.log(` Skills invoked: ${agentResponse.skillsInvoked.join(', ') || '(none)'}`); + console.log(` --- Agent Output ---`); + console.log(agentResponse.output); + console.log(` --- End Output ---`); + } + + // Check skill activation + const skillCheckPassed = fixture.expectedSkills.every( + skill => agentResponse.skillsInvoked.includes(skill) + ); + + // Run all checks + const checkResults = await Promise.all( + fixture.checks.map(async checkConfig => { + const check = createCheck(checkConfig); + return check.run({ + agentResponse, + fixtureDir, + projectDir, + checkConfig, + }); + }) + ); + + // Determine overall pass/fail + const allChecksPassed = checkResults.every(r => r.passed); + const passed = skillCheckPassed && allChecksPassed; + + return { + name: fixture.name, + description: fixture.description, + fixtureDir: path.relative(process.cwd(), fixtureDir), + prompt: fixture.prompt, + expectedSkills: fixture.expectedSkills, + skillsInvoked: agentResponse.skillsInvoked, + skillCheckPassed, + checks: checkResults, + passed, + agentOutput: agentResponse.output, + }; + } catch (error) { + const errorMessage = error instanceof Error ? error.message : String(error); + return { + name: fixture.name, + description: fixture.description, + fixtureDir: path.relative(process.cwd(), fixtureDir), + prompt: fixture.prompt, + expectedSkills: fixture.expectedSkills, + skillsInvoked: [], + skillCheckPassed: false, + checks: [], + passed: false, + agentOutput: '', + error: errorMessage, + }; + } finally { + // Clean up temp directory + try { + await fs.rm(tempDir, { recursive: true, force: true }); + } catch { + // Ignore cleanup errors + } + } +} + +export async function discoverFixtures(baseDir: string): Promise { + const fixtures: string[] = []; + + async function scan(dir: string): Promise { + const entries = await fs.readdir(dir, { withFileTypes: true }); + + // Check if this directory is a fixture (has eval.json) + const hasEvalJson = entries.some(e => e.isFile() && e.name === 'eval.json'); + if (hasEvalJson) { + fixtures.push(dir); + return; // Don't recurse into fixtures + } + + // Recurse into subdirectories + for (const entry of entries) { + if (entry.isDirectory() && !entry.name.startsWith('.')) { + await scan(path.join(dir, entry.name)); + } + } + } + + await scan(baseDir); + return fixtures.sort(); +} + +async function runPool(items: T[], concurrency: number, fn: (item: T) => Promise): Promise { + const results: R[] = new Array(items.length); + let idx = 0; + const worker = async () => { + while (idx < items.length) { + const i = idx++; + results[i] = await fn(items[i]); + } + }; + await Promise.all(Array.from({ length: Math.min(concurrency, items.length) }, () => worker())); + return results; +} + +function groupFixturesBySuite(fixtures: ExpandedFixture[]): Map { + const suites = new Map(); + + for (const fixture of fixtures) { + // Extract suite name from path (e.g., fixtures/entities/basic-entity -> entities) + const parts = fixture.fixtureDir.split(path.sep); + const fixturesIndex = parts.indexOf('fixtures'); + const suiteName = fixturesIndex >= 0 && parts.length > fixturesIndex + 1 + ? parts[fixturesIndex + 1] + : 'default'; + + if (!suites.has(suiteName)) { + suites.set(suiteName, []); + } + suites.get(suiteName)!.push(fixture); + } + + return suites; +} + +export interface RunOptions { + name?: string; + fixturesDir?: string; + verbose?: boolean; + filter?: string; + fixtures?: string[]; + concurrency?: number; +} + +export async function runEvals( + agent: CodingAgent, + options: RunOptions = {} +): Promise { + const { + name = 'default', + fixturesDir = 'fixtures', + verbose = false, + filter, + fixtures: fixtureNames, + concurrency = 5, + } = options; + + // Initialize check registry + await initializeChecks(); + + // Discover fixture directories + let fixtureDirs = await discoverFixtures(fixturesDir); + + // Filter directories by specific fixture names if provided + if (fixtureNames && fixtureNames.length > 0) { + fixtureDirs = fixtureDirs.filter(f => { + const fixtureName = path.basename(f); + return fixtureNames.some(name => + fixtureName === name || fixtureName.includes(name) + ); + }); + } + + // Apply filter pattern to directories + if (filter) { + const filterLower = filter.toLowerCase(); + fixtureDirs = fixtureDirs.filter(f => f.toLowerCase().includes(filterLower)); + } + + // Expand multi-prompt fixtures into individual entries + let expanded = await expandFixtures(fixtureDirs); + + // Apply filter to expanded fixture names too + if (filter) { + const filterLower = filter.toLowerCase(); + expanded = expanded.filter(f => f.name.toLowerCase().includes(filterLower)); + } + + if (verbose) { + console.log(`Found ${fixtureDirs.length} fixture directories, ${expanded.length} prompts`); + } + + // Group by suite + const suiteMap = groupFixturesBySuite(expanded); + + // Flatten all fixtures with their suite name for parallel execution + const allItems: { suiteName: string; fixture: ExpandedFixture }[] = []; + for (const [suiteName, suiteFixtures] of suiteMap) { + for (const fixture of suiteFixtures) { + allItems.push({ suiteName, fixture }); + } + } + + if (verbose) { + console.log(`Running ${allItems.length} fixtures with concurrency ${concurrency}`); + } + + // Run all fixtures in parallel with concurrency limit + const allResults = await runPool(allItems, concurrency, async ({ fixture }) => { + const result = await runFixture(agent, fixture, verbose); + + // Print pass/fail as each fixture completes + if (result.passed) { + console.log(` ✅ ${result.name}`); + } else { + console.log(` ❌ ${result.name}`); + if (verbose && result.error) { + console.log(` Error: ${result.error}`); + } + } + + return result; + }); + + // Re-group results by suite + let totalPassed = 0; + let totalFailed = 0; + const suiteResults = new Map(); + + for (let i = 0; i < allItems.length; i++) { + const { suiteName } = allItems[i]; + const result = allResults[i]; + + if (!suiteResults.has(suiteName)) { + suiteResults.set(suiteName, []); + } + suiteResults.get(suiteName)!.push(result); + + if (result.passed) { + totalPassed++; + } else { + totalFailed++; + } + } + + const suites: SuiteResult[] = []; + for (const [suiteName] of suiteMap) { + const fixtureResults = suiteResults.get(suiteName) || []; + suites.push({ + name: suiteName, + fixtures: fixtureResults, + passed: fixtureResults.filter(f => f.passed).length, + failed: fixtureResults.filter(f => !f.passed).length, + }); + } + + return { + name, + date: new Date(), + agent: agent.name, + suites, + totalPassed, + totalFailed, + }; +} diff --git a/evals/src/setup.ts b/evals/src/setup.ts new file mode 100644 index 0000000..c4c44e0 --- /dev/null +++ b/evals/src/setup.ts @@ -0,0 +1,210 @@ +#!/usr/bin/env node + +/** + * Setup script that copies skills from fixture's own skills folder to project/. + * Experiments can optionally provide CLAUDE.md to override behavior. + */ + +import fs from 'fs/promises'; +import path from 'path'; +import { fileURLToPath } from 'url'; + +const __dirname = path.dirname(fileURLToPath(import.meta.url)); +const rootDir = path.join(__dirname, '..'); +const defaultFixturesDir = path.join(rootDir, 'fixtures'); + +async function copyDir(src: string, dest: string): Promise { + await fs.mkdir(dest, { recursive: true }); + const entries = await fs.readdir(src, { withFileTypes: true }); + + for (const entry of entries) { + const srcPath = path.join(src, entry.name); + const destPath = path.join(dest, entry.name); + + if (entry.isDirectory()) { + await copyDir(srcPath, destPath); + } else if (entry.isSymbolicLink()) { + const link = await fs.readlink(srcPath); + try { + await fs.unlink(destPath); + } catch { + // Ignore if doesn't exist + } + await fs.symlink(link, destPath); + } else { + await fs.copyFile(srcPath, destPath); + } + } +} + +export interface SetupOptions { + fixturesDir?: string; + experimentDir?: string; // Optional experiment dir for CLAUDE.md and skills override + verbose?: boolean; +} + +export async function setupFixtures(options: SetupOptions = {}): Promise { + const { + fixturesDir = defaultFixturesDir, + experimentDir, + verbose = true, + } = options; + + if (verbose) { + console.log('Setting up fixtures...'); + } + + // Get list of fixtures + const fixtureDirs = await fs.readdir(fixturesDir, { withFileTypes: true }); + const fixtures = fixtureDirs + .filter(d => d.isDirectory() && !d.name.startsWith('.')) + .map(d => d.name); + + if (verbose) { + console.log(`Found fixtures: ${fixtures.join(', ')}`); + } + + // Determine skills source: experiment skills override fixture skills + let experimentSkillsSrc: string | null = null; + let experimentSkillNames: string[] = []; + if (experimentDir) { + const expSkillsPath = path.join(experimentDir, 'skills'); + try { + await fs.access(expSkillsPath); + const skillDirs = await fs.readdir(expSkillsPath, { withFileTypes: true }); + experimentSkillNames = skillDirs + .filter(d => d.isDirectory() && !d.name.startsWith('.')) + .map(d => d.name); + if (experimentSkillNames.length > 0) { + experimentSkillsSrc = expSkillsPath; + if (verbose) { + console.log(`Using experiment skills: ${experimentSkillNames.join(', ')}`); + } + } + } catch { + // No skills in experiment, will use fixture skills + } + } + + // Copy skills to each fixture's project/skills + for (const fixture of fixtures) { + const fixtureDir = path.join(fixturesDir, fixture); + const fixtureSkillsSrc = path.join(fixtureDir, 'skills'); + const projectSkillsDest = path.join(fixtureDir, 'project', 'skills'); + + // Use experiment skills if available, otherwise fixture skills + const skillsSrc = experimentSkillsSrc ?? fixtureSkillsSrc; + + // Check if skills source exists + let skillCount = 0; + try { + await fs.access(skillsSrc); + + // Get list of skills + const skillDirs = await fs.readdir(skillsSrc, { withFileTypes: true }); + const skills = skillDirs + .filter(d => d.isDirectory() && !d.name.startsWith('.')) + .map(d => d.name); + skillCount = skills.length; + + // Remove existing project skills directory + try { + await fs.rm(projectSkillsDest, { recursive: true, force: true }); + } catch { + // Ignore + } + + // Copy skills to project/skills + await copyDir(skillsSrc, projectSkillsDest); + } catch { + // No skills folder found, skip + if (verbose) { + console.log(` ⚠ ${fixture}: no skills folder found`); + } + continue; + } + + // Handle CLAUDE.md from experiment (if provided) + const destClaudeMd = path.join(fixtureDir, 'project', 'CLAUDE.md'); + let hasClaude = false; + + if (experimentDir) { + const srcClaudeMd = path.join(experimentDir, 'CLAUDE.md'); + try { + await fs.access(srcClaudeMd); + await fs.copyFile(srcClaudeMd, destClaudeMd); + hasClaude = true; + } catch { + // No CLAUDE.md in experiment, remove any existing one + try { + await fs.unlink(destClaudeMd); + } catch { + // Ignore if doesn't exist + } + } + } else { + // No experiment, remove any existing CLAUDE.md + try { + await fs.unlink(destClaudeMd); + } catch { + // Ignore if doesn't exist + } + } + + // Handle AGENTS.md from experiment (if provided) + const destAgentsMd = path.join(fixtureDir, 'project', 'AGENTS.md'); + let hasAgents = false; + + if (experimentDir) { + const srcAgentsMd = path.join(experimentDir, 'AGENTS.md'); + try { + await fs.access(srcAgentsMd); + await fs.copyFile(srcAgentsMd, destAgentsMd); + hasAgents = true; + } catch { + // No AGENTS.md in experiment, remove any existing one + try { + await fs.unlink(destAgentsMd); + } catch { + // Ignore if doesn't exist + } + } + } else { + // No experiment, remove any existing AGENTS.md + try { + await fs.unlink(destAgentsMd); + } catch { + // Ignore if doesn't exist + } + } + + if (verbose) { + const mdFiles = [hasClaude && 'CLAUDE.md', hasAgents && 'AGENTS.md'].filter(Boolean); + const suffix = mdFiles.length > 0 ? ` + ${mdFiles.join(', ')}` : ''; + const source = experimentSkillsSrc ? ' (from experiment)' : ''; + console.log(` ✓ ${fixture}: copied ${skillCount} skills${source}${suffix}`); + } + } + + if (verbose) { + console.log('Setup complete!'); + } +} + +async function setup(): Promise { + try { + await setupFixtures(); + } catch (error) { + console.error('Setup failed:', error instanceof Error ? error.message : error); + process.exit(1); + } +} + +// Run if executed directly +const isMainModule = process.argv[1]?.includes('setup'); +if (isMainModule) { + setup().catch(error => { + console.error('Setup failed:', error); + process.exit(1); + }); +} diff --git a/evals/src/types.ts b/evals/src/types.ts new file mode 100644 index 0000000..303fe07 --- /dev/null +++ b/evals/src/types.ts @@ -0,0 +1,131 @@ +export interface CodingAgentResponse { + output: string; + skillsInvoked: string[]; + metadata?: Record; +} + +export interface CodingAgent { + name: string; + run(prompt: string, workingDir: string): Promise; +} + +export interface CheckResult { + name: string; + passed: boolean; + details?: string; +} + +export interface EvalContext { + agentResponse: CodingAgentResponse; + fixtureDir: string; + projectDir: string; + checkConfig: CheckConfig; +} + +export interface Check { + type: string; + run(context: EvalContext): Promise; +} + +export interface CheckConfig { + type: string; + description: string; + target?: 'output' | 'file'; + filePath?: string; + expectedValid?: boolean; + value?: string; + pattern?: string; + command?: string; + schema?: Record; +} + +export interface PromptConfig { + name: string; + description?: string; + prompt: string; + expectedSkills: string[]; + checks: CheckConfig[]; +} + +export interface EvalConfig { + name: string; + description: string; + // Single-prompt (backward compat) + prompt?: string; + expectedSkills?: string[]; + checks?: CheckConfig[]; + // Multi-prompt + prompts?: PromptConfig[]; +} + +export interface ExpandedFixture { + name: string; + description: string; + fixtureDir: string; + prompt: string; + expectedSkills: string[]; + checks: CheckConfig[]; +} + +export interface FixtureResult { + name: string; + description: string; + fixtureDir: string; + prompt: string; + expectedSkills: string[]; + skillsInvoked: string[]; + skillCheckPassed: boolean; + checks: CheckResult[]; + passed: boolean; + agentOutput: string; + error?: string; +} + +export interface SuiteResult { + name: string; + fixtures: FixtureResult[]; + passed: number; + failed: number; +} + +export interface EvalRunResult { + name: string; + date: Date; + agent: string; + suites: SuiteResult[]; + totalPassed: number; + totalFailed: number; +} + +// Experiment configuration +export interface Experiment { + name: string; // Directory name + skillsDir: string; // Absolute path to skills + skills: string[]; // Skill names found +} + +// Result for one experiment across all fixtures +export interface ExperimentRunResult { + experiment: string; + date: Date; + agent: string; + suites: SuiteResult[]; + totalPassed: number; + totalFailed: number; +} + +// Full comparison result +export interface ComparisonResult { + name: string; + date: Date; + agent: string; + experiments: string[]; + fixtures: string[]; + matrix: Record>; // exp -> fixture -> passed + experimentResults: ExperimentRunResult[]; + summary: { + byExperiment: Record; + byFixture: Record; + bestExperiment: string; + }; +} diff --git a/evals/tsconfig.json b/evals/tsconfig.json new file mode 100644 index 0000000..d99b7ee --- /dev/null +++ b/evals/tsconfig.json @@ -0,0 +1,16 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "NodeNext", + "moduleResolution": "NodeNext", + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "outDir": "dist", + "rootDir": "src", + "declaration": true + }, + "include": ["src/**/*"], + "exclude": ["node_modules", "dist"] +}