@zeyos/client 0.2.0 → 0.4.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.
Files changed (118) hide show
  1. package/CHANGELOG.md +33 -4
  2. package/README.md +31 -1
  3. package/agents/README.md +4 -0
  4. package/agents/shared/zeyos-entity-map.md +5 -1
  5. package/agents/shared/zeyos-entity-reference.md +89 -33
  6. package/agents/zeyos-mail-operations/SKILL.md +7 -1
  7. package/agents/zeyos-mail-operations/references/workflows.md +19 -2
  8. package/agents/zeyos-platform-and-schema/SKILL.md +1 -1
  9. package/agents/zeyos-platform-and-schema/references/workflows.md +21 -5
  10. package/agents/zeyos-time-tracking/SKILL.md +48 -0
  11. package/agents/zeyos-time-tracking/references/workflows.md +230 -0
  12. package/agents/zeyos-work-management/SKILL.md +5 -2
  13. package/agents/zeyos-work-management/references/workflows.md +54 -4
  14. package/docs/02-javascript-client/03-making-requests.md +46 -1
  15. package/docs/03-cli/02-commands.md +63 -1
  16. package/docs/03-cli/03-configuration.md +37 -5
  17. package/docs/04-agent-workflows/01-agent-quickstart.md +24 -0
  18. package/docs/04-agent-workflows/03-cli-coverage-and-escalation.md +3 -2
  19. package/docs/06-okf/01-overview.md +70 -0
  20. package/docs/06-okf/02-producing-and-consuming.md +46 -0
  21. package/docs/06-okf/03-keeping-fresh.md +53 -0
  22. package/docs/06-okf/04-loops.md +58 -0
  23. package/docs/06-okf/_category_.json +9 -0
  24. package/okf/concepts/counting-and-sums.md +10 -0
  25. package/okf/concepts/dates-unix-seconds.md +12 -0
  26. package/okf/concepts/enums.md +14 -0
  27. package/okf/concepts/filters-vs-filter.md +14 -0
  28. package/okf/concepts/index.md +8 -0
  29. package/okf/concepts/operationid-vocabulary.md +17 -0
  30. package/okf/concepts/visibility-column.md +13 -0
  31. package/okf/entities/accounts.md +82 -0
  32. package/okf/entities/actionsteps.md +84 -0
  33. package/okf/entities/addresses.md +50 -0
  34. package/okf/entities/applicationassets.md +43 -0
  35. package/okf/entities/applications.md +62 -0
  36. package/okf/entities/appointments.md +79 -0
  37. package/okf/entities/associations.md +41 -0
  38. package/okf/entities/binfiles.md +32 -0
  39. package/okf/entities/campaigns.md +66 -0
  40. package/okf/entities/categories.md +55 -0
  41. package/okf/entities/channels.md +54 -0
  42. package/okf/entities/comments.md +44 -0
  43. package/okf/entities/components.md +46 -0
  44. package/okf/entities/contacts.md +96 -0
  45. package/okf/entities/contacts2contacts.md +42 -0
  46. package/okf/entities/contracts.md +83 -0
  47. package/okf/entities/couponcodes.md +58 -0
  48. package/okf/entities/coupons.md +69 -0
  49. package/okf/entities/customfields.md +59 -0
  50. package/okf/entities/davservers.md +74 -0
  51. package/okf/entities/devices.md +65 -0
  52. package/okf/entities/documents.md +76 -0
  53. package/okf/entities/dunning.md +82 -0
  54. package/okf/entities/dunning2transactions.md +46 -0
  55. package/okf/entities/entities2channels.md +42 -0
  56. package/okf/entities/events.md +57 -0
  57. package/okf/entities/feedservers.md +67 -0
  58. package/okf/entities/files.md +50 -0
  59. package/okf/entities/follows.md +40 -0
  60. package/okf/entities/forks.md +54 -0
  61. package/okf/entities/groups.md +48 -0
  62. package/okf/entities/groups2users.md +44 -0
  63. package/okf/entities/index.md +93 -0
  64. package/okf/entities/invitations.md +53 -0
  65. package/okf/entities/items.md +95 -0
  66. package/okf/entities/ledgers.md +56 -0
  67. package/okf/entities/likes.md +40 -0
  68. package/okf/entities/links.md +70 -0
  69. package/okf/entities/mailinglists.md +67 -0
  70. package/okf/entities/mailingrecipients.md +45 -0
  71. package/okf/entities/mailservers.md +77 -0
  72. package/okf/entities/messagereads.md +40 -0
  73. package/okf/entities/messages.md +104 -0
  74. package/okf/entities/notes.md +73 -0
  75. package/okf/entities/objects.md +70 -0
  76. package/okf/entities/opportunities.md +87 -0
  77. package/okf/entities/participants.md +52 -0
  78. package/okf/entities/payments.md +76 -0
  79. package/okf/entities/permissions.md +46 -0
  80. package/okf/entities/pricelists.md +70 -0
  81. package/okf/entities/pricelists2accounts.md +46 -0
  82. package/okf/entities/prices.md +49 -0
  83. package/okf/entities/projects.md +72 -0
  84. package/okf/entities/records.md +75 -0
  85. package/okf/entities/relateditems.md +43 -0
  86. package/okf/entities/resources.md +55 -0
  87. package/okf/entities/services.md +64 -0
  88. package/okf/entities/stocktransactions.md +72 -0
  89. package/okf/entities/storages.md +56 -0
  90. package/okf/entities/suppliers.md +51 -0
  91. package/okf/entities/tasks.md +86 -0
  92. package/okf/entities/tickets.md +86 -0
  93. package/okf/entities/transactions.md +118 -0
  94. package/okf/entities/users.md +66 -0
  95. package/okf/entities/weblets.md +66 -0
  96. package/okf/index.md +11 -0
  97. package/okf/log.md +4 -0
  98. package/okf/metrics/cash-received.md +10 -0
  99. package/okf/metrics/index.md +6 -0
  100. package/okf/metrics/invoiced-net-revenue.md +16 -0
  101. package/okf/metrics/open-customers.md +14 -0
  102. package/okf/metrics/overdue-receivables.md +12 -0
  103. package/okf/playbooks/customer-360.md +12 -0
  104. package/okf/playbooks/index.md +5 -0
  105. package/okf/playbooks/revenue-this-year.md +19 -0
  106. package/okf/playbooks/ticket-work-packet.md +11 -0
  107. package/package.json +9 -5
  108. package/scripts/data/okf-curation.mjs +258 -0
  109. package/scripts/generate-client.mjs +4 -275
  110. package/scripts/generate-okf.mjs +241 -0
  111. package/scripts/lib/okf.mjs +272 -0
  112. package/scripts/lib/spec-model.mjs +325 -0
  113. package/src/index.js +4 -0
  114. package/src/runtime/client.js +199 -18
  115. package/src/runtime/okf.js +237 -0
  116. package/samples/missioncontrol/README.md +0 -106
  117. package/samples/missioncontrol/fetch-data.mjs +0 -341
  118. package/samples/missioncontrol/index.html +0 -419
