惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

S
Security Affairs
S
Schneier on Security
N
News | PayPal Newsroom
T
Threatpost
Cloudbric
Cloudbric
H
Heimdal Security Blog
Recent Commits to openclaw:main
Recent Commits to openclaw:main
Google Online Security Blog
Google Online Security Blog
D
Darknet – Hacking Tools, Hacker News & Cyber Security
Spread Privacy
Spread Privacy
V
Vulnerabilities – Threatpost
The Last Watchdog
The Last Watchdog
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
L
LINUX DO - 最新话题
P
Proofpoint News Feed
C
CXSECURITY Database RSS Feed - CXSecurity.com
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
Apple Machine Learning Research
Apple Machine Learning Research
NISL@THU
NISL@THU
Application and Cybersecurity Blog
Application and Cybersecurity Blog
The Hacker News
The Hacker News
O
OpenAI News
人人都是产品经理
人人都是产品经理
C
Cyber Attacks, Cyber Crime and Cyber Security
C
Check Point Blog
C
Cisco Blogs
GbyAI
GbyAI
J
Java Code Geeks
L
LangChain Blog
I
Intezer
T
Tailwind CSS Blog
有赞技术团队
有赞技术团队
MyScale Blog
MyScale Blog
美团技术团队
The Register - Security
The Register - Security
Help Net Security
Help Net Security
WordPress大学
WordPress大学
Y
Y Combinator Blog
T
Tor Project blog
M
MIT News - Artificial intelligence
爱范儿
爱范儿
TaoSecurity Blog
TaoSecurity Blog
V
Visual Studio Blog
T
Threat Research - Cisco Blogs
P
Palo Alto Networks Blog
月光博客
月光博客
T
Tenable Blog
S
Securelist
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
D
DataBreaches.Net

Show HN

