ZeyOS Client & CLI
A dependency-light JavaScript client, a command-line tool, and agent guidance for integrating external tools with ZeyOS — the ERP/CRM platform. Read and write business data (tickets, accounts, tasks, projects, billing, and 50+ more resources) over the ZeyOS OpenAPI surface.
This repository ships two npm packages plus the docs, OpenAPI specs, sample apps, and coding-agent skills:
| Package | What it is | Install |
|---|---|---|
@zeyos/client |
Zero-runtime-dependency JS client (browser + Node 18+). Auto-generated methods for the full API. | npm install @zeyos/client |
@zeyos/cli |
The zeyos command — login, CRUD, schema introspection, agent-skill installer. |
npm install -g @zeyos/cli |
The authoritative, in-depth documentation lives in docs/. This README is the quick tour.
Table of contents
- Quick start
- Authentication & login
- CLI
- JavaScript client
- Using ZeyOS with a coding agent
- Sample apps
- Repository layout
- Documentation
- Testing
- Conventions & gotchas
- License
Quick start
From the command line
npm install -g @zeyos/cli
# Authenticate once (opens a browser for the OAuth flow)
zeyos login --base-url https://cloud.zeyos.com/demo --client-id myapp --secret "$ZEYOS_CLIENT_SECRET"
# Read and write
zeyos list tickets --filter '{"status":4}' --sort -lastmodified --limit 10
zeyos count tickets --filter '{"status":4}'
zeyos get ticket 42 --all
zeyos create ticket --name "Fix login bug" --status 0 --priority 3
zeyos update ticket 42 --status 9From JavaScript
import { createZeyosClient, MemoryTokenStore } from '@zeyos/client';
const client = createZeyosClient({
platform: 'https://cloud.zeyos.com/demo/',
auth: {
mode: 'oauth',
oauth: {
tokenStore: new MemoryTokenStore({ accessToken: process.env.ZEYOS_TOKEN }),
},
},
});
const tickets = await client.api.listTickets({
fields: ['ID', 'name', 'status', 'priority'],
filters: { visibility: 0 },
limit: 10,
});
platformis your instance URL:https://<host>/<instance>/. Forhttps://cloud.zeyos.com/demo/, the instance isdemo. The string'live'is a shorthand preset forhttps://cloud.zeyos.com.
Authentication & login
ZeyOS uses OAuth 2.0. You register an application in your ZeyOS instance to get a client ID and client secret, then obtain tokens via one of the flows below. The client also supports legacy session-cookie auth.
The client supports four auth modes:
| Mode | Behavior |
|---|---|
auto (default) |
Bearer token → auto-refresh on expiry/401 → session-cookie fallback |
oauth |
Bearer token only |
session |
Session cookies only |
none |
No authentication (public endpoints) |
Option 1 — Log in with the CLI
The easiest path. zeyos login runs the OAuth authorization-code flow: it prints the URLs, opens your browser, starts a local callback server to capture the redirect, and stores the resulting tokens.
zeyos login \
--base-url https://cloud.zeyos.com/demo \
--client-id myapp \
--secret "$ZEYOS_CLIENT_SECRET"
zeyos whoami # confirm you're authenticated- Omit any of
--base-url/--client-id/--secretto be prompted interactively (the secret prompt is masked). --globalstores credentials in~/.config/zeyos/credentials.jsoninstead of a local.zeyos/auth.json.--manualskips the browser and prompts you to paste the authorization code (useful over SSH).--forcere-authenticates even if a token is already stored;--cleandiscards the saved config and re-prompts for everything.
Tokens auto-refresh on use, and the refreshed token is written back to whichever config file you logged into. Add .zeyos/auth.json to your .gitignore — it holds credentials and tokens.
If a stored refresh token is invalid or expired, interactive zeyos whoami shows
where the stale credential came from and asks whether to re-authenticate.
Non-interactive and machine-readable runs print the corresponding zeyos login --force
command instead of prompting.
Option 2 — Programmatic OAuth (authorization-code flow)
For server-side apps that run their own OAuth flow:
import { createZeyosClient } from '@zeyos/client';
const client = createZeyosClient({
platform: 'https://cloud.zeyos.com/demo/',
auth: { mode: 'oauth', oauth: { clientId: 'myapp', clientSecret: process.env.ZEYOS_CLIENT_SECRET } },
});
// 1. Send the user to the authorization URL
const authUrl = client.oauth2.buildAuthorizationUrl({
redirectUri: 'https://myapp.example.com/callback',
scope: 'all',
state: 'csrf-token-here',
});
// 2. On your callback route, exchange the code for tokens (stored in the token store)
const { code } = client.oauth2.parseAuthorizationCallback(req.url);
await client.oauth2.exchangeAuthorizationCode({
code,
redirectUri: 'https://myapp.example.com/callback',
});
// 3. Make authenticated calls — tokens refresh automatically when they expire
const me = await client.oauth2.getUserInfo();PKCE is supported by passing codeChallenge / codeChallengeMethod to buildAuthorizationUrl and codeVerifier to exchangeAuthorizationCode.
Option 3 — Use an access token directly
If you already have an access token (and optionally a refresh token), seed a MemoryTokenStore:
import { createZeyosClient, MemoryTokenStore } from '@zeyos/client';
const client = createZeyosClient({
platform: 'https://cloud.zeyos.com/demo/',
auth: {
mode: 'oauth',
oauth: {
clientId: 'myapp',
clientSecret: process.env.ZEYOS_CLIENT_SECRET, // required for auto-refresh
tokenStore: new MemoryTokenStore({
accessToken: process.env.ZEYOS_TOKEN,
refreshToken: process.env.ZEYOS_REFRESH_TOKEN,
}),
},
},
});Implement your own persistent token store by providing an object with async get() and async set(tokenSet) — see server-side integrations.
CLI credential resolution
The CLI resolves credentials in this order (first match wins, field by field):
- Environment variables —
ZEYOS_BASE_URL,ZEYOS_INSTANCE,ZEYOS_CLIENT_ID,ZEYOS_CLIENT_SECRET,ZEYOS_TOKEN,ZEYOS_REFRESH_TOKEN - Local
.zeyos/auth.json(found by walking up from the current directory, like.git) - Global
~/.config/zeyos/credentials.json
Environment variables make CI and ephemeral agent environments easy:
export ZEYOS_BASE_URL=https://cloud.zeyos.com/demo
export ZEYOS_TOKEN=your-access-token
zeyos list accounts --limit 5CLI
Install globally (npm install -g @zeyos/cli) or run from this repo with node cli/bin/zeyos.mjs <command>.
zeyos <command> [options] [args…]
| Command | What it does | Example |
|---|---|---|
login |
OAuth login, stores tokens | zeyos login --base-url https://cloud.zeyos.com/demo --client-id myapp --secret $S |
logout |
Revoke session and clear stored credentials | zeyos logout |
whoami |
Show the authenticated user | zeyos whoami --json |
list <resource> |
List / query records | zeyos list tickets --filter '{"status":4}' --sort -lastmodified |
count <resource> |
Count records (true total) | zeyos count tickets --filter '{"status":4}' |
get <resource> <id> |
Fetch one record (show is an alias) |
zeyos get ticket 42 --all |
create <resource> |
Create a record | zeyos create ticket --name "Bug" --status 0 --priority 3 |
update <resource> <id> |
Update a record (edit is an alias) |
zeyos update ticket 42 --status 9 |
delete <resource> <id> |
Delete a record (rm/remove aliases) |
zeyos delete ticket 42 --force |
resources |
List resource types the CLI exposes | zeyos resources --json |
describe <resource> |
Show a resource's fields, types and enums | zeyos describe ticket |
skills <list|show|install> |
Manage bundled coding-agent skills | zeyos skills install --target claude --global |
okf <list|show|check|export|build> |
Work with the OKF knowledge bundle | zeyos okf show tickets |
Global options (work on every command): --json, --yaml, --no-color, -h/--help, -v/--version.
Querying
# Field selection: comma list, JSON array, or JSON object (with aliasing + dot-notation joins)
zeyos list tickets --fields ID,name,status --limit 10
zeyos list accounts --fields '{"Name": "lastname", "City": "contact.city"}'
# Filtering (JSON object) and sorting (prefix - for descending)
zeyos list tickets --filter '{"status":4,"priority":4}' --sort -lastmodified
# Pagination
zeyos list tickets --limit 100 --offset 100
zeyos listdefaults to--limit 50and prints aShowing X–Y of TOTALhint to stderr when the result is truncated. To count records, usezeyos count <resource>— counting the rows of alistonly counts the current page.
Creating & updating
Pass fields as individual flags or as a JSON blob:
zeyos create ticket --name "Fix login bug" --status 0 --priority 3
zeyos create account --lastname "Acme Corp" --currency EUR --type 1
zeyos update ticket 42 --status 9 --data '{"priority":4}'--json / --yaml switch any command to machine-readable output, which is what you want for scripting and agents:
zeyos list tickets --filter '{"status":4}' --json | jq '.[].name'Full CLI reference: docs/03-cli.
JavaScript client
createZeyosClient(config) returns a frozen client. Every API operation is available as a method under client.api.* (and client.oauth2.*, client.legacyAuth.*).
import {
createZeyosClient,
MemoryTokenStore,
ZeyosApiError,
normalizeListResult,
normalizeCountResult,
} from '@zeyos/client';
const client = createZeyosClient({
platform: 'https://cloud.zeyos.com/demo/',
auth: { mode: 'oauth', oauth: { tokenStore: new MemoryTokenStore({ accessToken: TOKEN }) } },
});CRUD
// List with field selection, filters, and sorting
const tickets = await client.api.listTickets({
fields: ['ID', 'name', 'status', 'priority', 'lastmodified'],
filters: { visibility: 0, status: 4 },
sort: ['-lastmodified'],
limit: 25,
});
// Count (count is a boolean flag)
const open = await client.api.listTickets({ filters: { visibility: 0, status: 4 }, count: true });
const total = normalizeCountResult(open);
// Fetch one
const ticket = await client.api.getTicket({ ID: 42 });
// Create (returns the new record)
const created = await client.api.createTicket({ name: 'Fix login bug', status: 0, priority: 3 });
// Update — flat spread or an explicit body both work
await client.api.updateTicket({ ID: 42, status: 9 });
// Delete
await client.api.deleteTicket({ ID: 42 });Normalizing responses
List endpoints return either a plain array or a { data, count } wrapper depending on the call. normalizeListResult smooths that over:
const raw = await client.api.listAccounts({ filters: { visibility: 0 } });
const { data, count } = normalizeListResult(raw); // data is always an arraySchema introspection
client.schema is a read-only view of resources, fields, enums, and operations — handy for building UIs and for agents that need to self-correct:
client.schema.resources(); // ['accounts', 'tickets', ...]
client.schema.fields('tickets'); // ['ID', 'name', 'status', ...]
client.schema.describe('accounts'); // { name, type, fields }
client.schema.operationIds(); // every callable operationId
// Opt-in pre-flight validation: catches unknown fields, filter/filters
// spelling, bad enum values, and required-create fields before the request.
const result = client.schema.validate('createAccount', { lastname: 'Acme' });
// → { valid: false, errors: [{ field: 'currency', message: 'Missing required field "currency" …' }] }Enable validation on every call with createZeyosClient({ validate: true }), or per call with client.api.createAccount(input, { validate: true }) (throws ZeyosValidationError).
Error handling
Non-2xx responses throw a ZeyosApiError carrying the full response context:
try {
await client.api.getTicket({ ID: 999999 });
} catch (err) {
if (err instanceof ZeyosApiError) {
console.error(err.status, err.statusText); // 404 'Not Found'
console.error(err.body); // parsed server response
console.error(err.operationId, err.url); // 'getTicket', full URL
}
}Calling an operation that doesn't exist throws a helpful ZeyosApiError with a "did you mean …?" suggestion instead of an opaque TypeError.
Retries & low-level access
- Retries: 429/503 are retried automatically with exponential backoff that honors
Retry-After. Configure withretry: { maxRetries, retryOn, baseDelayMs, maxDelayMs }, or disable withretry: false. - Escape hatch:
client.request({ service, operationId, ... })orclient.request({ service, method, path, ... })for anything the generated methods don't cover. Pass{ raw: true }to get the full{ status, headers, data }response.
Full client reference: docs/02-javascript-client. For battle-tested patterns and gotchas, see the Practical Guide.
Using ZeyOS with a coding agent
ZeyOS ships agent skills — curated instructions and query playbooks that teach a coding agent (Claude Code, Codex, etc.) how to operate against ZeyOS with the right conventions out of the box. This is the fastest way to let an agent read and write your business data correctly.
1. Install the skills into your project
zeyos skills list # see what's available
zeyos skills install # interactive: pick an agent, then local vs. global
zeyos skills install --target claude --global # or skip the prompts with flags
zeyos skills install zeyos-work-management # install just one skillRun bare, install shows the ZeyOS banner and asks (a) which coding agent to target and (b) whether to install for this project or globally. Flags skip the prompts:
--target <agent>—claude,codex,opencode,droid,pi, oragents(auto-detected when omitted)--global/--local— install into the agent's home directory or just this project (default--local)--dir <path>— install into any directory you choose (overrides--target)-y/--yes— skip all prompts and use flags + defaults (also implied when piped non-interactively)
| Agent | local | global |
|---|---|---|
claude |
.claude/skills/ |
~/.claude/skills/ |
codex |
.codex/skills/ |
~/.codex/skills/ |
opencode |
.opencode/skills/ |
~/.config/opencode/skills/ |
droid |
.factory/skills/ |
~/.factory/skills/ |
pi |
.pi/skills/ |
~/.pi/agent/skills/ |
agents |
.agents/skills/ |
~/.agents/skills/ |
Shared reference docs are installed alongside so the skills' cross-links resolve. Point your agent at the install directory.
Bundled skills:
| Skill | Focus |
|---|---|
zeyos-work-management |
Tickets, tasks, projects, action steps, assignees, workload |
zeyos-time-tracking |
First-person work views and interactive time logging (effort as action steps) |
zeyos-account-intelligence |
Accounts, contacts, addresses, opportunities |
zeyos-billing-insights |
Transactions, invoices, credits, payments, revenue |
zeyos-collections-and-dunning |
Overdue receivables, dunning notices, collection workflows |
zeyos-commerce-and-inventory |
Items, pricing, price lists, stock, suppliers |
zeyos-procurement-and-supplier-performance |
Supplier comparison, procurement orders/deliveries/invoices, lead times |
zeyos-campaign-and-outreach |
Campaigns, mailing lists, outbound mailings |
zeyos-collaboration-and-activity |
Timelines, comments, followers, channels, files, events |
zeyos-mail-operations |
Querying, summarizing, and drafting email/message records |
zeyos-notes-and-sops |
Notes, SOPs, documents, file-backed knowledge |
zeyos-document-and-approval |
Formal document status, approval/finalization gates, note-vs-SOP |
zeyos-calendar-and-scheduling |
Appointments, availability/conflicts, scheduling, invitations |
zeyos-data-quality-and-governance |
Duplicate detection, completeness gaps, safe remediation previews |
zeyos-platform-and-schema |
Platform/admin entities, schema, custom fields |
2. Give the agent the CLI as its tool
For most agent workflows, the CLI with --json is the right interface: stable machine-readable output, built-in auth, and a safe delete confirmation. The agent runs zeyos commands and pipes JSON through jq or parses it directly.
zeyos describe ticket --json # discover fields & enums
zeyos list tickets --filter '{"status":4}' --json
zeyos count accounts --filter '{"type":1}'When a task needs a resource or request shape the CLI doesn't expose, the agent escalates to @zeyos/client, which covers the full generated API surface.
See Agent Workflows: quickstart, recipes, and CLI coverage & escalation.
Open Knowledge Format (OKF)
The client ships a conformant Open Knowledge Format
bundle under okf/ — a portable, Git-native Markdown description of the ZeyOS data
model (one concept per API-backed entity: schema, foreign keys, enums, indexes, operationIds)
plus curated metrics, playbooks, and query concepts. Agents and tools read it as a shared
knowledge layer, independent of this client.
zeyos okf list # browse concepts
zeyos okf show tickets # print a concept (schema + curated notes)
zeyos okf check # validate OKF v0.1 conformance
zeyos okf export --out ./okf # vendor the bundle into your projectimport { buildOkf, loadOkfBundle, validateOkfBundle } from '@zeyos/client';
const files = buildOkf(); // synthesize from the client schema
const bundle = await loadOkfBundle('node_modules/@zeyos/client/okf');The bundle is generated from the OpenAPI/dbref specs into managed blocks, with curated
prose preserved across regeneration, and it is canonical for structural facts (the shared
skill references derive from it). It is regenerated by npm run generate and gated by
npm run okf:check. See the OKF docs for the bundle layout,
the spec-refresh runbook, and how it ties into the skill-improvement loops.
Sample apps
Three runnable, dependency-free browser demos live in samples/:
- Kanban — drag-and-drop ticket board (status updates, detail view).
- CRM — contact list with dot-notation joins, full-text search, sortable columns, pagination, inline editing.
- Dashboard — KPI cards and charts built from parallel
countqueries.
Each can be served as static files from the repository root; see the linked docs for run and configuration instructions.
Repository layout
src/— the@zeyos/clientJavaScript client (src/runtime/is hand-written;src/generated/is generated from the OpenAPI specs).cli/— the@zeyos/clicommand-line tool.docs/— the authoritative documentation (Docusaurus).agents/— repo-local ZeyOS agent skills and query playbooks.okf/— the Open Knowledge Format bundle: a portable, Git-native Markdown description of the ZeyOS data model (canonical for structural facts).openapi/— the OpenAPI specifications and DB schema reference.samples/— the sample browser applications.scripts/— client + OKF generation (generate-client.mjs,generate-okf.mjs) and the test runner.
Documentation
| Section | Covers | Entry point |
|---|---|---|
| API Reference | Query language, OAuth2, every resource endpoint, field schema | docs/01-api-reference |
| JavaScript Client | Install, auth modes, CRUD, filtering, schema, retries, errors, patterns | docs/02-javascript-client |
| CLI | Install, login, all commands, config & field display | docs/03-cli |
| Agent Workflows | CLI-first agent orientation, JSON recipes, escalation | docs/04-agent-workflows |
| Sample Apps | Kanban, CRM, Dashboard walkthroughs | docs/04-sample-apps |
| Tutorials | Architecture guide, build-your-own frontend, server-side integration | docs/05-tutorials |
| Open Knowledge Format | The OKF knowledge bundle: overview, producing/consuming, keeping it fresh, refinement loops | docs/06-okf |
| Agent Skills | What the bundled skills do and how they're organized | agents/README.md |
Testing
npm test # offline unit + schema tests (mocked fetch)
npm test -- --live # adds a live OAuth smoke test (needs config.test.json)
npm run test:cli-integration # live CLI CRUD lifecycle (requires `zeyos login`)
npm run test:agent-protocol # agent-driven live protocol; --dry-run to verify wiring firstThe CLI has its own offline suite: node --test cli/test/offline.mjs.
The agent test protocol drives a coding agent against a live instance and uses a model-rotation rule to separate real client defects from model flakiness. See test/agent-protocol/PROTOCOL.md.
Conventions & gotchas
A few platform facts that save debugging time (full list in the Practical Guide):
- Use
filters(plural), notfilter, in client/CLI code.filtersalso matches GIN-indexed foreign-key fields (account,project,ticket);filtersilently ignores them. - Always include
visibility: 0in filters unless you want archived (1) or deleted (2) records. - Dates are Unix timestamps in seconds, not milliseconds (
new Date(value * 1000)). createAccountrequirescurrency(e.g."EUR") — it'sNOT NULLwith no default and otherwise fails with an opaque HTTP 500. Accounts uselastname/firstname(there is nonamefield) andtype(notaccounttype).operationIds are CamelCase compounds that don't always match DB table names (e.g.dunning→listDunningNotices). Useclient.schema.operationIds()orzeyos resourcesto discover them.
License
MIT — see LICENSE.