@@ -0,0 +1,230 @@
1
+ # Time Tracking Workflows
2
+
3
+ ## Resources and operation IDs
4
+
5
+ These are dbref nouns; the REST/client operationIds differ for action steps (compound CamelCase):
6
+
7
+ - `tickets` -> `listTickets` / `getTicket` / `createTicket` / `updateTicket`
8
+ - `tasks` -> `listTasks` / `getTask` / `createTask` / `updateTask`
9
+ - `projects` -> `listProjects` / `getProject`
10
+ - `accounts` -> `listAccounts` / `getAccount`
11
+ - `actionsteps` -> `listActionSteps` / `getActionStep` / `createActionStep` / `updateActionStep` (not `listActionsteps`)
12
+ - current user -> `getUserInfo` (oauth2 service); `zeyos whoami --json` exposes its `sub`
13
+
14
+ See [../../shared/zeyos-entity-reference.md](../../shared/zeyos-entity-reference.md#entity-noun-to-rest-operationid) before calling `@zeyos/client`.
15
+
16
+ ## Schema facts this skill relies on
17
+
18
+ - **Time entries are `actionsteps`.** The `effort` field is **minutes** (integer). There is no separate time-entry resource.
19
+ - **Actionstep foreign keys:** `task`, `ticket`, `account`, `transaction` — plus `assigneduser`, `date`, `duedate`, `status`, `effort`. **There is no `project` FK on an actionstep**, so project-level time attaches through a ticket/task in that project, or to the project's `account`.
20
+ - **Actionstep status:** `0` DRAFT · `1` COMPLETED · `2` CANCELLED · `3` BOOKED. Log already-done work as **COMPLETED (1)**; use **BOOKED (3)** only when the instance treats booked effort as the billed/locked record and the user says so.
21
+ - **Ticket/task status:** `0` NOT_STARTED · `1` AWAITING_ACCEPTANCE · `2` ACCEPTED · `3` REJECTED · `4` ACTIVE · `5` INACTIVE · `6` FEEDBACK_REQUIRED · `7` TESTING · `8` CANCELLED · `9` COMPLETED · `10` FAILED · `11` BOOKED. Treat **open/current = status `!IN [8,9,10,11]`** (exclude cancelled/completed/failed/booked). State this definition in the answer; the user can narrow it (e.g. only `4` ACTIVE).
22
+ - **Foreign keys per resource:** `tickets` carry `account`, `project`, `assigneduser`; `tasks` carry `ticket`, `project`, `assigneduser` (no `account` — reach the account through the task's ticket/project); `projects` carry `account`, `assigneduser`; `accounts` carry `lastname`, `firstname`, `customernum` (no `name` field).
23
+ - **Visibility:** `tickets`, `tasks`, `projects`, `accounts` expose `visibility` (use `0` for live records). `transactions` do **not** — never filter `visibility` there.
24
+ - All dates are **Unix timestamps in seconds**.
25
+
26
+ ## Filter operators (server-side)
27
+
28
+ `{"field": value}` is equality. Object values take operators: `{">=":3}`, `{"!=":0}`, `{"IN":[1,3]}`, `{"!IN":[8,9,10,11]}`. For name search, `~~*` is case-insensitive LIKE: `{"lastname": {"~~*": "%acme%"}}`. The client accepts `filters` (plural); the CLI flag is `--filter` (it writes `filters` internally).
29
+
30
+ ---
31
+
32
+ ## Pattern: "What are my current tickets / tasks / action steps?"
33
+
34
+ 1. Resolve the current user id:
35
+
36
+ ```bash
37
+ zeyos whoami --json # read the "sub" field — this is your users.ID
38
+ ```
39
+
40
+ 2. List open work assigned to that id. Use `--limit` high enough that nothing is silently truncated, or `zeyos count` first if the user asked "how many".
41
+
42
+ ```bash
43
+ # my open tickets, most urgent first
44
+ zeyos list tickets \
45
+ --fields ID,ticketnum,name,status,priority,duedate,account.lastname,project \
46
+ --filter '{"assigneduser":<sub>,"visibility":0,"status":{"!IN":[8,9,10,11]}}' \
47
+ --sort -priority,+duedate \
48
+ --limit 200 --json
49
+
50
+ # my open tasks
51
+ zeyos list tasks \
52
+ --fields ID,tasknum,name,status,priority,duedate,ticket,project,projectedeffort \
53
+ --filter '{"assigneduser":<sub>,"visibility":0,"status":{"!IN":[8,9,10,11]}}' \
54
+ --sort -priority,+duedate --limit 200 --json
55
+
56
+ # my open action steps (follow-ups)
57
+ zeyos list actionsteps \
58
+ --fields ID,actionnum,name,status,date,duedate,effort,ticket,task,account \
59
+ --filter '{"assigneduser":<sub>,"status":0}' \
60
+ --sort +duedate --limit 200 --json
61
+ ```
62
+
63
+ Client form:
64
+
65
+ ```js
66
+ const me = await client.oauth2.getUserInfo(); // me.sub === users.ID (string)
67
+ const myTickets = await client.api.listTickets({
68
+ fields: ['ID', 'ticketnum', 'name', 'status', 'priority', 'duedate', 'account.lastname', 'project'],
69
+ filters: { assigneduser: me.sub, visibility: 0, status: { '!IN': [8, 9, 10, 11] } },
70
+ limit: 200,
71
+ });
72
+ ```
73
+
74
+ 3. Report the resolved user and the open-status definition, then the list ordered by priority and due date. Flag overdue items (`duedate < now`).
75
+
76
+ ---
77
+
78
+ ## Pattern: Interactive time logging — "Log 60 minutes of work for client XYZ"
79
+
80
+ This is the headline interactive flow. Run each step; ask the user only at the marked decision points.
81
+
82
+ ### Step 1 — Parse the request
83
+
84
+ - **Duration -> minutes.** "60 minutes" -> `60`; "2 hours" -> `120`; "1.5h"/"an hour and a half" -> `90`; "half an hour" -> `30`. `effort` is integer minutes.
85
+ - **Date.** Default to now (`date` = current Unix seconds). Honor an explicit date if given ("yesterday", "on Monday").
86
+ - **Note.** Use any description the user gave ("call about the renewal") as the actionstep `name`/`description`; otherwise compose a short one and confirm it.
87
+
88
+ ### Step 2 — Resolve the account (ask only if ambiguous)
89
+
90
+ ```bash
91
+ zeyos list accounts \
92
+ --fields ID,customernum,lastname,firstname,type \
93
+ --filter '{"visibility":0,"lastname":{"~~*":"%xyz%"}}' \
94
+ --limit 25 --json
95
+ ```
96
+
97
+ - Also try `customernum` and `firstname` if `lastname` yields nothing.
98
+ - **0 matches:** report it and ask for a customer number or exact name — do not create anything.
99
+ - **1 match:** state it ("Logging against ACME Corp (#10042)") and continue.
100
+ - **>1 match (DECISION POINT):** list the candidates with `customernum`, name, and `type`, and ask which one. Do not guess.
101
+
102
+ ### Step 3 — Enumerate candidate work items for that account
103
+
104
+ Run these in parallel; you are building the menu of places the time could land. (Tasks have no `account` FK, so reach them through the account's tickets/projects.)
105
+
106
+ ```bash
107
+ # open tickets on the account
108
+ zeyos list tickets --fields ID,ticketnum,name,status,priority \
109
+ --filter '{"account":<accountId>,"visibility":0,"status":{"!IN":[8,9,10,11]}}' --limit 50 --json
110
+
111
+ # active projects on the account
112
+ zeyos list projects --fields ID,projectnum,name,status \
113
+ --filter '{"account":<accountId>,"visibility":0,"status":{"!IN":[8,9,10,11]}}' --limit 50 --json
114
+
115
+ # open tasks under those tickets/projects (use the IDs gathered above)
116
+ zeyos list tasks --fields ID,tasknum,name,status,ticket,project \
117
+ --filter '{"visibility":0,"status":{"!IN":[8,9,10,11]},"ticket":{"IN":[<ticketIds>]}}' --limit 50 --json
118
+ ```
119
+
120
+ ### Step 4 — Choose the attachment (DECISION POINT)
121
+
122
+ Present the candidates grouped (tickets / tasks / projects) and ask where the time should go. Map the choice to exactly one actionstep FK:
123
+
124
+ - a ticket -> `ticket: <id>`
125
+ - a task -> `task: <id>`
126
+ - a project -> attach to a ticket/task in that project, or fall back to the project's `account` (no `project` FK exists on actionsteps — say so)
127
+ - "just the account / general" or no work items exist -> `account: <accountId>`
128
+
129
+ If there is exactly one obvious candidate (e.g. a single open ticket), propose it as the default and let the user confirm rather than asking open-endedly.
130
+
131
+ ### Step 5 — Preview, confirm, then write
132
+
133
+ Preview the exact request without sending it, show it to the user, and create it only after confirmation:
134
+
135
+ ```bash
136
+ # dry-run preview (no network, no write)
137
+ zeyos create actionstep \
138
+ --name "Call about the renewal" \
139
+ --ticket <ticketId> \
140
+ --account <accountId> \
141
+ --assigneduser <sub> \
142
+ --effort 60 --status 1 --date <nowSeconds> \
143
+ --query
144
+
145
+ # after the user confirms, drop --query to actually create, then read it back
146
+ zeyos create actionstep --name "Call about the renewal" --ticket <ticketId> \
147
+ --assigneduser <sub> --effort 60 --status 1 --date <nowSeconds> --json
148
+ zeyos get actionstep <newId> --json
149
+ ```
150
+
151
+ Client form:
152
+
153
+ ```js
154
+ const now = Math.floor(Date.now() / 1000);
155
+ const created = await client.api.createActionStep({
156
+ name: 'Call about the renewal',
157
+ ticket: ticketId, // or task / account — exactly one work anchor
158
+ assigneduser: me.sub,
159
+ effort: 60, // minutes
160
+ status: 1, // COMPLETED (logged work)
161
+ date: now,
162
+ });
163
+ const verify = await client.api.getActionStep({ ID: created.ID });
164
+ ```
165
+
166
+ Set **one** of `ticket` / `task` / `account` as the work anchor (a ticket already implies its account, so you do not need both). Report the created id, the anchor, effort minutes, and date.
167
+
168
+ ---
169
+
170
+ ## Pattern: "How much time did I log this week?" (timesheet summary)
171
+
172
+ The read complement to logging — totals the effort the current user already booked over a period, optionally grouped by account/ticket.
173
+
174
+ 1. Resolve the current user id (`zeyos whoami --json` → `sub`).
175
+ 2. Normalize the window to Unix **seconds**. The actionstep `date` field carries the business date of the entry; filter on it.
176
+ 3. List the user's time entries in the window and sum `effort` (minutes). Count only COMPLETED (1) and BOOKED (3) — those are real logged time, not open follow-ups (status 0).
177
+
178
+ ```bash
179
+ # my logged minutes between two timestamps
180
+ zeyos list actionsteps \
181
+ --fields ID,name,effort,date,status,account,ticket,task \
182
+ --filter '{"assigneduser":<sub>,"status":{"IN":[1,3]},"date":{">=":<weekStart>,"<=":<weekEnd>}}' \
183
+ --limit 10000 --json
184
+ ```
185
+
186
+ ```js
187
+ const me = await client.oauth2.getUserInfo();
188
+ const rows = await client.api.listActionSteps({
189
+ fields: ['ID', 'effort', 'date', 'status', 'account', 'ticket', 'task'],
190
+ filters: { assigneduser: me.sub, status: { IN: [1, 3] }, date: { '>=': weekStart, '<=': weekEnd } },
191
+ limit: 10000,
192
+ });
193
+ const totalMinutes = rows.reduce((sum, r) => sum + (Number(r.effort) || 0), 0);
194
+ ```
195
+
196
+ 4. Sum `effort` as minutes; convert to hours only if asked. To break down "by account/ticket", group the rows by the FK and resolve the account/ticket names with a second query. Report the total and the breakdown, and state the window and status set you used.
197
+
198
+ ## Pattern: Adjust or correct a logged entry
199
+
200
+ For "actually that was 90 minutes, not 60" or "move that time to ticket 813" right after logging — or any later correction.
201
+
202
+ 1. Get the entry first so you preview the current values: `zeyos get actionstep <id> --json`.
203
+ 2. Build a minimal PATCH with only the changed fields. Preview with `--query`, confirm with the user (it is a mutation on an existing record), then update and read back.
204
+
205
+ ```bash
206
+ zeyos update actionstep <id> --effort 90 --query # preview the change
207
+ zeyos update actionstep <id> --effort 90 --json # after confirmation
208
+ zeyos get actionstep <id> --json # verify
209
+ ```
210
+
211
+ ```js
212
+ await client.api.updateActionStep({ ID: id, effort: 90 }); // spread form: ID + changed fields
213
+ const verify = await client.api.getActionStep({ ID: id });
214
+ ```
215
+
216
+ Only correct entries the user pointed to (by id, or one you just created in this turn). Re-anchoring time to a different work item means changing the `ticket`/`task`/`account` FK — set the new anchor and clear the old one if it conflicts. Never bulk-rewrite a category of entries.
217
+
218
+ ## Degrading when there is no human (automated / harness runs)
219
+
220
+ - "My work" reads still work: resolve `sub`, run the list, report it.
221
+ - For a **write**, never break ambiguity by guessing. If the account or the work item is ambiguous and nothing can be asked, stop and report the candidates and what was missing. A wrong foreign key on a created time entry is worse than an unanswered request.
222
+
223
+ ## Common failure modes
224
+
225
+ - Counting a `list` page instead of the real total — use `zeyos count` for "how many of my …".
226
+ - Filtering `visibility` on `transactions` (no such column -> HTTP 400).
227
+ - Putting effort in hours — `effort` is **minutes**.
228
+ - Trying to set a `project` FK on an actionstep — it does not exist; anchor on a ticket/task/account instead.
229
+ - Treating BOOKED (status 11) tickets as current — they are terminal; the open set excludes `[8,9,10,11]`.
230
+ - Logging time against a user id you guessed — always read `sub` from `whoami`.
@@ -7,12 +7,15 @@ description: Manage ZeyOS tickets, tasks, projects, action steps, assignees, and
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. Read [../shared/zeyos-entity-map.md](../shared/zeyos-entity-map.md) when the request crosses users, accounts, tickets, tasks, and projects. Read [references/workflows.md](references/workflows.md) for the concrete query patterns.
9
9
 
10
+ For **first-person** requests ("what are *my* current tickets?", "my open tasks") or for **recording new time** ("log 60 minutes for client XYZ"), use [../zeyos-time-tracking/SKILL.md](../zeyos-time-tracking/SKILL.md) instead — it resolves the current user and runs the interactive account → work-item → time-entry flow. This skill stays focused on third-person analytical queues, tracing, and effort *summaries*.
11
+
10
12
  Typical prompts:
11
13
 
12
14
  - "On which projects did Max Power work in the last two weeks?"
13
15
  - "Show overdue high-priority tickets for customer ACME."
14
16
  - "Which open tasks are blocking Project Atlas?"
15
17
  - "Which action steps are due this week for ACME?"
18
+ - "How much booked effort did this user log last week?"
16
19
  - "Create a follow-up ticket for this billing issue."
17
20
 
18
21
  ## Workflow
@@ -22,10 +25,10 @@ Typical prompts:
22
25
  3. Start with the narrowest query that can answer the question:
23
26
  - use `tickets` for queue, backlog, priority, and account-linked support work
24
27
  - use `tasks` for actionable delivery work and short-lived assignments
25
- - use `actionsteps` for smaller cross-record follow-ups attached to tasks, tickets, accounts, or transactions
28
+ - use `actionsteps` for smaller cross-record follow-ups and effort/time-entry evidence attached to tasks, tickets, accounts, or transactions
26
29
  - use `projects` for top-level initiative state
27
30
  4. Follow relationships only after the primary record set is clear.
28
- 5. Treat "worked on" as a proxy unless a stronger activity source exists. Use assignment plus recent timestamps, optionally strengthened by linked action steps, and say so explicitly.
31
+ 5. Treat "worked on" as a proxy unless actionstep effort/date evidence exists. Assignment and timestamps show involvement; `actionsteps.effort` on `COMPLETED` or `BOOKED` records is stronger time-entry evidence.
29
32
  6. Distinguish direct project assignment from project inference through linked tickets.
30
33
  7. When the question is really about account or transaction follow-up, check `actionsteps` before inventing a new task.
31
34
  8. For mutations, preview the affected record first and update with an explicit PATCH body.
@@ -9,10 +9,20 @@
9
9
  - `users`: system identities for assignees
10
10
 
11
11
  These are dbref nouns, not operationIds. Note `actionsteps` -> `listActionSteps` /
12
- `getActionStep` / `createActionStep` (compound CamelCase, not `listActionsteps`). See
12
+ `getActionStep` / `createActionStep` / `updateActionStep` / `deleteActionStep`
13
+ (compound CamelCase, not `listActionsteps`). See
13
14
  [../../shared/zeyos-entity-reference.md](../../shared/zeyos-entity-reference.md#entity-noun-to-rest-operationid)
14
15
  before calling `@zeyos/client`.
15
16
 
17
+ Actionstep status values:
18
+
19
+ - `0` = DRAFT / open follow-up
20
+ - `1` = COMPLETED
21
+ - `2` = CANCELLED
22
+ - `3` = BOOKED
23
+
24
+ Use `effort` as minutes of effort only when the question is about time entries or booked/completed work. Do not infer booked time from task assignment alone.
25
+
16
26
  ## Resolve Before Querying
17
27
 
18
28
  1. Resolve the user or account first.
@@ -32,7 +42,7 @@ Recommended approach:
32
42
  1. Resolve the user from `users.name` or `users.email`.
33
43
  2. Query recent tasks assigned to that user with `lastmodified > cutoff`.
34
44
  3. Query recent tickets assigned to that user with `lastmodified > cutoff`.
35
- 4. Optionally query recent `actionsteps` for the same user when you want stronger evidence of follow-up activity.
45
+ 4. Query recent `actionsteps` for the same user when you need stronger evidence of follow-up activity or booked effort.
36
46
  5. Collect direct `project` links from tasks and tickets.
37
47
  6. For tasks that only link to a ticket, fetch the ticket or include `ticket.project` if available in the selected fields.
38
48
  7. For action steps linked to tasks or tickets, infer the project through the linked parent only if the user asked for a broad activity summary.
@@ -67,7 +77,7 @@ const recentTickets = await client.api.listTickets({
67
77
 
68
78
  Important caveat:
69
79
 
70
- - The documented schema does not expose timesheets or effort logs here. "Worked on" is therefore an activity proxy, not proof of time spent.
80
+ - Assignment-based "worked on" answers are activity proxies. `actionsteps` with `date`, `status` COMPLETED/BOOKED, and `effort` are the better evidence for time-entry summaries.
71
81
 
72
82
  ## Pattern: Review A Ticket Queue
73
83
 
@@ -87,6 +97,13 @@ zeyos list tickets \
87
97
  Follow up with `tasks` only if the answer requires execution detail below the ticket level.
88
98
  Follow up with `actionsteps` if the queue management style in this instance uses reminders or next steps below the ticket.
89
99
 
100
+ For ticket work packets, include:
101
+
102
+ - ticket status, priority, due date, account/project links
103
+ - open tasks linked by `task.ticket`
104
+ - open actionsteps linked by `actionstep.ticket` or by task
105
+ - recent messages linked by `message.ticket` if the request asks for customer context
106
+
90
107
  ## Pattern: Overdue Work For An Account Or Project
91
108
 
92
109
  Use this for prompts like:
@@ -121,6 +138,39 @@ Recommended approach:
121
138
  4. Keep due date and status visible in the result.
122
139
  5. If the anchor is a transaction and the user also wants broader work context, then check related tickets or account-level tasks second.
123
140
 
141
+ CLI examples:
142
+
143
+ ```bash
144
+ zeyos list actionsteps \
145
+ --fields ID,actionnum,name,status,date,duedate,effort,ticket,task,account,transaction \
146
+ --filter '{"ticket":812}' \
147
+ --sort +duedate \
148
+ --limit 100 \
149
+ --json
150
+ ```
151
+
152
+ For counts:
153
+
154
+ ```bash
155
+ zeyos count actionsteps --filter '{"ticket":812,"status":0}' --json
156
+ ```
157
+
158
+ ## Pattern: Time Entry / Effort Summaries
159
+
160
+ Use this for prompts like:
161
+
162
+ - "How many minutes were booked on ticket 812 last week?"
163
+ - "Summarize completed actionstep effort for this account."
164
+ - "Which user logged effort against this project?"
165
+
166
+ Recommended approach:
167
+
168
+ 1. Resolve the anchor and the date window.
169
+ 2. Query `actionsteps` with fields `ID`, `date`, `status`, `effort`, and the relevant FK (`ticket`, `task`, `account`, or `transaction`).
170
+ 3. Include statuses `1` COMPLETED and `3` BOOKED for time-entry totals unless the user asks for drafts/open follow-ups.
171
+ 4. Sum `effort` as minutes; convert to hours only if the user asked for hours.
172
+ 5. Keep task/ticket assignment separate from effort totals.
173
+
124
174
  ## Pattern: Create Follow-Up Work
125
175
 
126
176
  For prompts like:
@@ -136,7 +186,7 @@ Recommended approach:
136
186
  - use `project` if the follow-up is project-wide
137
187
  - use `actionsteps` if the follow-up is small, account-scoped, or transaction-scoped and does not justify a standalone task
138
188
  - keep `visibility: 0`
139
- 3. Confirm the new owner, due date, and priority if they are not explicit.
189
+ 3. Confirm the new owner, due date, and priority/effort if they are not explicit.
140
190
  4. Use explicit PATCH or create bodies and return the created record ID.
141
191
 
142
192
  ## Common Failure Modes
@@ -253,6 +253,26 @@ const page2 = await client.api.listTickets({
253
253
  Use `count: true` to get the total number of matching records without fetching the full dataset. This is useful for building pagination controls.
254
254
  :::
255
255
 
256
+ ### Auto-pagination
257
+
258
+ To iterate an entire result set without managing `offset` yourself, use `client.paginate(operationId, input, opts)` — an async iterator that pages until a short/empty page (or `opts.max`) is reached. It removes the off-by-one and "I only got the first 1000 / 50 rows" mistakes the list caps otherwise invite.
259
+
260
+ ```js
261
+ // Stream every matching ticket, one page at a time
262
+ for await (const ticket of client.paginate('listTickets', { filters: { visibility: 0 } }, { pageSize: 1000 })) {
263
+ process(ticket);
264
+ }
265
+
266
+ // Eagerly collect up to a cap
267
+ const recent = await client.collect(
268
+ 'listTickets',
269
+ { filters: { visibility: 0 }, sort: ['-lastmodified'] },
270
+ { pageSize: 500, max: 2000 }
271
+ );
272
+ ```
273
+
274
+ `opts`: `pageSize` (default 1000, clamped to the server max of 10000), `max` (stop after N records), and `requestOptions` (forwarded to each underlying call, e.g. `{ signal, timeoutMs }`). `collect` is the eager array form of `paginate`.
275
+
256
276
  ## Normalising List Responses
257
277
 
258
278
  List endpoints are not uniform across the full surface area. Depending on the endpoint and response mode, you may see:
@@ -432,9 +452,32 @@ const noRetry = createZeyosClient({ platform: 'live', instance: 'demo', retry: f
432
452
  Only `429`/`503` are retried by default -- statuses that clearly mean "try again later". `5xx` codes such as `500`/`502` are **not** retried automatically, to avoid re-applying a non-idempotent write that may have partially succeeded. Add them to `retryOn` only for read-heavy workloads.
433
453
  :::
434
454
 
455
+ ### Request timeout
456
+
457
+ A request with no built-in deadline can hang indefinitely if the connection stalls (e.g. an instance restarting). Set `timeoutMs` to bound each attempt; it composes with any `AbortSignal` you pass. The timeout applies **per attempt**, so a retry gets a fresh deadline.
458
+
459
+ ```js
460
+ // Per request
461
+ await client.api.listTickets({ filters: { visibility: 0 } }, { timeoutMs: 8000 });
462
+
463
+ // Or a client-wide default
464
+ const client = createZeyosClient({ platform: 'live', instance: 'demo', timeoutMs: 8000 });
465
+ ```
466
+
467
+ A timeout rejects with an `Error` whose `isTimeout === true` (and `code === 'ETIMEDOUT'`). A timeout is distinct from a caller abort: aborting your own `AbortSignal` always propagates immediately and is never retried.
468
+
469
+ ### Network-error retries
470
+
471
+ Network-level failures (a dropped connection, DNS blip, or a timeout) are retried for **read** operations only — `GET`/`HEAD` plus side-effect-free `list`/`count`/`search` queries — using the same retry budget and backoff. Writes (`create`/`update`/`delete`) are **never** auto-retried on a network error, so a dropped connection can't cause a duplicate mutation. Override per request or per client with `retryOnNetworkError: true | false`.
472
+
473
+ ```js
474
+ await client.api.listTickets({ filters: {} }, { retryOnNetworkError: true }); // force (already on for reads)
475
+ await client.api.createTicket({ name: 'x' }, { retryOnNetworkError: true }); // opt a write in, at your own risk
476
+ ```
477
+
435
478
  ## Error Handling
436
479
 
437
- All API errors are thrown as `ZeyosApiError` instances. This class extends `Error` and includes structured information about the failed request.
480
+ All API errors are thrown as `ZeyosApiError` instances. This class extends `Error` and includes structured information about the failed request. When the server returns an error body with a message, a short snippet of it is folded into `err.message` (e.g. `api.listTickets failed with HTTP 400: unknown filter field: bogus`), so the thrown error says *why*, not just the status code. The full body is always on `err.body`.
438
481
 
439
482
  ```js
440
483
  import { ZeyosApiError } from '@zeyos/client';
@@ -535,6 +578,8 @@ All generated methods and `client.request()` accept an optional second argument
535
578
  | Option | Type | Description |
536
579
  |--------|------|-------------|
537
580
  | `signal` | `AbortSignal` | An `AbortController` signal to cancel the request |
581
+ | `timeoutMs` | `number` | Abort this attempt after N ms (composes with `signal`); also settable client-wide as `timeoutMs` |
582
+ | `retryOnNetworkError` | `boolean` | Force/disable retrying network errors & timeouts for this call (default: on for reads, off for writes) |
538
583
  | `raw` | `boolean` | Return the full response envelope instead of just the data |
539
584
  | `auth` | `string \| { mode?: string, accessToken?: string, access_token?: string, refreshToken?: string, refresh_token?: string, clientId?: string, client_id?: string, clientSecret?: string, client_secret?: string }` | Override the authentication mode or credentials for this request |
540
585
  | `baseUrl` | `string` | Override the base URL for this request |
@@ -14,6 +14,7 @@ These options work with any command:
14
14
  |--------|-------------|
15
15
  | `--json` | Output as formatted JSON |
16
16
  | `--yaml` | Output as YAML |
17
+ | `--profile <name>` | Use a named credential profile for this command |
17
18
  | `--no-color` | Disable ANSI color output |
18
19
  | `-h`, `--help` | Show help for a command |
19
20
 
@@ -101,6 +102,36 @@ zeyos whoami --show-token --json # explicitly include the current access token
101
102
 
102
103
  ---
103
104
 
105
+ ## profile
106
+
107
+ Manage named credential profiles and switch between ZeyOS instances. See [Configuration → Profiles](./03-configuration.md#profiles) for the full model.
108
+
109
+ ```
110
+ zeyos profile <list|current|use|add|remove> [options]
111
+ ```
112
+
113
+ | Command | Description |
114
+ |---------|-------------|
115
+ | `profile list` | List all profiles; the active one is marked `*`, with token status |
116
+ | `profile current` | Show which profile resolves right now, and why (flag/env/pin/active) |
117
+ | `profile use <name>` | Make `<name>` the active profile (global) |
118
+ | `profile use <name> --local` | Pin `<name>` to the current project (`.zeyos/profile`) |
119
+ | `profile add <name> [opts]` | Create/update a profile (`--base-url`, `--client-id`, `--secret`, or `--from-current`) |
120
+ | `profile remove <name>` | Delete a profile |
121
+
122
+ **Examples:**
123
+
124
+ ```bash
125
+ zeyos profile add dev --base-url https://zeyos.cms-it.de/dev
126
+ zeyos profile add prod --from-current # snapshot current credentials
127
+ zeyos login --profile prod # authenticate into & activate a profile
128
+ zeyos profile use dev # switch active profile
129
+ zeyos profile use prod --local # pin to this project
130
+ zeyos list tickets --profile dev # one-off override on any command
131
+ ```
132
+
133
+ ---
134
+
104
135
  ## list
105
136
 
106
137
  Query and list records for a resource with filtering, sorting, and pagination.
@@ -363,7 +394,10 @@ List all curated CLI resources and their operations. This is the authoritative b
363
394
  zeyos resources
364
395
  ```
365
396
 
366
- Shows a table of all CLI-supported resource types and available operations.
397
+ Shows a table of all CLI-supported resource types and available operations. Operational
398
+ workflows can use `actionstep` / `actionsteps` / `time-entries` for follow-ups and
399
+ effort records. Read-only platform schema definitions are available as `customfield` /
400
+ `customfields` with `list`, `get`, and therefore `count` support.
367
401
 
368
402
  ---
369
403
 
@@ -447,6 +481,34 @@ Skills are copied into `<dir>/<name>/`, with the shared reference files installe
447
481
 
448
482
  ---
449
483
 
484
+ ## okf
485
+
486
+ Work with the [Open Knowledge Format](../06-okf/01-overview.md) bundle that ships with the
487
+ client — a portable Markdown description of the ZeyOS data model (one concept per
488
+ API-backed entity) plus curated metrics, playbooks, and query concepts.
489
+
490
+ ```bash
491
+ zeyos okf list # list concepts (type, id, title); --json for automation
492
+ zeyos okf show tickets # print a concept (bare resource name, or entities/tickets)
493
+ zeyos okf check # validate OKF v0.1 conformance (exit non-zero on error)
494
+ zeyos okf export --out ./okf # copy the shipped bundle into a directory
495
+ zeyos okf build --out ./okf # synthesize a bundle from the client's schema
496
+ ```
497
+
498
+ | Subcommand | What it does |
499
+ |-----------|--------------|
500
+ | `list` | List the concepts in the bundle (`--json`/`--yaml` supported). |
501
+ | `show <concept>` | Print one concept doc. Accepts a bare resource (`tickets`) or full id (`entities/tickets`). |
502
+ | `check` | Validate the bundle for OKF v0.1 conformance; exits non-zero on any error (CI-friendly). |
503
+ | `export` | Copy the shipped `okf/` bundle into `--out` (default `./okf`); `--force` to overwrite. |
504
+ | `build` | Synthesize a structural bundle from the client's schema into `--out` (default `./okf`). |
505
+
506
+ Options: `--dir <path>` reads from an explicit bundle directory (`list`/`show`/`check`);
507
+ `--out <path>` is the write target (`build`/`export`); `--force` overwrites an existing
508
+ target. `export` ships the rich curated bundle; `build` is the lighter runtime projection.
509
+
510
+ ---
511
+
450
512
  ## Command Aliases
451
513
 
452
514
  | Alias | Equivalent |
@@ -12,15 +12,47 @@ These settings apply to the CLI's curated resource registry. If you need a resou
12
12
 
13
13
  ## Credential Cascade
14
14
 
15
- Credentials are resolved from three sources in priority order:
15
+ The CLI first decides **which credential set** is the base, then lets environment credential variables field-override on top. The base is chosen by the first match in this order:
16
16
 
17
17
  | Priority | Source | Location |
18
18
  |----------|--------|----------|
19
- | 1 (highest) | Environment variables | `ZEYOS_BASE_URL`, `ZEYOS_TOKEN`, etc. |
20
- | 2 | Local config file | `.zeyos/auth.json` (walks up from CWD) |
21
- | 3 (lowest) | Global config file | `~/.config/zeyos/credentials.json` |
19
+ | 1 (highest) | `--profile <name>` flag | named profile (per command) |
20
+ | 2 | `ZEYOS_PROFILE` env var | named profile |
21
+ | 3 | Project pin | `.zeyos/profile` (walks up from CWD) |
22
+ | 4 | Local config file | `.zeyos/auth.json` (walks up from CWD) |
23
+ | 5 | Global active profile | `~/.config/zeyos/profiles.json` (`active`) |
24
+ | 6 (lowest) | Global config file | `~/.config/zeyos/credentials.json` |
22
25
 
23
- Configuration is merged from global, then local, then environment variables. Higher-priority sources override individual fields from lower-priority sources. For example, setting `ZEYOS_TOKEN` as an environment variable overrides the stored token while still allowing `baseUrl` and client credentials to come from a config file.
26
+ On top of the chosen base, the credential **environment variables** (`ZEYOS_BASE_URL`, `ZEYOS_TOKEN`, …) always override individual fields so you can keep `baseUrl` and client credentials in a profile while overriding just the token via `ZEYOS_TOKEN` in CI.
27
+
28
+ If you have never created a profile, nothing changes: the CLI falls through to the legacy local `.zeyos/auth.json` and global `credentials.json` exactly as before.
29
+
30
+ ## Profiles
31
+
32
+ Profiles let you store several ZeyOS instances (e.g. `dev`, `prod`, `client-x`) and switch between them without re-running `login`. Each profile is a full credential set (URL, OAuth app, tokens) kept in `~/.config/zeyos/profiles.json`, with one marked **active**.
33
+
34
+ ```bash
35
+ # Create profiles (connection params now; tokens via login)
36
+ zeyos profile add dev --base-url https://zeyos.cms-it.de/dev
37
+ zeyos profile add prod --base-url https://cloud.zeyos.com/acme --client-id app --secret "$SECRET"
38
+
39
+ # Authenticate into a profile (also makes it active)
40
+ zeyos login --profile prod
41
+
42
+ # See and switch profiles
43
+ zeyos profile list # active marked with *, shows token status
44
+ zeyos profile use dev # switch the global active profile
45
+ zeyos profile current # what resolves right now, and why
46
+
47
+ # Per-project: pin a profile so cd-ing into a repo selects its instance
48
+ cd ~/work/acme && zeyos profile use prod --local # writes ./.zeyos/profile
49
+
50
+ # One-off override on any command (beats env, pin, and active)
51
+ zeyos whoami --profile dev
52
+ zeyos list tickets --profile prod
53
+ ```
54
+
55
+ `zeyos profile add <name> --from-current` snapshots whatever credentials are in effect (including tokens) into a new profile — handy for adopting an existing `.zeyos/auth.json` setup. Add `.zeyos/profile` to `.gitignore` alongside `.zeyos/auth.json`.
24
56
 
25
57
  ## Environment Variables
26
58
 
@@ -107,6 +107,28 @@ Count matching records:
107
107
 
108
108
  ```bash
109
109
  zeyos count tickets --filter '{"visibility":0,"status":4}' --json
110
+ zeyos count customfields --json
111
+ zeyos count actionsteps --filter '{"status":0}' --json
112
+ ```
113
+
114
+ Summarize actionstep effort/time-entry evidence:
115
+
116
+ ```bash
117
+ zeyos list actionsteps \
118
+ --fields ID,name,status,date,duedate,effort,ticket,task,account \
119
+ --filter '{"status":3}' \
120
+ --limit 100 \
121
+ --json
122
+ ```
123
+
124
+ Inspect ticket-linked mail without sending anything:
125
+
126
+ ```bash
127
+ zeyos list messages \
128
+ --fields ID,date,mailbox,subject,sender_email,to_email,ticket,reference,messageid \
129
+ --filter '{"ticket":42}' \
130
+ --sort +date \
131
+ --json
110
132
  ```
111
133
 
112
134
  ## Write Data
@@ -145,4 +167,6 @@ zeyos delete ticket 42 --force
145
167
  - Include `visibility: 0` in filters for normal business views.
146
168
  - Prefer `--data '<json>'` over many separate flags in automation.
147
169
  - Run `zeyos resources` before assuming a resource is CLI-supported.
170
+ - Use `actionsteps.effort` for time-entry totals; do not infer booked time from task assignment.
171
+ - Draft e-mail text in the response unless the user explicitly asks for a real ZeyOS draft record; never send mail from an agent workflow.
148
172
  - Escalate to [`@zeyos/client`](./03-cli-coverage-and-escalation.md) when the CLI resource registry is not enough.
@@ -13,10 +13,11 @@ The command `zeyos resources` is the source of truth for CLI-supported resource
13
13
 
14
14
  | Resource | Operations |
15
15
  |----------|------------|
16
- | `account`, `appointment`, `campaign`, `contact`, `document`, `event`, `file`, `invitation`, `item`, `message`, `note`, `opportunity`, `payment`, `project`, `storage`, `task`, `ticket`, `transaction` | `list`, `get`, `create`, `update`, `delete` |
16
+ | `account`, `actionstep`, `appointment`, `campaign`, `contact`, `document`, `event`, `file`, `invitation`, `item`, `message`, `note`, `opportunity`, `payment`, `project`, `storage`, `task`, `ticket`, `transaction` | `list`, `get`, `create`, `update`, `delete` |
17
+ | `customfield` / `customfields` | `list`, `get` |
17
18
  | `group`, `user` | `list`, `get` |
18
19
 
19
- Plural names and common aliases such as `tickets`, `docs`, `invoice`, and `crm` are resolved by the CLI, but the underlying coverage boundary is still the registry above.
20
+ Plural names and common aliases such as `tickets`, `actionsteps`, `time-entries`, `docs`, `invoice`, and `crm` are resolved by the CLI, but the underlying coverage boundary is still the registry above.
20
21
 
21
22
  ## What the CLI Does Not Try to Cover
22
23