GitHub - villagesql/villagesql-skills: Agent skills for VillageSQL - gemini-cli-extension; claude-code-plugin GitHub - flightdeckhq/flightdeck: Observability and control plane for AI agents. CSP Radar GitHub - Light-Heart-Labs/DreamServer: Turn your PC, Mac, or Linux box into an AI server. LLM inference, chat UI, voice, agents, workflows, RAG, and image generation. GitHub - Diplomat-ai/diplomat-agent-ts: What can your TypeScript AI agent do to the real world? Scan your code. See which tool calls have zero checks Code Block Selector - Visual Studio Marketplace Prometheus dependency graph — interactive showcase | Riftmap Show HN: I made a vi-like modal keyboard plugin for Figma GitHub - run-llama/liteparse: A fast, helpful, and open-source document parser GitHub - dalemyers/Roar: A macOS CLI tool for notifications GitHub - district-solutions/open-agent-tools-coder: Enables small-to-large self-hosted ai models to use local source code when running tool-calling agentic workloads. We actively data mine 20,900+ (2+ TB) popular github repos using large and small ai models to create reuseable: json, markdown and parquet files for local-first tool-calling models. GitHub - progapandist/stripeek: A local TUI proxy for real-time Stripe API debugging, built for navigating complex payloads fast. GitHub - sir1st/hermes-desktop: All-in-one cross-platform desktop app for Hermes Agent — bundles Python + hermes-agent + hermes-web-ui GitHub - astefanutti/shaderbang: Shebang for Shaders Show HN: Generate Claude Code Workflows using Spec Driven Development approach GitHub - nixys/nxs-universal-chart: The Helm chart you can use to install any of your applications into Kubernetes/OpenShift Show HN: AI agents for UK GDAD PCF roles and their skills The Two Pillars: Mixer Mode and Meta-Software in the Reorganization of Software Work After AI GitHub - JaiCode08/teleport-env What 1,000+ Harness Experiments Taught Me About Self-Improving Agents Show HN: Liiists, a Markdown-first, iOS and CLI list app SwiperTab – Get this Extension for 🦊 Firefox (en-US) GitHub - kouhxp/fftext: Summarize, explain, fact-check, or translate any text, URL, or file. No GPU. No cloud. One command GitHub - sweetpad-dev/sweetpad: Develop Swift/iOS projects using VSCode GitHub - dogmaticdev/IRON: IRON a.k.a. Intermediate Representation Object Notation is a Interpreter/Database that is used to create Programming Languages. GitHub - sjhalani7/vaen: Package your AI coding harness into a portable .agent file, and share it across repos, teams, & the community without ever having to copy-paste instructions, skills, MCP config, or secrets. Show HN: Gandalf the Grader Show HN: Citadeld – replay any CI failure locally from a single file GitHub - tdortman/cuSBF: High-Performance GPU Super Bloom Filter coral-ai/claude-code-token-xray at main · Coral-Bricks-AI/coral-ai GitHub - ulyssestenn/funes: Funes is a Git-based framework for LLM-managed knowledge work: an AI Librarian ingests raw sources, builds an interlinked Markdown knowledge base, and uses it to produce cited reports, analyses, and other outputs. GitHub - ThatXliner/gah: Git Add Hunk, built for agents to use GitHub - harmont-dev/harmont-cli: Command-line client for the Harmont CI platform GitHub - brooksmcmillin/mcp-authflow: OAuth 2.0 Authorization Server framework for MCP servers GitHub - javaid-codes/audit-supply-chain-agents GitHub - amorey/gochan: A small library of common channel architectures for Go, inspired by Rust GitHub - arifozgun/OpenGem: Free, Open-Source AI API Gateway with Gemini, OpenAI & Anthropic Compatibility in 1 file GitHub - Pranesh950/BioPetals: 🌸 Run BIOxAI models at home, BitTorrent-style. Fine-tuning and inference up to 10x faster than offloading GitHub - cnguyen14/bounty-doctor: Diagnose a GitHub bounty issue before you waste hours: detects honeypot scam repos, AI-bot attempt swarms, and stale contests. Show HN: CoreMCP – MCP Server for On-Prem DBs Show HN: KittyHTML – Render HTML/CSS as an inline image in your terminal GitHub - bingud/filemat: Web-based file manager Show HN: TruthLens – Free multi-signal deepfake image detector GitHub - apexlocal-jz/claude-usage-tray: Windows system-tray app showing your Claude Code rate-limit usage at a glance. Zero deps, ~300 lines of PowerShell. Cross-IDE (works regardless of VS Code, Cursor, plain terminal). Release v0.1.2.1 · kouhxp/yapsnap GitHub - noopolis/moltnet: Self-hostable chat network for AI agents. Pre-built bridges for Claude Code, Codex, and the Claws. Rooms, DMs, history. No Slack bots, no Matrix, no glue code. GitHub - tamerh/enju: Coordinating Humans, AI Agents, and Compute as Peers on a Shared Workflow Graph Show HN: Continuity-auth – Respect-weighted rate limits for the open web GitHub - luml-ai/luml: AI lifecycle platform where engineers and agents track experiments, train models, and ship to production. GitHub - mrdanielcasper/CoreTex: A UNIX-inspired, biomimetic, flat-file AI harness and knowledge engine. GitHub - clemg/pierre-github: Pierre's diffs.com and trees.software for Github GitHub - lyriks-io/unspaghettit: Behavior-driven AI development without prompt spaghetti. GitHub - sofumel/claude-handoff-revive: Resume Claude Code work after rate/usage/context limits without replaying the prior transcript. Auto-saves at 90%/95% usage. Plugin-installable, 10 languages. GitHub - dotexorg/saferpc: Typed, end-to-end encrypted RPC over any bidirectional channel. GitHub - BeeZeeAgent/beezee: Agent harness orchestration Legato Next.js Boilerplate for Internal Tools · CoreUI GitHub - clark-labs-inc/clark-hash: Clark Hash, 32x smaller searchable sketches for embeddings GitHub - ZeroPointRepo/youtube-mcp: The fastest YouTube transcript + YouTube search MCP for AI agents. Try for free. Typing Mastery — climb toward 100+ WPM, deliberately GitHub - Andebugulin/Awareen GitHub - fayzan123/claude-workflow-composer: Visual desktop app for composing multi-agent coding workflows. Drag agents, attach skills and MCPs, wire handoffs, export to .claude/ GitHub - harshaneel/humanize: Best static AI text humanizer. Two research-grounded skills that work in any LLM (Claude, ChatGPT, Gemini, Codex): humanize beats perplexity-based detectors, ai-check produces forensic scoring with evidence-quoted flags. Nine levers, 50+ peer-reviewed sources, 2024-2026 detection literature. GitHub - StackOneHQ/stack-nudge GitHub - nodes-app/swift-markdown-engine: A native AppKit Markdown editor for macOS, built on TextKit 2 and bridged to SwiftUI. We hardened an LLM agent. Each defense we added made it more exploitable. GitHub - alkait/WhatsKept: Agent-queryable WhatsApp history from an iOS backup — a single Go binary. GitHub - octelium/cordium: Open-source, general-purpose sandbox platform for devs and AI agents that provides identity-based secure access to infrastructure without credentials. WAR.GOV/UFO Microfilm5 GitHub - scosman/videowright: Build animated explainer videos with your coding agent GitHub - dipankar/dscode: The code editor you can take apart. GitHub - zoharbabin/web-researcher-mcp: MCP server (Go) for AI assistants: web search, content extraction, academic/patent/news research. Multi-provider routing, 4-tier scraping, search lenses. Works with Claude, Cursor, and any MCP client. GitHub - ruvnet/RuView: π RuView turns commodity WiFi signals into real-time spatial intelligence, vital sign monitoring, and presence detection — all without a single pixel of video. GitHub - scanaislop/aislop: Catch the slop AI coding agents leave in your code: narrative comments, swallowed exceptions, as-any casts, dead code, oversized functions. 50+ rules across 7 languages (TypeScript, JavaScript, Python, Go, Rust, Ruby, PHP). Sub-second, deterministic, no LLM at runtime. MIT-licensed. GitHub - kouhxp/cheap-im: CPU-only voice agent approximating Thinking Machines' Interaction Models demo GitHub - unprovable/OrchidMantis: Orchid Mantis — standalone framework for Zero-Knowledge Proofs of eXploit (ZKPoX). GitHub - MarcellM01/TinySearch: Shrink the web for your local LLMs! GitHub - TangibleResearch/Halgorithem: A Algo designed to detect AI Hallucitions GitHub - DO-SAY-GO/freelang: I love freelang GitHub - CarpseDeam/Aura-IDE: An AI coding harness that shaped itself - Planner/Worker agents, repo awareness, surgical edits, validation, recovery, and safe diff approvals. GitHub - chojs23/concord: A feature-rich TUI client for Discord GitHub - tommyjepsen/awesome-ux-skills: UX & AI Product designs skills you can use today in Claude Code GitHub - aerf-spec/aerf: Agent Evidence Receipt Format (AERF) — an open specification for tamper-evident, independently verifiable records of AI agent actions. GitHub - kklimuk/docx-cli: CLI for AI agents (Claude, Codex) to read, edit, and comment on .docx files with full format fidelity. GitHub - Jwrede/tokentoll: Catch LLM cost changes in code review. Infracost for LLM spend. GitHub - samchon/ttsc: A `typescript-go` toolchain for compiler-powered plugins and type-safe execution + 500x faster lint integrated into compiler GitHub - Higangssh/homebutler: 🏠 Manage your homelab from chat. Single binary, zero dependencies. GitHub - olalie/tapmap: See where your computer connects and what stands out on a live world map. GitHub - matisiekpl/neond: DX-focused control plane for Postgres dedicated to non-critical workloads. Your postgres:latest replacement 🐘 GitHub - Diplomat-ai/diplomat-agent: What can your AI agent do to the real world? Scan your code. See which tool calls have zero checks GitHub - Bajusz15/beacon: Open-source agent for secure remote access, monitoring, and deploys across home-lab and self-hosted machines like Raspberry Pi, N100, or any Linux server. Open web based TTY or tunnel Home Assistant and other local services securely without opening ports. BigTech AI News - Chrome 应用商店 GitHub - vinhnx/VTCode: VT Code is an open-source coding agent with LLM-native code understanding and robust shell safety. Supports multiple LLM providers with automatic failover and efficient context management. GitHub - michaelaz774/decision-engine: A decision operating system for startup founders, powered by Claude Code. Synthesizes wisdom from 25+ legendary founders and investors into interactive AI-driven decision frameworks. GitHub - Chrilleweb/dotenv-diff: Validate environment variable usage in your codebase GitHub - Lumen-Labs/brainapi2: BrainAPI is a knowledge graph–powered AI memory layer that transforms unstructured data into structured knowledge, enabling intelligent search, recommendations, and contextual memory for AI agents and applications. GitHub - familiar-software/familiar: Let AI watch you work. Familiar lets your AI update its memory, skills, and knowledge by watching your screen. GitHub - skorotkiewicz/rudo: A small, elegant dock for Wayland GitHub - muxshed/shed: One stream in, or many. Every destination, simultaneously. No cloud middleman, no per-channel fees, no limits. make sidebar/address bar rounded corner toggleable
GitHub - localflow-ai/localflow-core: The core API to enable metadata-first AI in your projects.
renaudpawlak · 2026-06-02 · via Show HN

