@zeyos/client 0.5.0 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -3,6 +3,43 @@
3
3
  Notable changes to `@zeyos/client` and `@zeyos/cli`. This project follows
4
4
  [Semantic Versioning](https://semver.org/).
5
5
 
6
+ ## 0.6.0
7
+
8
+ ### `@zeyos/cli` (`zeyos`)
9
+ - Added direct resource coverage and aliases for common API nouns whose generated
10
+ operationIds differ from user-facing names, including group/user junctions,
11
+ mailing lists/recipients, price lists, price-list account junctions, prices,
12
+ and dunning junction aliases.
13
+ - Expanded forgiving filter normalization for agent and shell workflows:
14
+ Mongo-style operators, bare comparison operators, array-to-`IN`, suffix forms
15
+ such as `field__in`/`field__nin`/`field__like`, and negative-set filters now
16
+ normalize to the native ZeyOS request shape and remain visible in dry-run JSON.
17
+ - Expanded `zeyos sum` coverage and regression tests for actionsteps, payments,
18
+ and transactions, including the documented actionsteps oversized-page failure
19
+ mode.
20
+
21
+ ### Agent skills (shipped)
22
+ - The generic ZeyOS entrypoint is slash-command-free and explicitly supports direct
23
+ execution for simple counts once the resource and filter constraints are clear.
24
+ - Shared guidance now prefers `zeyos sum` for simple ungrouped totals while keeping
25
+ manual aggregation for grouped, joined, or conditional totals.
26
+
27
+ ### Agent test protocol (dev-only)
28
+ - The fixed benchmark mode is now DeepSeek-only
29
+ (`openrouter/deepseek/deepseek-v4-flash`) and defaults to one-attempt strict
30
+ data (`--transient-retries 0`), while normal report runs can still use transient
31
+ retries.
32
+ - Added efficiency budgets for direct count, b14, b22, dunning, mail, and sum
33
+ scenarios; pass-but-expensive cases are now separated from correctness defects
34
+ in Markdown and HTML scorecards.
35
+ - Added transcript leakage detection for user-home/global skill paths and classifies
36
+ those runs as `ENVIRONMENT_DEFECT`.
37
+ - Hardened RESULT parsing/output-contract checks for Markdown-wrapped markers and
38
+ file-output mistakes.
39
+ - Added complex collections and mail regression scenarios and coverage for minimal
40
+ query shape, zero API errors, API-call budgets, provider/runner failure clarity,
41
+ and token/cost capture.
42
+
6
43
  ## 0.5.0
7
44
 
8
45
  ### `@zeyos/cli` (`zeyos`)
package/README.md CHANGED
@@ -42,6 +42,7 @@ zeyos login --base-url https://cloud.zeyos.com/demo --client-id myapp --secret "
42
42
  # Read and write
43
43
  zeyos list tickets --filter '{"status":4}' --sort -lastmodified --limit 10
44
44
  zeyos count tickets --filter '{"status":4}'
45
+ zeyos sum actionsteps effort --filter '{"status":[1,3]}'
45
46
  zeyos get ticket 42 --all
46
47
  zeyos create ticket --name "Fix login bug" --status 0 --priority 3
47
48
  zeyos update ticket 42 --status 9
@@ -200,6 +201,7 @@ zeyos <command> [options] [args…]
200
201
  | `whoami` | Show the authenticated user | `zeyos whoami --json` |
201
202
  | `list <resource>` | List / query records | `zeyos list tickets --filter '{"status":4}' --sort -lastmodified` |
202
203
  | `count <resource>` | Count records (true total) | `zeyos count tickets --filter '{"status":4}'` |
204
+ | `sum <resource> <field>` | Sum a numeric field across matching records | `zeyos sum actionsteps effort --filter '{"status":[1,3]}'` |
203
205
  | `get <resource> <id>` | Fetch one record (`show` is an alias) | `zeyos get ticket 42 --all` |
204
206
  | `create <resource>` | Create a record | `zeyos create ticket --name "Bug" --status 0 --priority 3` |
205
207
  | `update <resource> <id>` | Update a record (`edit` is an alias) | `zeyos update ticket 42 --status 9` |
@@ -221,6 +223,10 @@ zeyos list accounts --fields '{"Name": "lastname", "City": "contact.city"}'
221
223
  # Filtering (JSON object) and sorting (prefix - for descending)
222
224
  zeyos list tickets --filter '{"status":4,"priority":4}' --sort -lastmodified
223
225
 
226
+ # Agent-friendly filter normalization: arrays become IN, Mongo/suffix operators
227
+ # are normalized before the request is sent and are visible with --query --json.
228
+ zeyos list tickets --filter '{"status":{"$nin":[8,9,10]},"priority":[3,4]}' --query --json
229
+
224
230
  # Pagination
225
231
  zeyos list tickets --limit 100 --offset 100
226
232
  ```
@@ -41,21 +41,29 @@ and `zeyos resources` are offline and safe for orienting yourself.
41
41
  When you only have this skill text and a shell, keep the loop small:
42
42
 
43
43
  1. Pick the resource from the domain workflow.
44
- 2. If the question says "how many", run `zeyos count …` first.
44
+ 2. If the question says "how many" and one filtered resource directly answers it, run
45
+ `zeyos count …` first. Joined/anti-join counts (for example unanswered mail, missing
46
+ related records, or "with no later reply") need the domain workflow instead.
45
47
  3. Put filters inline as single-quoted JSON: `--filter '{"visibility":0}'`.
46
48
  4. If a field is uncertain, run `zeyos describe <resource>` before filtering on it.
47
49
  5. Never answer from a plan. Run the command, read stdout/stderr, then report the result.
48
50
 
51
+ For a simple single-resource count, a successful `zeyos count <resource> ...` is the
52
+ answer. Do not follow it with `zeyos list` just to verify the number; listing costs an
53
+ extra API call, can hit server limits, and is only needed when the user asks for the
54
+ records or the count fails.
55
+
49
56
  ## First move for the common question shapes
50
57
 
51
58
  | The user asks… | Your first command |
52
59
  |----------------|--------------------|
53
- | "How many X …?" | `zeyos count <resource> --filter '{…}'` |
60
+ | "How many X …?" where one resource/filter answers it | `zeyos count <resource> --filter '{…}'` |
54
61
  | "List / show X …" | `zeyos list <resource> --filter '{…}' --fields … --json` |
55
62
  | "Details of record N" | `zeyos get <resource> <id> --json` |
56
63
  | "What fields / enums does X have?" | `zeyos describe <resource>` |
57
64
  | "Is resource X even available?" | `zeyos resources --json` |
58
- | A total / sum (e.g. revenue) | `zeyos list <resource> --filter '{…}' --fields … --limit 10000 --json`, then sum client-side |
65
+ | A simple numeric total / sum | `zeyos sum <resource> <field> --filter '{…}'` |
66
+ | A grouped or joined total | `zeyos list <resource> --filter '{…}' --fields … --limit 10000 --json`, then aggregate client-side |
59
67
  | "Will this request do what I think?" | append `--query` to any data command to print the route + JSON body **without sending it** (preview a write before running it) |
60
68
 
61
69
  Then read [zeyos-query-patterns.md](./zeyos-query-patterns.md) for the rules that make
@@ -71,6 +79,7 @@ the matching domain skill for the metric definitions.
71
79
  - Prefer inline JSON for small filters. For complex filters, `zeyos list` and
72
80
  `zeyos count` support `--filter-file <path>`, while `zeyos create` and
73
81
  `zeyos update` support `--data-file <path>`.
82
+ - In CLI filters, arrays normalize to `IN`: `--filter '{"status":[1,3]}'`.
74
83
  - Do not pass `@filter.json` or any other response-file syntax; use the documented
75
84
  `--filter-file` / `--data-file` flags when a file is safer than inline JSON.
76
85
  - For counts, use `zeyos count <resource>` rather than `zeyos list … --json | length`.
@@ -128,7 +137,7 @@ contract above as checkable invariants.
128
137
  - **R-010 External side effects are high risk.** Email, campaign, dunning and calendar-invitation dispatch are never automatic. In protocol tests they are prohibited; interactively they require sender/audience/content/time preview and confirmation.
129
138
  - **R-011 High-impact state transitions.** Final, approved, booked, paid, cancelled, archived, deleted, sent and similar terminal states require exact target preview and confirmation.
130
139
  - **R-012 Conditional visibility.** Use `visibility:0` only when the resource exposes `visibility`; inspect the schema first.
131
- - **R-013 Count and sum discipline.** Use server-side count for counts. For sums, page through every matching row and aggregate the documented numeric field client-side.
140
+ - **R-013 Count and sum discipline.** Use server-side count for counts. For simple ungrouped sums, use `zeyos sum`; for grouped, joined, or conditional sums, page through every matching row and aggregate the documented numeric field client-side.
132
141
  - **R-014 Time and timezone discipline.** ZeyOS timestamps are Unix seconds. State timezone, window boundaries and whether the end is inclusive or exclusive.
133
142
  - **R-015 Operation discovery.** Use `zeyos resources`, `zeyos describe`, `client.schema`, or OKF operation metadata before guessing resource names or operationIds.
134
143
  - **R-016 Financial basis separation.** Invoices, credits, payments, receivables and dunning are different facts. State invoiced/cash/collection basis, currency and credit treatment.
@@ -41,10 +41,11 @@ The DB-table noun (from `dbref.json`, also the REST URL path segment) is **not**
41
41
  operationId. The `@zeyos/client` methods and the names you reason about are CamelCase
42
42
  compound operationIds, and several diverge from a naive "capitalize + pluralize the noun".
43
43
 
44
- **Agent rule: when calling `@zeyos/client` (`client.api.<operationId>(...)`) or constructing
45
- CLI resource names, use the operationIds below, not the raw `dbref.json` table noun.** Building
46
- `client.api.listDunning(...)` or `zeyos list dunning` from the noun will fail with
47
- "operation not found".
44
+ **Agent rule: when calling `@zeyos/client` (`client.api.<operationId>(...)`), use the
45
+ operationIds below, not the raw `dbref.json` table noun.** Building
46
+ `client.api.listDunning(...)` from the noun will fail with "operation not found". The CLI
47
+ accepts curated resource aliases for common divergent nouns, including
48
+ `zeyos count/list dunning` and `zeyos list dunning2transactions`.
48
49
 
49
50
  ### The regular rule (most entities)
50
51
 
@@ -32,6 +32,15 @@ For cross-platform benchmark guidance, read [business-app-benchmarks.md](./busin
32
32
  - CLI filters are inline JSON strings. Use `--filter '{"field":123}'`; never run the raw
33
33
  JSON as a shell command, and do not use `@filter.json` unless the CLI help explicitly
34
34
  documents response-file support.
35
+ - CLI filter arrays normalize to `IN`, e.g. `--filter '{"status":[1,3]}'`.
36
+ - CLI filters also normalize common negative-set aliases to native `!IN`, e.g.
37
+ `--filter '{"status":{"$nin":[8,9,10]}}'`.
38
+ - Do not invent filter operators. For text search use ZeyOS' documented
39
+ case-insensitive LIKE operator, e.g. `{"lastname":{"~~*":"%Bureau3%"}}`; do not use
40
+ `contains`, `like`, or `ilike` unless `zeyos describe` documents that exact operator.
41
+ - For numeric/date comparisons, prefer native ZeyOS operators such as `<`, `<=`, `>`,
42
+ and `>=`. The CLI also normalizes common Mongo-style range aliases (`$lt`, `$lte`,
43
+ `$gt`, `$gte`) before sending, but use the native operators in examples and docs.
35
44
  - Creating accounts requires `currency` (e.g. `"EUR"`): the column is NOT NULL with no DB default, so a create that omits it fails with an opaque HTTP 500 even though the OpenAPI spec does not mark it required. `validate('createAccount', …)` now catches this; supply a currency code. (The spec carries no required-field metadata at all, so unknown required fields can still surface only as a server-side 500 — when a create 500s, suspect a missing NOT-NULL column.)
36
45
  - Use `visibility: 0` on resources that expose a `visibility` field, unless the user explicitly wants archived or deleted records. Not every resource has the column: `tickets`, `accounts`, and `items` do; **`transactions` does not — filtering `visibility` there returns an opaque HTTP 400**. More generally, filtering on any column a resource lacks 400s with no hint which field was wrong, so filter only on fields `zeyos describe <resource>` lists.
37
46
  - Treat list operations as `POST` queries.
@@ -44,6 +53,10 @@ For cross-platform benchmark guidance, read [business-app-benchmarks.md](./busin
44
53
  - `extdata` exposes custom fields
45
54
  - `expand` inlines JSON or binary columns
46
55
  - For a "how many?" question, count server-side: `zeyos count <resource>` on the CLI, or pass `count: true` to the list call on the client (e.g. `client.api.listItems({ filters: { visibility: 0 }, count: true })`). Never use `list` + array length. `zeyos list` defaults to `--limit 50` (the client default is 1000), so counting listed rows silently returns the page size, not the total. In `--json` mode the only truncation signal is a stderr "Showing X–Y of TOTAL" hint.
56
+ - After a successful `zeyos count`, stop and report the count. Do not run `zeyos list` as a second check unless the user also asked for records or the count command failed.
57
+ - For a simple numeric total, use `zeyos sum <resource> <field> --filter '{...}'`; it
58
+ pages internally. Use `list` and aggregate yourself only for grouped, conditional,
59
+ joined, or per-row outputs.
47
60
  - Treat `count: true` responses defensively because wrappers vary across resources and client layers.
48
61
  - Confirm delete, send, revoke, or bulk-update actions before executing them unless the workflow is already explicitly automated.
49
62
 
@@ -68,7 +81,7 @@ Use these defaults unless the target instance clearly behaves differently:
68
81
  ## Default Resolution Patterns
69
82
 
70
83
  - Resolve a user with `users.name` or `users.email` first.
71
- - Resolve a customer with `accounts.customernum`, `accounts.lastname`, `accounts.firstname`, then `contacts.email` or contact name if needed.
84
+ - Resolve a customer with `accounts.customernum`, `accounts.lastname`, `accounts.firstname`, then `contacts.email` or contact name if needed. Company names live in `accounts.lastname`; there is no generic `accounts.name` column.
72
85
  - Resolve a project with `projects.projectnum` or `projects.name`.
73
86
  - Resolve a ticket with `tickets.ticketnum` or `tickets.name`.
74
87
  - Resolve a task with `tasks.tasknum` or `tasks.name`.
@@ -6,13 +6,47 @@ description: Read or change ZeyOS business data (accounts, contacts, tickets, ta
6
6
  # Working with ZeyOS via the CLI
7
7
 
8
8
  This is the generic, do-it-now skill for talking to a ZeyOS instance. The specialized
9
- `zeyos-*` skills add metric definitions and domain rules; this one just makes sure you
10
- **run real commands against the live instance and answer from the result**.
9
+ `zeyos-*` skills add metric definitions and domain rules; this one routes to the right
10
+ guide, makes sure you **run real commands against the live instance**, and answers from
11
+ the result.
11
12
 
12
13
  Read [../shared/zeyos-agent-operating-guide.md](../shared/zeyos-agent-operating-guide.md)
13
14
  first (it establishes that you have tools and the CLI is already authenticated), then
14
15
  [../shared/zeyos-query-patterns.md](../shared/zeyos-query-patterns.md) for the query rules.
15
16
 
17
+ ## Route first, then execute
18
+
19
+ When this guide is used, inspect the user request and read the matching specialized skill
20
+ before querying. Do not depend on a runner-global slash command, do not ask the user to
21
+ pick the skill, and do not answer from this generic guide if the request needs domain
22
+ rules.
23
+
24
+ | Request area | Read this guide |
25
+ | --- | --- |
26
+ | Accounts, customers, contacts, CRM profile, account type, relationship lookup | [../zeyos-account-intelligence/SKILL.md](../zeyos-account-intelligence/SKILL.md) |
27
+ | Tickets, tasks, projects, actionstep queues, workload, third-person effort summaries | [../zeyos-work-management/SKILL.md](../zeyos-work-management/SKILL.md) |
28
+ | First-person work ("my ...") or logging/booking time | [../zeyos-time-tracking/SKILL.md](../zeyos-time-tracking/SKILL.md) |
29
+ | Transactions, invoices, delivery notes, revenue, payments, billing documents | [../zeyos-billing-insights/SKILL.md](../zeyos-billing-insights/SKILL.md) |
30
+ | Dunning notices, receivables follow-up, collection state | [../zeyos-collections-and-dunning/SKILL.md](../zeyos-collections-and-dunning/SKILL.md) |
31
+ | Items, products, catalog, stock, inventory, orders | [../zeyos-commerce-and-inventory/SKILL.md](../zeyos-commerce-and-inventory/SKILL.md) |
32
+ | Mail, inbound/outbound messages, drafts, unanswered ticket mail | [../zeyos-mail-operations/SKILL.md](../zeyos-mail-operations/SKILL.md) |
33
+ | Campaigns, mailing lists, outreach recipients, message reads | [../zeyos-campaign-and-outreach/SKILL.md](../zeyos-campaign-and-outreach/SKILL.md) |
34
+ | Activity events, timeline, collaboration history | [../zeyos-collaboration-and-activity/SKILL.md](../zeyos-collaboration-and-activity/SKILL.md) |
35
+ | Notes, SOPs, knowledge retrieval | [../zeyos-notes-and-sops/SKILL.md](../zeyos-notes-and-sops/SKILL.md) |
36
+ | Documents, approval gates, official/latest file state | [../zeyos-document-and-approval/SKILL.md](../zeyos-document-and-approval/SKILL.md) |
37
+ | Calendar availability, scheduling, appointment creation | [../zeyos-calendar-and-scheduling/SKILL.md](../zeyos-calendar-and-scheduling/SKILL.md) |
38
+ | Custom fields, schema/admin resources, operationId traps, platform model lookup | [../zeyos-platform-and-schema/SKILL.md](../zeyos-platform-and-schema/SKILL.md) |
39
+ | Supplier scorecards, procurement, supplier delivery performance | [../zeyos-procurement-and-supplier-performance/SKILL.md](../zeyos-procurement-and-supplier-performance/SKILL.md) |
40
+ | Duplicate accounts, null/empty/missing checks, remediation previews | [../zeyos-data-quality-and-governance/SKILL.md](../zeyos-data-quality-and-governance/SKILL.md) |
41
+
42
+ If multiple domains apply, read each relevant specialized guide plus
43
+ [../shared/zeyos-entity-map.md](../shared/zeyos-entity-map.md), then choose the smallest
44
+ query plan that answers the user.
45
+
46
+ For simple counts where the user already names the resource and filter constraints, run
47
+ the direct `zeyos count <resource> --filter '{...}'` command and stop after the successful
48
+ count. Do not add a discovery round-trip unless the resource or field is unclear.
49
+
16
50
  ## Do this, don't just describe it
17
51
 
18
52
  When asked anything about ZeyOS data, **run a `zeyos` command and report what it
@@ -33,6 +67,7 @@ zeyos resources --json # list every available resource type
33
67
  zeyos describe <resource> # fields, types, enums, foreign keys (offline)
34
68
 
35
69
  zeyos count <resource> --filter '{"status":1}'
70
+ zeyos sum <resource> <field> --filter '{"status":[1,3]}'
36
71
  zeyos list <resource> --filter '{…}' --fields ID,name,status --sort -lastmodified --limit 50 --json
37
72
  zeyos get <resource> <id> --json # single record (alias: show)
38
73
 
@@ -55,13 +90,22 @@ you intend before hitting the live instance — especially before any write. Add
55
90
  - **Counting:** use `zeyos count <resource>`. Do **not** `zeyos list` and count rows —
56
91
  `list` defaults to `--limit 50`, so you get the page size, not the total. In `--json`
57
92
  the only truncation signal is a stderr `Showing X–Y of TOTAL` hint.
58
- - **Totals / sums:** there is no server-side sum. `list` the matching records with the
59
- numeric field and a high `--limit` (up to 10000), then add them up yourself.
93
+ - **Stop after count:** for a simple "how many" task, a successful `zeyos count` is
94
+ sufficient. Do not list records afterward unless the user asks for them or the count
95
+ command fails.
96
+ - **Joined counts are not simple counts:** questions such as unanswered mail, missing
97
+ related records, or "with no later reply" require the domain workflow and usually a
98
+ small client-side join. Do not answer from a raw `zeyos count` on one resource.
99
+ - **Totals / sums:** for a simple numeric total, use `zeyos sum <resource> <field>
100
+ --filter '{...}'`; it pages internally and prints the total. For grouped, conditional,
101
+ or joined totals, list the needed fields and aggregate client-side.
60
102
  - **Filters:** the flag is `--filter '{…}'` (JSON). The CLI writes it to the API's
61
103
  `filters` key internally, which is the form that works for foreign-key (GIN-indexed)
62
104
  fields like `account`, `project`, `ticket`. **Only filter on columns the resource
63
105
  actually has** — filtering an unknown field returns an opaque HTTP 400 with no hint
64
106
  which field was wrong. When unsure, run `zeyos describe <resource>` first.
107
+ Arrays in CLI filters normalize to `IN`, so `{"status":[1,3]}` is the compact form
108
+ for "status is 1 or 3".
65
109
  - **`visibility: 0`** hides archived/deleted records — but **only some resources have a
66
110
  `visibility` column** (e.g. `tickets`, `accounts`, `items` do; `transactions` does
67
111
  **not** — adding `"visibility":0` there 400s). Include it on resources that have it
@@ -83,9 +127,8 @@ zeyos count accounts --filter '{"type":1,"visibility":0}'
83
127
  ```
84
128
 
85
129
  Report the number, and state the definition you used ("customer = `accounts.type` 1,
86
- excluding archived"). For a domain-specific metric (revenue, receivables, workload),
87
- hand off to the matching `zeyos-*` skill for the correct definition, but still run the
88
- query here.
130
+ excluding archived"). For a domain-specific metric (revenue, receivables, workload), read
131
+ the matching `zeyos-*` skill for the correct definition, then still run the query here.
89
132
 
90
133
  ## Safety
91
134
 
@@ -79,9 +79,15 @@ Use this for prompts like:
79
79
 
80
80
  Recommended approach:
81
81
 
82
- 1. Query addresses with type `1` (`BILLING_BILLING`) for the population you care about.
83
- 2. Compare against the account set client-side.
84
- 3. Report missing accounts and, if useful, whether they still have shipping addresses.
82
+ 1. Query the customer account population first. Company names are in `accounts.lastname`,
83
+ not `accounts.name`:
84
+ `zeyos list accounts --filter '{"type":1,"visibility":0,"lastname":{"~~*":"<prefix>%"}}' --fields ID,lastname --limit 1000 --json`
85
+ 2. Query addresses for those accounts in one call. `addresses` has no `visibility`
86
+ column. Use type `1` for billing and type `0` for shipping:
87
+ `zeyos list addresses --filter '{"account":[<accountIds>],"type":[0,1]}' --fields ID,account,type --limit 1000 --json`
88
+ 3. Compare client-side: keep accounts with no type `1` row, and set `has_shipping`
89
+ from presence of a type `0` row. For CSV exports, write the file first and end with
90
+ the required `RESULT_FILE:` marker.
85
91
 
86
92
  ## Common Failure Modes
87
93
 
@@ -32,7 +32,7 @@ Typical prompts:
32
32
  - use `transactions` for invoice and credit value
33
33
  - use `payments` for cash movement
34
34
  - use `documents` only when the question is about the formal document artifact
35
- - use `dunning` plus `dunning2transactions` when the question is really about receivables follow-up or collection state (operationIds: `listDunningNotices`, `listDunningToTransactions` these dbref nouns do not map naively; see [../shared/zeyos-entity-reference.md](../shared/zeyos-entity-reference.md#entity-noun-to-rest-operationid))
35
+ - use `dunning` plus `dunning2transactions` when the question is really about receivables follow-up or collection state. In the CLI, call `zeyos count/list dunning` and `zeyos list dunning2transactions`; in `@zeyos/client`, call `listDunningNotices` and `listDunningToTransactions`.
36
36
  4. State the default metric if the prompt is ambiguous. Do not silently switch between net, gross, invoiced, and paid.
37
37
  5. Pull line-item detail only when necessary, usually with `expand: ['items']` through `@zeyos/client`.
38
38
  6. If the prompt is mainly about overdue notices, reminder stages, or next collection actions, treat that as a collections workflow rather than a pure revenue workflow.
@@ -14,7 +14,7 @@ Recommended defaults when the user does not specify:
14
14
  - Use `transactions.type = 3` for billing invoices.
15
15
  - Use `netamount` for invoiced revenue unless the user asks for gross.
16
16
  - Subtract billing credits (`transactions.type = 4`) if the user asks for net revenue after credits.
17
- - If the question is about reminders, notices, or overdue follow-up, switch to `dunning` and `dunning2transactions` instead of answering from revenue data alone. These dbref nouns diverge: call `listDunningNotices` (not `listDunning`) and `listDunningToTransactions` (not `listDunning2transactions`). See [../../shared/zeyos-entity-reference.md](../../shared/zeyos-entity-reference.md#entity-noun-to-rest-operationid).
17
+ - If the question is about reminders, notices, or overdue follow-up, switch to `dunning` and `dunning2transactions` instead of answering from revenue data alone. The CLI maps these nouns directly (`zeyos count/list dunning`, `zeyos list dunning2transactions`). In JavaScript, use `listDunningNotices` (not `listDunning`) and `listDunningToTransactions` (not `listDunning2transactions`). See [../../shared/zeyos-entity-reference.md](../../shared/zeyos-entity-reference.md#entity-noun-to-rest-operationid).
18
18
 
19
19
  ## First Commands For Counts
20
20
 
@@ -90,18 +90,41 @@ If the user actually wants cash basis, switch to `payments` and sum `amount` ove
90
90
  Use this for prompts like:
91
91
 
92
92
  - "Show me all invoice activity for customer XYZ."
93
+ - "List all delivery notes for customer XYZ."
93
94
  - "What is the payment status for account 122?"
94
95
 
95
96
  Recommended approach:
96
97
 
97
- 1. Resolve the account first.
98
- 2. Query `transactions` for invoice and credit records for that account.
98
+ 1. Resolve the account first. Company names are stored in `accounts.lastname`, not
99
+ `accounts.name`; for partial customer names use `{"lastname":{"~~*":"%XYZ%"}}`, not
100
+ an invented operator such as `contains`.
101
+ 2. Query `transactions` for the requested transaction type for that account.
102
+ - Billing delivery notes are `transactions.type = 2`.
103
+ - Billing invoices are `transactions.type = 3`.
104
+ - Billing credits are `transactions.type = 4`.
99
105
  3. Query `payments` for the same account or linked transactions.
100
106
  4. Present:
107
+ - delivery notes when requested
101
108
  - invoices and credits
102
109
  - payments received
103
110
  - open items or gaps you can identify from the available status fields
104
111
 
112
+ CLI example for delivery notes:
113
+
114
+ ```bash
115
+ zeyos list accounts \
116
+ --filter '{"lastname":{"~~*":"%Bureau3%"},"visibility":0}' \
117
+ --fields ID,customernum,firstname,lastname,type \
118
+ --limit 20 \
119
+ --json
120
+
121
+ zeyos list transactions \
122
+ --filter '{"account":<accountId>,"type":2}' \
123
+ --fields ID,transactionnum,type,account,date,status,netamount \
124
+ --limit 100 \
125
+ --json
126
+ ```
127
+
105
128
  ## Pattern: Cash Received In A Period
106
129
 
107
130
  Use this for prompts like:
@@ -26,7 +26,9 @@ Typical prompts:
26
26
  3. Use the correct primary resource:
27
27
  - `transactions` for invoice and credit obligations
28
28
  - `payments` for cash actually received
29
- - `dunning` and `dunning2transactions` for reminder and notice state (operationIds: `listDunningNotices`, `listDunningToTransactions` — these dbref nouns do not map naively; see [../shared/zeyos-entity-reference.md](../shared/zeyos-entity-reference.md#entity-noun-to-rest-operationid))
29
+ - `dunning` and `dunning2transactions` for reminder and notice state. In the CLI, use
30
+ `zeyos count/list dunning` and `zeyos list dunning2transactions`; in `@zeyos/client`,
31
+ call `listDunningNotices` and `listDunningToTransactions`.
30
32
  4. State what "overdue" means in the answer:
31
33
  - past `duedate`
32
34
  - unpaid or not fully settled
@@ -2,8 +2,9 @@
2
2
 
3
3
  ## Primary Resources
4
4
 
5
- The DB-table noun is not the operationId. Use the operationIds below with `@zeyos/client`
6
- (`client.api.<operationId>(...)`); see [../../shared/zeyos-entity-reference.md](../../shared/zeyos-entity-reference.md#entity-noun-to-rest-operationid) for the full mapping.
5
+ The DB-table noun is not always the operationId. Use the CLI resource names below for
6
+ `zeyos count/list ...`; use the operationIds below with `@zeyos/client`
7
+ (`client.api.<operationId>(...)`). See [../../shared/zeyos-entity-reference.md](../../shared/zeyos-entity-reference.md#entity-noun-to-rest-operationid) for the full mapping.
7
8
 
8
9
  | Concept | dbref noun | list operationId | single-record operationIds |
9
10
  |---------|------------|------------------|----------------------------|
@@ -14,8 +15,10 @@ The DB-table noun is not the operationId. Use the operationIds below with `@zeyo
14
15
  | Dunning-to-transaction links | `dunning2transactions` | `listDunningToTransactions` | `getDunningToTransaction`, `createDunningToTransaction`, `deleteDunningToTransaction` |
15
16
 
16
17
  Note: `dunning` is the **`DunningNotice`** entity (not `Dunning`), and `dunning2transactions`
17
- is the `DunningToTransaction` junction. Calling `client.api.listDunning(...)` or
18
- `client.api.listDunning2transactions(...)` will fail with "operation not found".
18
+ is the `DunningToTransaction` junction. The CLI maps those nouns directly, so
19
+ `zeyos count dunning` is the shortest path for a notice count. Calling
20
+ `client.api.listDunning(...)` or `client.api.listDunning2transactions(...)` will fail with
21
+ "operation not found"; use the operationIds in the table when writing JavaScript.
19
22
 
20
23
  ## Important Status Caution
21
24
 
@@ -6,6 +6,8 @@ description: Detect duplicate accounts, completeness gaps, stale data and schema
6
6
  # ZeyOS Data Quality and Governance
7
7
 
8
8
  Read [../shared/zeyos-agent-operating-guide.md](../shared/zeyos-agent-operating-guide.md) and [../shared/zeyos-query-patterns.md](../shared/zeyos-query-patterns.md) first. See the OKF `playbooks/duplicate-account-review` playbook and `concepts/null-empty-missing` concept.
9
+ Read [references/workflows.md](references/workflows.md) for concrete duplicate, anti-join,
10
+ and remediation-preview query plans.
9
11
 
10
12
  > **Detection is not remediation.** Find and explain; never bulk-merge, archive or delete
11
13
  > from a fuzzy match. Each fix is a human decision on a named ID.
@@ -34,6 +36,23 @@ Typical prompts:
34
36
  preview: exact IDs + proposed per-ID action, and request a human decision (R-009, R-023).
35
37
  6. Re-query after any approved, bounded remediation.
36
38
 
39
+ ## Fast Path: Missing Billing Addresses
40
+
41
+ For "active customers whose name starts with X and missing a billing address", use
42
+ `accounts.lastname` with the ZeyOS pattern operator. Do **not** invent `name`,
43
+ `name_starts`, or `lastname_starts` filters.
44
+ If the prompt already states that billing is address type `1`, shipping is type `0`, and
45
+ `addresses` has no visibility column, skip schema discovery and run the two list queries.
46
+ Batch address lookup with `account:[ids]`; do not loop one account at a time.
47
+
48
+ ```bash
49
+ zeyos list accounts --filter '{"type":1,"visibility":0,"lastname":{"~~*":"<prefix>%"}}' --fields ID,lastname --limit 1000 --json
50
+ zeyos list addresses --filter '{"account":[<accountIds>],"type":[0,1]}' --fields ID,account,type --limit 1000 --json
51
+ ```
52
+
53
+ `addresses` has no `visibility` column. Keep accounts with no type `1` address; derive
54
+ `has_shipping` from presence of a type `0` address.
55
+
37
56
  ## Safety
38
57
 
39
58
  - Read-only by default.
@@ -27,8 +27,16 @@
27
27
 
28
28
  "Customers missing a billing address": list customers, list `addresses` of `type: 1`
29
29
  (billing), keep customers with no matching `account`. `addresses` has **no** `visibility`
30
- column — do not filter it. State whether you treat empty string, null and missing the same
31
- (R-020); by default they are distinct.
30
+ column — do not filter it. For scoped/prefix tasks, batch address lookup with
31
+ `account:[ids]`; do not loop one account at a time. Use:
32
+
33
+ ```bash
34
+ zeyos list accounts --filter '{"type":1,"visibility":0,"lastname":{"~~*":"<prefix>%"}}' --fields ID,lastname --limit 1000 --json
35
+ zeyos list addresses --filter '{"account":[<accountIds>],"type":[0,1]}' --fields ID,account,type --limit 1000 --json
36
+ ```
37
+
38
+ State whether you treat empty string, null and missing the same (R-020); by default they
39
+ are distinct.
32
40
 
33
41
  ## Remediation is a preview, not an action
34
42
 
@@ -28,6 +28,27 @@ Typical prompts:
28
28
  7. Treat textual drafts as safe. Treat message record creation/update as a write and sending or marking `mailbox=2` as high risk; require explicit confirmation plus verified sender context before any real mail mutation.
29
29
  8. Escalate to `@zeyos/client` when you need binary content, MIME expansion, or richer message correlation than the CLI can express cleanly.
30
30
 
31
+ ## Fast Path: Unanswered Inbox Count On Open Tickets
32
+
33
+ For "how many inbox messages (`mailbox` 0) are linked to open tickets and still
34
+ unanswered", use the workflow directly. This is a joined count, not a simple
35
+ `zeyos count messages` task.
36
+
37
+ If the prompt already states mailbox values and closed ticket statuses, skip schema
38
+ discovery. Do **not** use `notin`, `status_neq`, or `messageid` for this count.
39
+ Use the CLI commands below directly; do not write a scratch JavaScript client script for
40
+ this count because the CLI normalizes array filters to the API's native `IN` operator.
41
+
42
+ ```bash
43
+ zeyos list tickets --fields ID,status --filter '{"visibility":0,"status":[0,1,2,3,4,5,6,7,11]}' --limit 10000 --json
44
+ zeyos list messages --fields ID,ticket,reference,date --filter '{"mailbox":0,"ticket":[<ticketIds>]}' --limit 10000 --json
45
+ zeyos list messages --fields ID,ticket,reference,date --filter '{"mailbox":2,"ticket":[<ticketIds>]}' --limit 10000 --json
46
+ ```
47
+
48
+ Count inbound rows where no sent row has the same `ticket`, `reference == inbound.ID`,
49
+ and `sent.date >= inbound.date`. Do not run a separate raw inbox count after listing the
50
+ rows; the join logic is the answer.
51
+
31
52
  ## Safety
32
53
 
33
54
  - Never send email from an agent test or from a summary/draft request.
@@ -98,6 +98,26 @@ Recommended approach:
98
98
 
99
99
  For an operational count, use this exact definition unless the user specifies another one: inbox message (`mailbox = 0`) linked to an open ticket, with no later sent message (`mailbox = 2`) whose `reference` points back to that inbound message.
100
100
 
101
+ Fast path for "how many inbox messages on open tickets are still unanswered":
102
+
103
+ 1. If the prompt already states mailbox values and closed ticket statuses, skip schema
104
+ discovery. Do not use `notin`; open tickets are status `IN [0,1,2,3,4,5,6,7,11]`
105
+ with `visibility:0`.
106
+ Use the CLI commands below directly; do not write a scratch JavaScript client script
107
+ for this count because the CLI normalizes array filters to the API's native `IN`
108
+ operator.
109
+ 2. Query open ticket IDs once:
110
+ `zeyos list tickets --fields ID,status --filter '{"visibility":0,"status":[0,1,2,3,4,5,6,7,11]}' --limit 10000 --json`
111
+ 3. Query inbox and sent messages for those ticket IDs using batched `ticket:[ids]`:
112
+ `zeyos list messages --fields ID,ticket,reference,date --filter '{"mailbox":0,"ticket":[<ticketIds>]}' --limit 10000 --json`
113
+ `zeyos list messages --fields ID,ticket,reference,date --filter '{"mailbox":2,"ticket":[<ticketIds>]}' --limit 10000 --json`
114
+ 4. Count inbound rows where no sent row has the same `ticket`, `reference == inbound.ID`,
115
+ and `sent.date >= inbound.date`. Do not run a separate `zeyos count messages` after
116
+ listing the rows; the join logic, not the raw inbox count, is the answer.
117
+
118
+ For this operational count, do not select `messageid`; `ID`, `ticket`, `reference`, and
119
+ `date` are sufficient, and some instances reject `messageid` in list field selection.
120
+
101
121
  ## Pattern: Draft A Reply
102
122
 
103
123
  Use this for prompts like:
@@ -155,6 +155,27 @@ For counts:
155
155
  zeyos count actionsteps --filter '{"ticket":812,"status":0}' --json
156
156
  ```
157
157
 
158
+ For unanchored due-date counts, first narrow by equality filters and due-date presence
159
+ server-side. The API can return misleading zero counts for comparison filters like
160
+ `{"duedate":{"<=":4102444800}}`, and `duedate:null` is not a due item. If the
161
+ presence count is zero, the due-date count is zero.
162
+
163
+ ```bash
164
+ zeyos count actionsteps --filter '{"status":0,"duedate":{"!=":null}}' --json
165
+ ```
166
+
167
+ If due dates exist, list those rows and count the timestamp range client-side.
168
+
169
+ ```bash
170
+ zeyos list actionsteps \
171
+ --fields ID,status,duedate \
172
+ --filter '{"status":0,"duedate":{"!=":null}}' \
173
+ --limit 10000 \
174
+ --json
175
+ ```
176
+
177
+ Then parse the JSON and count rows where `duedate != null && Number(duedate) <= cutoff`.
178
+
158
179
  ## Pattern: Time Entry / Effort Summaries
159
180
 
160
181
  Use this for prompts like:
@@ -169,7 +190,9 @@ Recommended approach:
169
190
  1. Resolve the anchor and the date window.
170
191
  2. Query `actionsteps` with fields `ID`, `date`, `status`, `effort`, and the relevant FK (`ticket`, `task`, `account`, or `transaction`).
171
192
  3. Include statuses `1` COMPLETED and `3` BOOKED for time-entry totals unless the user asks for drafts/open follow-ups.
172
- 4. Sum `effort` as minutes; convert to hours only if the user asked for hours.
193
+ 4. For a simple ungrouped total, run `zeyos sum actionsteps effort --filter '{"status":[1,3]}'`.
194
+ For anchored or grouped totals, list the needed rows and sum `effort` as minutes.
195
+ Convert to hours only if the user asked for hours.
173
196
  5. For ticket-level totals, roll task time up to the ticket:
174
197
  - direct ticket time is `actionsteps.ticket = <ticketId>`
175
198
  - task ticket time is `actionsteps.task = <taskId>` where `tasks.ticket = <ticketId>`
@@ -18,6 +18,13 @@ These options work with any command:
18
18
  | `--no-color` | Disable ANSI color output |
19
19
  | `-h`, `--help` | Show help for a command |
20
20
 
21
+ Global options may be placed before or after the command name:
22
+
23
+ ```bash
24
+ zeyos --profile dev whoami
25
+ zeyos whoami --profile dev
26
+ ```
27
+
21
28
  ---
22
29
 
23
30
  ## login
@@ -169,6 +176,14 @@ The `--fields` option supports three formats:
169
176
  | JSON object (with aliases) | `--fields '{"Name":"lastname","City":"contact.city"}'` |
170
177
  | JSON array | `--fields '["ID","name","status"]'` |
171
178
 
179
+ **Filter compatibility:**
180
+
181
+ The CLI normalizes common agent-generated filter forms before sending the request:
182
+ arrays become `IN`, `$lt`/`$lte`/`$gt`/`$gte`/`$ne`/`$in`/`$nin` become native ZeyOS
183
+ operators, and suffix keys such as `lastname__startswith`, `lastname__like`, `ID__gt`,
184
+ `status__in`, and `status__nin` become native filters. On `accounts`, `name` in filters
185
+ or `--fields` resolves to `lastname`.
186
+
172
187
  **Examples:**
173
188
 
174
189
  ```bash
@@ -245,6 +260,37 @@ zeyos count accounts --json
245
260
 
246
261
  ---
247
262
 
263
+ ## sum
264
+
265
+ Sum a numeric field across matching records. The CLI pages internally, so this is the
266
+ short path for simple totals that would otherwise require `list` plus a script.
267
+
268
+ ```
269
+ zeyos sum <resource> <field> [options]
270
+ ```
271
+
272
+ | Option | Description |
273
+ |--------|-------------|
274
+ | `--filter <json>` | Filter criteria — JSON object. Arrays normalize to `IN`, e.g. `{"status":[1,3]}` |
275
+ | `--filter-file <path>` | Read filter criteria from a JSON file |
276
+ | `--page-size <n>` | Records per API page (default: 50) |
277
+ | `--limit <n>` | Maximum records to inspect |
278
+ | `--offset <n>` | Initial offset |
279
+ | `--json` | Output as `{"sum": N, "count": N, "field": "..."}` |
280
+ | `--yaml` | YAML output |
281
+
282
+ **Examples:**
283
+
284
+ ```bash
285
+ # Completed or booked effort minutes
286
+ zeyos sum actionsteps effort --filter '{"status":[1,3]}'
287
+
288
+ # Invoice net amount as JSON
289
+ zeyos sum transactions netamount --filter '{"type":3}' --json
290
+ ```
291
+
292
+ ---
293
+
248
294
  ## get
249
295
 
250
296
  Fetch a single record by ID.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zeyos/client",
3
- "version": "0.5.0",
3
+ "version": "0.6.0",
4
4
  "description": "Dependency-light JavaScript client for ZeyOS OpenAPI services",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -54,6 +54,7 @@
54
54
  "test": "node scripts/test.mjs",
55
55
  "test:cli-integration": "node --test cli/test/integration.mjs",
56
56
  "test:agent-protocol": "node test/agent-protocol/harness/run.mjs",
57
+ "test:agent-benchmark": "node test/agent-protocol/harness/run.mjs --benchmark",
57
58
  "test:agent-loop": "node test/agent-protocol/harness/loop.mjs",
58
59
  "test:agent-validate": "node test/agent-protocol/harness/validate-live.mjs"
59
60
  }