npm License: Apache 2.0 Live demo


Table of contents

  • What is Metadata-first AI?
    • The metadata boundary
    • Two-step execution
    • Key properties
  • Architecture overview
    • What leaves the browser?
    • Limitations
  • Quick start
  • Use an actual proxy
  • API Reference
    • LocalAssistant
      • Constructor
      • Configuration
      • Datasets
      • External APIs
      • Conversation & prompt()
      • Analysis match hook
      • Events
    • Proxy / LocalProxy / ProxyClient
  • Building
  • Package structure
  • Roadmap
  • License

What is Metadata-first AI?

In metadata-first AI, only metadata about your data ever reaches the LLM — column names, statistical samples, document structure. The actual rows, values, and documents stay on your machine. The model acts as a code generator: given a description of the data's shape, it writes analysis code that executes locally in a sandbox on your real data.

This is a different axis than the two approaches usually discussed:

  • Classical cloud AI — sends raw data to the model. Powerful and flexible, but your data leaves your environment and every result is a fresh inference, non-deterministic by nature.
  • Local-model AI (Ollama, llama.cpp, etc.) — runs the model on your device so data stays local, but you are constrained by what fits on your hardware, and results remain non-deterministic.

Metadata-first AI makes the constraint explicit: only metadata crosses the inference boundary — raw data never does. When using a cloud LLM, metadata (column names, statistics, document structure) does leave your machine, which is an acceptable exposure for most organisations. For the strictest privacy requirements, combining metadata-first AI with a self-hosted LLM eliminates even that: nothing crosses your infrastructure boundary. The generated code runs deterministically in your browser on your real data.

Classical cloud AI Local-model AI Metadata-first AI
Raw data stays local
Metadata stays local ⚠️ cloud / ✅ self-hosted
Computation executes locally
Uses best available models Limited
Compatible with self-hosted LLMs
Results are deterministic
Re-runs without AI (no tokens/GPU)
Works on large datasets Limited

Typical use cases range from "Talk to your Data" on sensitive enterprise spreadsheets — where you want natural language querying without exposing values to the model — to large-scale geospatial analysis, document intelligence on confidential PDFs, and any analytical pipeline where deterministic, repeatable results matter.

The metadata boundary

LocalFlow defines precisely what constitutes metadata for each data type:

  • Structured data (CSV, Excel, CRM): column headers and statistical samples — enough for the LLM to write correct analysis code. Raw rows are never sent.
  • Documents (PDF): the extracted text is needed so the LLM understands the document's structure and can write a reliable parser. Users can work with obfuscated or template documents to generate formulas, then run them locally on real documents — the LLM only needs the structure, not the actual values.

Two-step execution

Code generation (step 1) and local execution (step 2) are invisible to the user — the assistant behaves like a regular AI assistant. The difference is that generated analyses can be saved and re-run on any compatible dataset without making another LLM call.

Key properties

  1. Full AI power — use the best available LLM to analyse complex, heterogeneous data
  2. Data safety — raw data never reaches the LLM. Metadata (schema, statistics) leaves only for code generation; a self-hosted LLM removes even that exposure
  3. No hallucinated results — outputs are computed from real data by deterministic code, not inferred by the model
  4. Scalable — once generated, run the same analysis on large datasets as many times as needed, consuming no additional AI tokens
  5. Explainable — the generated code is fully inspectable; any AI can explain why a formula works or debug why it fails
  6. Green and sustainable — AI is used only for code generation, a one-time cost per analysis. Subsequent runs consume no AI inference at all, reducing dependence on energy-intensive infrastructure

Architecture overview

┌─────────────────────────────────────────────────────┐
│                  Browser (client)                   │
│                                                     │
│  ┌─────────────────┐      ┌──────────────────────┐  │
│  │  Host App / UI  │      │      Sandbox         │  │
│  │  (React or any) │      │  formula execution   │  │
│  │                 │      │  charts / maps       │  │
│  │  LocalAssistant │◄────►│  fetch → proxy relay │  │
│  │  (vanilla JS)   │      └──────────────────────┘  │
│  └────────┬────────┘                                │
│           │ HTTPS (column stats + generated code)   │
└───────────┼─────────────────────────────────────────┘
            │
   ┌────────▼────────┐
   │ LocalFlow Proxy │  (manages keys, auth,
   │                 │   whitelists APIs,
   └────────┬────────┘   edge services: PDF, OCR...)
            │
            ├──────────────────┐─────────────────────┐
            │                  │                     │
   ┌────────▼────────┐ ┌───────▼───────┐ ┌───────────▼───────────┐
   │     LLM API     │ │ Whitelisted   │ │  Your CRM / ERP / DB  │
   └─────────────────┘ │ external APIs │ └───────────────────────┘
                       └───────────────┘

What leaves the browser?

Operation Data sent Where
Tabular analysis — code generation Column headers + statistics 🟠 LLM
PDF extraction Raw PDF bytes 🔵 Proxy
PDF analysis — code generation Extracted document text 🟠 LLM
Analysis execution Actual data 🟢 Browser
External API calls (optional) Query parameters only 🔵 Proxy

🟢 Browser — stays in your browser  ·  🔵 Proxy — goes to your server only and proxied APIs  ·  🟠 LLM — forwarded to the AI model via your proxy

Limitations

Because the LLM only ever sees metadata — never the actual rows — there are tasks that a metadata-first approach cannot perform on its own. Inference excels at understanding natural language, translating free-form content, summarising documents, or spotting patterns across raw text. When those capabilities need to operate on the full dataset, a classical AI pipeline (one that sends the data to the model) remains the right tool.

LocalFlow is therefore a complement to classical AI, not a replacement. It opens up use cases that are out of reach for classical AI — large-scale data analysis without privacy exposure, deterministic and repeatable results, scalable execution at zero marginal AI cost — while leaving room for classical approaches where they are genuinely needed.

That boundary is not fixed. The proxy can expose tools — including LLM-powered ones — that operate on a carefully scoped subset of data, defined and controlled by the administrator or the user. A formula could, for example, call a proxy-hosted service that summarises a specific field or translates a column, without the LLM ever seeing the full dataset. This kind of extension requires intentional configuration of your proxy environment and its available tools, tailoring the setup to your specific use cases and acceptable data-sharing boundaries.


📄 For a deeper dive into these concepts, read the LocalFlow white paper.

Quick start — embedding LocalAssistant in your app

No server required — LocalProxy runs entirely in the browser and calls the LLM directly. See localflow-examples for complete React and vanilla JS apps you can run immediately.

1. Install

npm install @localflow/core

2. Create the assistant

import { LocalProxy, LocalAssistant } from '@localflow/core'

// No server needed — LocalProxy calls the LLM directly from the browser
const proxy = new LocalProxy()

const assistant = new LocalAssistant({
  proxy,
  llm: {
    type: 'gemini',
    model: 'gemini-3-flash-preview',   // optional, this is the default
  },

  // Point to the div where formula results should be rendered.
  // Accepts an HTMLElement, a CSS selector string, or a factory function.
  resultContainer: '#result',
})

// Pass the user's Gemini API key — stored locally, never sent to any third party
// NOTE: automatically encryted when using an actual proxy
await assistant.setLlmApiKey('AIza...')

// Persist LLM config whenever it changes (user sets a new key, model, etc.)
assistant.on('llm:change', (llm) => {
  if (llm.apiKey) localStorage.setItem('llm-key', llm.apiKey)
})

To restore a key across page loads, pass it at construction:

const assistant = new LocalAssistant({
  proxy,
  llm: { type: 'gemini', apiKey: localStorage.getItem('llm-key') ?? '' },
  resultContainer: '#result',
})

3. Load your data

// Tabular data — from any source: CSV parse, DB query, API response, etc.
assistant.addDataset('portfolio', portfolioRows)   // rows: Record<string, unknown>[]
assistant.addDataset('market',    marketRows)

// Mark which dataset is the "active" one (the `data` variable in formulas)
assistant.setActiveDataset('portfolio')

4. Send a message and render the result

With resultContainer configured, the assistant takes care of everything — creating the iframe, setting sandbox permissions, and relaying proxied API calls. No boilerplate needed.

assistant.on('message', (response) => {
  // Show response.answer in your chat UI
  appendChatBubble(response.answer)
})

assistant.on('formula:done', ({ data }) => {
  // Optional: react to the formula's output data
  console.log('Analysis result:', data)
})

// Send a message — the LLM generates a formula, and the assistant
// renders it automatically in the configured resultContainer.
const response = await assistant.prompt('Show me the allocation by asset class')

You can change the result container at any time:

assistant.resultContainer = document.getElementById('result')
// or: assistant.resultContainer = '#result'
// or: assistant.resultContainer = () => document.querySelector('.panel.active')

5. Save and replay formulas (optional)

Generated formulas are deterministic — you can save them and re-run on any compatible dataset without an additional LLM call.

// Get the formula from the last response
const formula = assistant.getLastFormula()

// Save it to your catalog...
saveToCatalog({ formula, title: response.title, description: response.description })

// ...and replay it later
assistant.executeFormula(savedFormula)

6. Register a semantic analysis-match hook (optional)

This lets the assistant inject a relevant past analysis as a system-prompt example before calling the LLM, improving output quality.

assistant.setAnalysisMatchHook(async (query, ctx) => {
  const analyses = catalogLoad()        // your catalog store
  return findBestMatch(query, analyses, ctx.activeColumns)
})

Use an actual proxy

LocalProxy is designed for local development and quick prototyping. For production — where you need session management, API governance, PDF extraction, BYOK key encryption, rate limiting, and data flow monitoring — replace it with a ProxyClient connected to a LocalFlow proxy server.

The proxy handles:

  • Security and session management — authenticates users against your business systems (CRM, ERP, or guest sessions), manages session tokens, and encrypts API keys so secrets are never exposed to the browser
  • API governance — defines which external APIs formulas may call; supports BYOK, per-source throttling, URL whitelisting, and OAuth 2.0 token exchange
  • Server-side edge services — PDF text extraction, OCR, and other tasks better suited to a server than a browser
  • LLM bridge — decrypts the user's API key at request time and forwards generation requests to the LLM
  • Data flow monitoring — tracks and audits what data enters and leaves the sandbox

LocalAssistant accepts any implementation of the Proxy interface — switching from LocalProxy to ProxyClient requires no other changes.

Connect to the proxy

import { ProxyClient, LocalAssistant } from '@localflow/core'

const proxy = new ProxyClient('https://your-proxy.example.com')

// Authenticate — stores the session token on the proxy instance
await proxy.connect('odoo', { url, database, login, password })
// or for a public/guest session:
await proxy.connect('public', {})

// Save the token so the session survives a page reload
localStorage.setItem('proxy-token', proxy.token!)

// Restore on next load
const proxy = new ProxyClient('https://your-proxy.example.com', localStorage.getItem('proxy-token'))
await proxy.getSessionInfo()  // throws if expired — re-authenticate if needed

const assistant = new LocalAssistant({ proxy, llm: { type: 'gemini' }, resultContainer: '#result' })

Quick testing: a hosted instance is available at https://backoffice.daquota.io/v1 — no account needed. You can start with a guest (public) session, or authenticate against your own CRM if you want to test with real data. That said, you probably don't want to point your production CRM at an instance you don't control; use a sandbox or test environment instead.

Self-hosting: for production use, run your own instance — see the localflow-proxy repository for setup instructions.

PDF extraction

PDF extraction requires a proxy (not available with LocalProxy).

// Extract text via the proxy, then load as a dataset
const { text, pageCount } = await proxy.extractPdf(pdfBuffer)
assistant.addPdfDataset('report.pdf', pdfBuffer, text, pageCount)

assistant.setActiveDataset('report.pdf')
const response = await assistant.prompt('Summarise the key figures')

Pass an optional searchString to receive only pages matching the query (plus one page of context either side):

const { text, pageCount } = await proxy.extractPdf(pdfBuffer, 'revenue')

Wire up external APIs (optional)

The proxy admin configures which external APIs analysis formulas may call. Users can opt in/out and supply their own API keys (BYOK) per API.

// Fetch the list of APIs the proxy admin has whitelisted
const apis = await assistant.fetchApiConfigs()

// Activate one for use in formulas
assistant.activateApi('overpass')

// If an API requires a user-supplied key (BYOK):
// pass the plain key — the assistant encrypts it via the proxy internally.
await assistant.setApiUserKey('my-api', plainApiKey)

// Persist API preferences whenever they change
assistant.on('prefs:change', (prefs) => {
  localStorage.setItem('api-prefs', JSON.stringify(prefs))
})

API Reference

LocalAssistant API reference

Constructor

new LocalAssistant(config: LocalAssistantConfig)
interface LocalAssistantConfig {
  proxy: Proxy             // any Proxy implementation — LocalProxy or ProxyClient
  llm: LLMConfig           // LLM backend configuration
  darkMode?: boolean       // passed to the formula sandbox (default: false)
  apiPreferences?: ApiPreference[]    // previously persisted prefs (from 'prefs:change')
  resultContainer?: ResultContainer   // where to render formula results
  sandboxPermissions?: string[]       // iframe sandbox flags (see defaults below)
  sandboxTheme?: Record<string, unknown>    // Tailwind theme object injected into the sandbox (see below)
  pdfFormulaRevision?: boolean        // silent self-correction on first PDF query (default: false)
  formulaHealingRetries?: number      // silent retries on JS syntax errors (default: 1)
}

interface LLMConfig {
  type: 'gemini' | string  // backend type — only 'gemini' is implemented today
  apiKey?: string          // encrypted key restored from storage — set via setLlmApiKey()
  model?: string           // model ID (default: 'gemini-3-flash-preview')
}

// resultContainer accepts any of:
type ResultContainer = HTMLElement | string | (() => HTMLElement | null)
//                     ^ element     ^ selector  ^ factory (evaluated at render time)

// Default sandbox permissions:
const DEFAULT_SANDBOX = [
  'allow-scripts', 'allow-downloads', 'allow-modals',
  'allow-popups',  'allow-popups-to-escape-sandbox',
]

Configuration

Property / Method Type / Returns Description
assistant.llm LLMConfig Read or replace the LLM configuration. Emits 'llm:change'.
assistant.darkMode boolean Toggle dark mode in the analysis sandbox.
assistant.resultContainer ResultContainer Where formula results are rendered.
assistant.sandboxPermissions string[] iframe sandbox flags.
assistant.pdfFormulaRevision boolean When true, the first formula for a new PDF is silently run, its logs collected, and a second LLM call revises it before anything is shown to the user.
sandboxTheme (constructor only) Record<string, unknown> Tailwind theme object injected into the sandbox CDN config before it initialises. Use it to align generated UI with your host app's palette: override gray shades for dark-mode card surfaces, define a primary color referenceable via bg-primary / text-primary, etc. The sandbox body also picks up dark:bg-gray-900 dark:text-gray-100 automatically, so setting gray[900] and gray[100] covers the full background. Works with any host framework — just translate your design tokens to Tailwind color values.
assistant.proxy (read-only) Proxy The proxy implementation passed at construction.
setLlmApiKey(plainKey) Promise<void> Encrypt a plain API key via the proxy and store it. Emits 'llm:change'.

Datasets

Datasets are ordered key-value pairs: name → array of row objects (tabular) or PDF document (with extracted text). The active dataset is exposed as the data variable inside formula code; all datasets are accessible via datasets['name'].

Method Returns Description
addDataset(name, rows) void Add or replace a tabular dataset. First added becomes active.
addPdfDataset(name, buffer, extractedText, pageCount) void Add a PDF document. Pass the text returned by proxy.extractPdf().
removeDataset(name) void Remove a dataset. Active dataset moves to next available.
updateDataset(name, rows) void Replace rows for an existing dataset.
getDataset(name) rows | undefined Read rows for a named dataset.
getDatasets() Record<string, rows[]> All datasets as a plain object.
setActiveDataset(name) void Mark a dataset as active (data variable in formulas).
getActiveDataset() { name, type, rows, columns } | null Current active dataset. type is 'table' or 'pdf'.
getActivePdfBuffer() ArrayBuffer | null Raw bytes of the active PDF.
getActivePdfExtractedText() string Extracted text of the active PDF.
getActivePdfPageCount() number Page count of the active PDF.
clearDatasets() void Remove all datasets and reset active.
assistant.addDataset('Sales Q1', salesRows)
assistant.addDataset('Products', productRows)
assistant.setActiveDataset('Sales Q1')

const active = assistant.getActiveDataset()
// { name: 'Sales Q1', rows: [...], columns: ['id', 'product', 'amount', ...] }

External APIs

The proxy admin configures which external APIs analysis formulas may call. Users can opt in/out and supply their own API keys (BYOK) per API.

Method Returns Description
fetchApiConfigs() Promise<ApiConfig[]> Fetch available APIs from the proxy and store them.
setApiConfigs(configs) void Set API configs without a network call.
getApiConfigs() ApiConfig[] Currently stored API configs.
setApiPreferences(prefs) void Bulk-set user preferences (enabled flags + BYOK keys).
getApiPreferences() ApiPreference[] Current user preferences.
activateApi(id) void Enable an API for use in formulas.
deactivateApi(id) void Disable an API.
setApiUserKey(id, plainKey) Promise<void> Encrypt a BYOK key via the proxy and store it. Pass the plain key — encryption is handled internally.
getActivatedApis() ActivatedApi[] APIs that are currently active (forced or user-enabled).

Conversation

Method Returns Description
prompt(message, opts?) Promise<AssistantResponse> Send a user message. Builds system prompt, calls the LLM, appends to history, emits 'message'.
getHistory() ConversationTurn[] Full conversation history.
setHistory(turns) void Replace the conversation history (e.g. to restore a session).
appendHistory(turn) void Append a single turn.
clearHistory() void Reset conversation to empty.
getLastFormula() string | null Formula code from the most recent model response.
buildCurrentSystemPrompt(example?) string Build the system prompt for the current state without sending it.

prompt() options

await assistant.prompt('Break down revenue by region', {
  exampleAnalysis: {      // inject a catalog analysis as a system-prompt example
    formula: '...',
    title: 'Revenue by country (bar chart)',
    description: '...',
  },
})

AssistantResponse shape

interface AssistantResponse {
  answer: string          // HTML description shown before the formula runs
  formula: string         // JS code to execute in the sandbox
  title?: string          // short title for the analysis
  description?: string    // longer functional description
  dependencies?: {        // declared data dependencies
    data: string[]        // columns accessed on the active dataset
    datasets: Record<string, string[]>  // columns accessed per named dataset
  }
  systemPrompt?: string   // snapshot of the system prompt used
}

executeFormula(formula)

Renders a formula in the configured resultContainer. Creates the sandboxed iframe, applies sandboxPermissions, wires up the proxy fetch relay, and emits formula:done / formula:error. Called automatically by prompt() when resultContainer is set.

// Manual execution (e.g. replaying a catalog analysis)
assistant.executeFormula(savedAnalysis.formula)

destroy()

Removes the managed iframe and cleans up all event listeners. Call when the assistant is no longer needed.


Analysis match hook

Register a function that the assistant calls before each LLM request to find a semantically similar past analysis. The result is injected into the system prompt as an example, improving output quality for recurring analysis patterns.

assistant.setAnalysisMatchHook(async (query, context) => {
  // context: { history, datasets, activeDatasetName, activeColumns }
  const best = await mySemanticSearch(query, myCatalog)
  if (!best || best.score < 0.3) return null
  return { analysis: best.formula, score: best.score }
})

// Call it directly (e.g. to check before showing a suggestion UI):
const match = await assistant.resolveAnalysisMatch('Show portfolio heatmap')

Events

assistant.on('message', (response: AssistantResponse) => {
  console.log('Formula ready:', response.formula)
})

assistant.on('dataset:change', () => {
  renderTabBar(assistant.getDatasets())
})

// Persist LLM config whenever it changes
assistant.on('llm:change', (llm: LLMConfig) => {
  if (llm.apiKey) localStorage.setItem('llm-key', llm.apiKey)
})

// Persist API preferences whenever they change — covers activateApi, deactivateApi,
// setApiPreferences, and setApiUserKey (which encrypts BYOK keys before storing).
assistant.on('prefs:change', (prefs: ApiPreference[]) => {
  localStorage.setItem('api-prefs', JSON.stringify(prefs))
})

assistant.off('message', myListener)
Event Payload When
message AssistantResponse After each successful LLM response
dataset:change (none) After any dataset add / remove / update / clear
formula:done { data: unknown } Formula finished executing; data is the formula's returned data field
formula:error { message: string } Formula threw or called console.error
llm:change LLMConfig After setLlmApiKey() or assistant.llm = .... The payload contains the encrypted key — safe to persist and pass back via llm.apiKey at construction.
prefs:change ApiPreference[] After any API preference mutation (activateApi, deactivateApi, setApiPreferences, setApiUserKey). The payload contains encrypted BYOK keys — safe to persist as-is and pass back via apiPreferences at construction.
configs:change ApiConfig[] After API configs are loaded or updated via fetchApiConfigs() or setApiConfigs().
history:reset (none) Conversation history was cleared (e.g. because the active PDF changed).
api:blocked { url, hostname, apiConfig | null } Formula called a URL not in the activated API list and the request failed. apiConfig is set if the API is known but inactive, null if completely unknown.
api:error { url, hostname, apiConfig, reason } An active API returned a JSON error body (e.g. expired key, quota exceeded). reason is extracted from the response.
data:local { data: string, action: string } An action completed entirely in the browser. E.g. file loaded from disk, formula executed in sandbox. Emitted by the app layer.
data:proxy { data: string, action: string } Data was sent to the proxy server but not to the LLM. E.g. PDF extraction. Emitted by the app layer.
data:llm LlmDataPayload Data was forwarded to the LLM. Categorical payload — no display strings. kind is 'table', 'pdf', or 'text'; always includes query (raw user message) and dataset (file/dataset name). Table events include columns: number; PDF events include pages: number. Import the type: import type { LlmDataPayload } from '@localflow/core'.
data:api-proxy ApiProxyPayload A formula fetch was routed through the api-proxy — fired on every call (success and failure). Includes url, method, body (raw request body — what left the browser), apiConfig (matched API definition or null if unrecognised), and status (HTTP response status; undefined on network error). Import the type: import type { ApiProxyPayload } from '@localflow/core'.

Proxy API reference

Proxy is the interface that both LocalProxy and ProxyClient implement. LocalAssistant depends only on this interface — you can supply any conforming implementation.

import type { Proxy } from '@localflow/core'

interface Proxy {
  readonly token: string | null

  isConnected(): boolean
  connect(type?: string, config?: Record<string, unknown>): Promise<void>
  getSessionInfo(): Promise<unknown>

  isEncrypted(str: string): boolean
  encryptMessage(message: string): Promise<string>
  decryptMessage(message: string): Promise<string>

  callGenai(payload: GenaiPayload): Promise<Response>

  getApiConfigs(): Promise<ApiConfig[]>
  proxyApiCall(url: string, method: string, headers: Record<string, string>, body: string): Promise<Response>

  extractPdf(buffer: ArrayBuffer, searchString?: string): Promise<{ text: string; pageCount: number }>

  listObjectTypes(): Promise<CrmObjectType[]>
  getObjectMetadata(objectType: string): Promise<CrmObjectType>
  getData(objectType: string, fields: string[]): Promise<Record<string, unknown>[]>
}

LocalProxy

Browser-only implementation. No server required — suitable for local development, testing, and demos.

import { LocalProxy } from '@localflow/core'

new LocalProxy(config?: {
  apis?: ApiConfig[]
  geminiBaseUrl?: string
  geminiApiKey?: string          // baked-in key used when no key is set on the assistant
  rateLimit?: {
    maxPerDay: number            // per-browser daily cap (tracked in localStorage)
    storageKey?: string          // localStorage key prefix — defaults to '_lf_rl'
  }
})

geminiApiKey — a fallback key used when the assistant has no key set. Useful for demos where you want users to try the app without supplying their own key. The user's own key (set via assistant.setLlmApiKey()) always takes precedence.

rateLimit — per-browser daily cap enforced before each callGenai call. When the limit is reached, callGenai throws LocalProxyRateLimitError. Pair with geminiApiKey to prevent a single user from exhausting a shared demo key for everyone.

import { LocalProxy, LocalProxyRateLimitError, LocalAssistant } from '@localflow/core'

const proxy = new LocalProxy({
  geminiApiKey: 'AIza...',       // shared demo key — visible in DevTools, use a limited one
  rateLimit: { maxPerDay: 20 },  // generous enough to evaluate, stingy enough to protect the key
})
const assistant = new LocalAssistant({ proxy, llm: { type: 'gemini' } })

try {
  await assistant.prompt('Show me the top 10 by revenue')
} catch (err) {
  if (err instanceof LocalProxyRateLimitError) {
    // show UI asking the user to enter their own key
  }
}
Behaviour Notes
callGenai Checks rate limit, then calls Gemini directly from the browser. Uses geminiApiKey if no key is set on the assistant.
encryptMessage / decryptMessage No-ops — the key is stored and used as plain text
extractPdf Throws — PDF extraction is not available in standalone mode
listObjectTypes / getObjectMetadata / getData Return empty results — no CRM access
connect / getSessionInfo No-ops — no session management

Not for production use. LocalProxy emits a console warning to remind you.

ProxyClient

HTTP client for a LocalFlow proxy server. Implements the full Proxy interface and adds session management.

import { ProxyClient } from '@localflow/core'

const proxy = new ProxyClient(baseUrl, token?)
Parameter Type Description
baseUrl string LocalFlow proxy base URL (e.g. 'https://backoffice.daquota.io/v1')
token string | null Optional — restore a previously saved session token

Session

Method Returns Description
connect(type, config) Promise<void> Authenticate and store the session token. type is 'odoo', 'salesforce', or 'public'. config contains connector-specific credentials.
getSessionInfo() Promise<unknown> Verify the current session. Throws if expired or not authenticated.
isConnected() boolean true if a session token is stored.
proxy.token string | null The current session token — set by connect(), readable for persistence.
proxy.baseUrl string The proxy base URL — mutable, can be changed before calling connect().

Encryption

Method Returns Description
isEncrypted(str) boolean true if the string is in the proxy-encrypted format.
encryptMessage(plainText) Promise<string> Encrypt a string via the proxy. The result is safe to store and pass to the assistant.
decryptMessage(cipherText) Promise<string> Decrypt a proxy-encrypted string.

You rarely need to call these directly — assistant.setLlmApiKey() and assistant.setApiUserKey() handle encryption internally.

PDF extraction

Method Returns Description
extractPdf(buffer, searchString?) Promise<{ text, pageCount }> Extract text from a PDF via the proxy. Pass an optional searchString to receive only pages matching the query (plus one page of context either side).

CRM

Method Returns Description
listObjectTypes() Promise<CrmObjectType[]> List all available CRM object types (without fields).
getObjectMetadata(objectType) Promise<CrmObjectType> Fetch full metadata for one object type, including its fields.
getData(objectType, fields) Promise<Record<string, unknown>[]> Fetch rows for a CRM object type.
const types = await proxy.listObjectTypes()
const meta  = await proxy.getObjectMetadata('res.partner')
const rows  = await proxy.getData('res.partner', ['name', 'email', 'country_id'])

Building

cd localflow-core
npm install
npm run build   # tsc → dist/
npm run dev     # tsc --watch (development)

The standalone tsc build produces dist/ with .js and .d.ts files and is needed when publishing the library to npm. During development inside the monorepo, localflow-app uses a Vite alias pointing to the TypeScript source directly — no pre-build required.

Package structure

localflow-core/
├── package.json          # name: "@localflow/core"
├── tsconfig.json         # emits dist/ with .js + .d.ts
└── src/
    ├── index.ts          # public exports
    ├── LocalAssistant.ts # the core class
    ├── Proxy.ts          # Proxy interface contract
    ├── LocalProxy.ts     # browser-only Proxy implementation
    ├── ProxyClient.ts    # HTTP proxy client
    └── types.ts          # all public TypeScript interfaces

Roadmap highlights

  • Pluggable LLM backends (OpenAI, Anthropic, Mistral, Ollama)
  • Interactive formula results (action buttons returned by formulas)
  • Async / streaming formula execution

Recently shipped

  • Published to npm — available as @localflow/core
  • PDF document support — PDFs as first-class datasets; text extracted via the proxy, full document text injected into LLM context
  • Data flow awarenessdata:local / data:proxy / data:llm events; animated status chip in the header; session history popover; sandbox safety indicator
  • Configurable proxy URL — proxy URL configurable at runtime; persisted in localStorage
  • CRM connectors — Odoo and Salesforce authentication and data loading via ProxyClient
  • Formula self-healingformulaHealingRetries config option; syntax errors are caught and silently retried before returning to the caller (default: 1)

License

Apache 2.0 — see LICENSE for the full text.

Copyright (c) 2026 LocalFlow (localflow.fr)