ima-claude 2.16.0 → 2.18.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/README.md CHANGED
@@ -255,7 +255,7 @@ Named subagents with hard constraints — model, tools, and permissions enforced
255
255
  | `ima-claude:implementer` | sonnet | full access | `functional-programmer` | Feature dev, bug fixes, refactoring, tests |
256
256
  | `ima-claude:reviewer` | sonnet | read-only | `functional-programmer` | Code review, security audit, FP compliance |
257
257
  | `ima-claude:tester` | sonnet | full access | `unit-testing`, `functional-programmer` | Test creation, TDD, test running, debugging failures |
258
- | `ima-claude:wp-developer` | sonnet | full access | `php-fp`, `php-fp-wordpress`, `wp-local`, `ima-forms-expert`, `ima-bootstrap`, `jquery` | WordPress plugins, themes, WP-CLI, forms |
258
+ | `ima-claude:wp-developer` | sonnet | full access | `php-fp`, `php-fp-wordpress`, `wp-ddev`, `wp-local`, `ima-forms-expert`, `ima-bootstrap`, `jquery` | WordPress plugins, themes, WP-CLI, forms |
259
259
  | `ima-claude:memory` | sonnet | full access | `mcp-vestige`, `mcp-qdrant`, `mcp-serena` | Memory search, storage, consolidation across Vestige/Qdrant/Serena |
260
260
 
261
261
  Agents are auto-discovered from `plugins/ima-claude/agents/`. No manifest changes needed to add new ones.
@@ -286,6 +286,13 @@ Agents are auto-discovered from `plugins/ima-claude/agents/`. No manifest change
286
286
  | `py-fp` | Python FP core - comprehensions, generators, frozen dataclasses |
287
287
  | `quasar-fp` | Quasar Framework with utility-first CSS |
288
288
 
289
+ ### CRM Skills
290
+
291
+ | Skill | Description |
292
+ |-------|-------------|
293
+ | `espocrm` | EspoCRM skill family router (intent detection, Salesforce mapping, child skill routing) |
294
+ | `espocrm-api` | EspoCRM v9.x REST API (auth, CRUD, WHERE filtering, relationships, webhooks, mass ops) |
295
+
289
296
  ### Domain Expert Skills
290
297
 
291
298
  | Skill | Description |
@@ -295,6 +302,7 @@ Agents are auto-discovered from `plugins/ima-claude/agents/`. No manifest change
295
302
  | `ima-bootstrap` | Bootstrap 5.3 + IMA brand (utility-first CSS, SCSS) |
296
303
  | `playwright` | E2E testing with Playwright + TypeScript |
297
304
  | `docs-organize` | Three-tier documentation organization |
305
+ | `wp-ddev` | WP-CLI commands for DDEV WordPress environments |
298
306
  | `wp-local` | WP-CLI commands for Flywheel Local WP |
299
307
  | `jira-checkpoint` | Jira awareness checkpoints for team visibility |
300
308
  | `phpunit-wp` | PHPUnit testing for WordPress plugins with FP principles |
package/dist/cli.js CHANGED
@@ -11,7 +11,7 @@ var HOOKS_DIR = join(CLAUDE_DIR, "hooks");
11
11
  var COMMANDS_DIR = join(CLAUDE_DIR, "commands");
12
12
  var RULES_DIR = join(CLAUDE_DIR, "rules");
13
13
  var SETTINGS_FILE = join(CLAUDE_DIR, "settings.json");
14
- var VERSION = "2.16.0";
14
+ var VERSION = "2.18.0";
15
15
  var colors = {
16
16
  reset: "\x1B[0m",
17
17
  bright: "\x1B[1m",
@@ -90,10 +90,14 @@ var SKILLS_TO_INSTALL = [
90
90
  "jquery",
91
91
  // Payment & API skills
92
92
  "php-authnet",
93
+ // CRM skills
94
+ "espocrm",
95
+ "espocrm-api",
93
96
  // Domain expert skills
94
97
  "architect",
95
98
  "docs-organize",
96
99
  "wp-local",
100
+ "wp-ddev",
97
101
  "rg",
98
102
  "ima-forms-expert",
99
103
  "ima-brand",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ima-claude",
3
- "version": "2.16.0",
3
+ "version": "2.18.0",
4
4
  "description": "IMA's AI coding agent skills - FP patterns, architecture guidance, and team standards. Supports Claude Code, Junie CLI, Gemini CLI, and GitHub Copilot.",
5
5
  "type": "module",
6
6
  "scripts": {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "ima-claude",
3
- "version": "2.16.0",
4
- "description": "IMA's Claude Code skills for functional programming, architecture, and team standards. 57 skills, 24 hooks, default persona, 3-tier memory system.",
3
+ "version": "2.18.0",
4
+ "description": "IMA's Claude Code skills for functional programming, architecture, and team standards. 60 skills, 24 hooks, default persona, 3-tier memory system.",
5
5
  "author": {
6
6
  "name": "IMA",
7
7
  "url": "https://github.com/Soabirw/ima-claude"
@@ -5,6 +5,7 @@ model: sonnet
5
5
  skills:
6
6
  - php-fp
7
7
  - php-fp-wordpress
8
+ - wp-ddev
8
9
  - wp-local
9
10
  - ima-forms-expert
10
11
  - ima-bootstrap
@@ -23,7 +24,7 @@ You are a WordPress development specialist with deep knowledge of the WordPress
23
24
  ## Capabilities
24
25
 
25
26
  - Plugin and theme development with WordPress coding standards
26
- - WP-CLI operations via Local WP environments
27
+ - WP-CLI operations via DDEV environments (preferred) or Local WP
27
28
  - IMA Forms component library (ima_forms_* functions)
28
29
  - Bootstrap 5.3 integration with IMA brand system
29
30
  - jQuery patterns for WordPress DOM manipulation
@@ -13,7 +13,7 @@
13
13
  | WordPress plugin, nonce, sanitization, capability | `php-fp-wordpress` |
14
14
  | Architecture, new project, scaling, microservices | `architect` |
15
15
  | Documentation structure, organize docs | `docs-organize` |
16
- | WP-CLI, Local WP, wp plugin, wp db query | `wp-local` |
16
+ | WP-CLI, DDEV, ddev wp, wp plugin, wp db query | `wp-ddev` (DDEV) or `wp-local` (Flywheel) |
17
17
  | Find in files, search code, grep | `rg` |
18
18
  | Latest, current, 2025/2026, recent updates, research | `mcp-tavily` |
19
19
  | Library docs, React API, how to use [library] | `mcp-context7` |
@@ -0,0 +1,79 @@
1
+ ---
2
+ name: "espocrm"
3
+ description: >-
4
+ EspoCRM skill family router. Detects intent (API calls, extension development,
5
+ UI customization) and routes to the appropriate child skill. Provides shared
6
+ context: v9.x target, entity-based REST architecture, Salesforce mental model
7
+ mapping. Use when: any EspoCRM work, CRM integration, CRM API, EspoCRM
8
+ customization. Triggers on: EspoCRM, Espo, CRM API, CRM integration, CRM
9
+ entity, CRM webhook, CRM hook.
10
+ ---
11
+
12
+ # EspoCRM - Skill Family Router
13
+
14
+ Routes EspoCRM work to the right child skill based on intent.
15
+
16
+ **Target version**: v9.x (9.0+)
17
+ **Architecture**: Entity-based REST API, PHP backend, Backbone.js frontend
18
+
19
+ ## Decision Tree
20
+
21
+ ```
22
+ What are you doing with EspoCRM?
23
+ ├── REST API calls (external integration)?
24
+ │ → espocrm-api (primary) + php-fp or js-fp-api
25
+ │ → Auth, CRUD, filtering, webhooks, mass ops
26
+
27
+ ├── PHP extension development (hooks, services, custom entities)?
28
+ │ → espocrm-extensions (Phase 2) + php-fp
29
+ │ → ORM, hooks, services, DI, custom controllers, modules
30
+
31
+ ├── Frontend/UI customization (views, fields, layouts)?
32
+ │ → espocrm-ui (Phase 3)
33
+ │ → Backbone views, Espo.Ajax, Handlebars templates
34
+
35
+ └── Not sure / mixed?
36
+ → Start with espocrm-api for data access
37
+ → Route to extension/UI skill once scope is clear
38
+ ```
39
+
40
+ ## Shared Context
41
+
42
+ ### Entity-Based Model
43
+ EspoCRM organizes data as **Entity Types** (Account, Contact, Lead, Opportunity, custom types). Every entity type gets automatic REST endpoints. Custom entities created via Entity Manager are immediately API-accessible.
44
+
45
+ ### Salesforce Mental Model
46
+ For developers familiar with Salesforce, this mapping accelerates onboarding:
47
+
48
+ | Salesforce | EspoCRM |
49
+ |---|---|
50
+ | sObject | Entity Type |
51
+ | Connected App + OAuth | API User + API Key |
52
+ | SOQL | WHERE JSON filters + select/orderBy params |
53
+ | SOSL | Text filter on list endpoint |
54
+ | Apex Trigger | PHP Hook (beforeSave, afterSave) |
55
+ | Apex REST endpoint | Custom API Action (Controller + routes.json) |
56
+ | LWC / Visualforce | Custom Views (JS, extending base views) |
57
+ | Platform Events / CDC | Webhooks ({Entity}.create, .update, .delete) |
58
+ | Bulk API 2.0 | No equivalent (loop individual calls or use Import) |
59
+ | Governor Limits | None (self-hosted, you manage resources) |
60
+ | AppExchange | EspoCRM Extensions marketplace |
61
+
62
+ ### Key Differences from Salesforce
63
+ - **No SOQL** — queries use structured JSON WHERE filters (verbose but explicit)
64
+ - **No Bulk API** — mass operations exist (massUpdate, massDelete) but no batch create
65
+ - **No Composite API** — one request per operation
66
+ - **No governor limits** — self-hosted, manage at server/proxy level
67
+ - **Simpler auth** — API Key in one header vs. multi-step OAuth
68
+ - **Metadata is JSON files** — no deployment steps, changes take effect on cache clear
69
+
70
+ ### Documentation Lookup
71
+ Use Context7 for live EspoCRM docs: `resolve-library-id("espocrm")` resolves to `/espocrm/documentation`.
72
+
73
+ ## Child Skill Status
74
+
75
+ | Skill | Status | Covers |
76
+ |---|---|---|
77
+ | `espocrm-api` | Active | REST API, auth, CRUD, filtering, webhooks, mass ops |
78
+ | `espocrm-extensions` | Planned | PHP hooks, services, ORM, custom entities, modules |
79
+ | `espocrm-ui` | Planned | JS views, fields, Espo.Ajax, Backbone, Handlebars |
@@ -0,0 +1,360 @@
1
+ ---
2
+ name: "espocrm-api"
3
+ description: >-
4
+ EspoCRM v9.x REST API patterns — authentication (API Key, HMAC), CRUD operations,
5
+ JSON WHERE filtering, relationship management, webhooks, mass operations, error
6
+ handling, and performance. Official PHP client and Node.js patterns. Use when:
7
+ calling EspoCRM API, building CRM integrations, querying CRM data, managing
8
+ webhooks, bulk CRM operations. Triggers on: EspoCRM API, CRM endpoint, CRM
9
+ query, CRM webhook, CRM filter, espo api, api/v1, X-Api-Key, HMAC auth,
10
+ entity CRUD.
11
+ ---
12
+
13
+ # EspoCRM REST API (v9.x)
14
+
15
+ Patterns for the EspoCRM REST API. External integrations, data pipelines, and automation.
16
+
17
+ **Base URL**: `https://{your-site}/api/v1/`
18
+ **Content-Type**: `application/json` (all requests)
19
+ **Parent skill**: `espocrm` (router — Salesforce mapping, shared context)
20
+ **Companion skills**: `php-fp` (PHP integrations), `js-fp-api` (Node integrations)
21
+ **Live docs**: Context7 `/espocrm/documentation`
22
+
23
+ ---
24
+
25
+ ## Authentication
26
+
27
+ Three methods, in order of recommendation:
28
+
29
+ ### 1. API Key (Simple, Recommended for Dev/Internal)
30
+
31
+ Create an API User at Administration > API Users. Authentication method: "API Key". Assign a Role for scope.
32
+
33
+ ```
34
+ X-Api-Key: {key_from_api_user_detail_view}
35
+ ```
36
+
37
+ One header. Done. Use when transport is already secured (HTTPS, internal network).
38
+
39
+ ### 2. HMAC (Production, Most Secure)
40
+
41
+ Create an API User with "HMAC" auth. Both API Key and Secret Key are generated. Secret never leaves your server.
42
+
43
+ ```
44
+ X-Hmac-Authorization: base64(apiKey + ':' + hmacSha256(METHOD + ' /' + uri, secretKey))
45
+ ```
46
+
47
+ Where `METHOD` is uppercase (GET, POST, PUT, DELETE) and `uri` is the path after `/api/v1/`.
48
+
49
+ ```php
50
+ // PHP
51
+ $string = $method . ' /' . $uri;
52
+ $hash = hash_hmac('sha256', $string, $secretKey);
53
+ $header = base64_encode($apiKey . ':' . $hash);
54
+ // X-Hmac-Authorization: $header
55
+ ```
56
+
57
+ ```javascript
58
+ // Node.js
59
+ import { createHmac } from 'node:crypto';
60
+ const hash = createHmac('sha256', secretKey).update(`${method} /${uri}`).digest('hex');
61
+ const header = Buffer.from(`${apiKey}:${hash}`).toString('base64');
62
+ // X-Hmac-Authorization: header
63
+ ```
64
+
65
+ ### 3. Basic / Token Auth (Session-Based Only)
66
+
67
+ ```
68
+ Espo-Authorization: base64(username + ':' + token)
69
+ ```
70
+
71
+ Obtain token via `GET App/user` with initial credentials. Only for session flows (SPA, frontend). Never for server-to-server.
72
+
73
+ ### Auth Decision
74
+
75
+ | Context | Method |
76
+ |---|---|
77
+ | Dev/testing, internal scripts | API Key |
78
+ | Production integrations | HMAC |
79
+ | Frontend SPA, session flows | Token auth |
80
+ | Never | Basic auth with plaintext password |
81
+
82
+ ---
83
+
84
+ ## CRUD Operations
85
+
86
+ All entity types share the same endpoint pattern. Replace `{Entity}` with the type name (Account, Contact, Lead, CMyCustomEntity, etc.).
87
+
88
+ ### List Records
89
+ ```
90
+ GET {Entity}
91
+ ```
92
+ Returns `{"list": [...], "total": N}`. Total is `-1` if more records exist (pagination needed), `-2` if count disabled.
93
+
94
+ ### Read One Record
95
+ ```
96
+ GET {Entity}/{id}
97
+ ```
98
+
99
+ ### Create
100
+ ```
101
+ POST {Entity}
102
+ {"name": "Acme Corp", "assignedUserId": "someUserId"}
103
+ ```
104
+ Returns the created record with generated `id`. Use `X-Skip-Duplicate-Check: true` to bypass duplicate detection.
105
+
106
+ ### Update (Partial)
107
+ ```
108
+ PUT {Entity}/{id}
109
+ {"status": "Closed Won"}
110
+ ```
111
+ Only send changed fields. Returns full updated record.
112
+
113
+ ### Delete
114
+ ```
115
+ DELETE {Entity}/{id}
116
+ ```
117
+ Returns `true`.
118
+
119
+ ### Endpoint Summary
120
+
121
+ | Operation | Method | Path |
122
+ |---|---|---|
123
+ | List | GET | `{Entity}` |
124
+ | Read | GET | `{Entity}/{id}` |
125
+ | Create | POST | `{Entity}` |
126
+ | Update | PUT | `{Entity}/{id}` |
127
+ | Delete | DELETE | `{Entity}/{id}` |
128
+ | List related | GET | `{Entity}/{id}/{link}` |
129
+ | Link | POST | `{Entity}/{id}/{link}` |
130
+ | Unlink | DELETE | `{Entity}/{id}/{link}` |
131
+ | Mass update | POST | `{Entity}/action/massUpdate` |
132
+ | Mass delete | POST | `{Entity}/action/massDelete` |
133
+ | Stream/notes | GET | `{Entity}/{id}/stream` |
134
+ | Webhook CRUD | POST/DELETE | `Webhook` / `Webhook/{id}` |
135
+ | Auth token | GET | `App/user` |
136
+ | Attachment up | POST | `Attachment` |
137
+ | Attachment down | GET | `Attachment/file/{id}` |
138
+ | OpenAPI spec | GET | `OpenApi` |
139
+
140
+ ---
141
+
142
+ ## Filtering & Search
143
+
144
+ Parameters go as query params or as a single JSON-encoded `searchParams` param.
145
+
146
+ ### Core Parameters
147
+
148
+ | Param | Type | Purpose |
149
+ |---|---|---|
150
+ | `select` | string | Comma-separated fields: `id,name,status` |
151
+ | `maxSize` | int | Records per page (max 200, default varies) |
152
+ | `offset` | int | Pagination offset |
153
+ | `orderBy` | string | Sort field |
154
+ | `order` | string | `asc` or `desc` |
155
+ | `where` | array | Filter conditions (see below) |
156
+ | `primaryFilter` | string | Named server-side filter (`open`, `onlyMy`, etc.) |
157
+ | `boolFilterList` | array | Boolean toggles: `["onlyMy", "followed"]` |
158
+
159
+ v9.0+ aliases (WAF-safe): `attributeSelect` for `select`, `whereGroup` for `where`.
160
+
161
+ ### WHERE Operators
162
+
163
+ Each filter is `{"type": "...", "attribute": "...", "value": "..."}`. Multiple items in the `where` array are implicitly ANDed. Use `{"type": "or", "value": [...]}` for OR logic.
164
+
165
+ **Operator categories**: equality (`equals`, `notEquals`), comparison (`greaterThan`, `lessThan`, `greaterThanOrEquals`, `lessThanOrEquals`), null (`isNull`, `isNotNull`), boolean (`isTrue`, `isFalse`), string (`contains`, `notContains`, `startsWith`, `endsWith`, `like`, `notLike`), set (`in`, `notIn`), relationship (`linkedWith`, `notLinkedWith`, `isLinked`, `isNotLinked`), date helpers (`today`, `past`, `future`, `lastSevenDays`, `currentMonth`, `lastMonth`, `currentYear`, `between`, `lastXDays`, `nextXDays`), logical (`or`, `and`).
166
+
167
+ Full operator reference with examples: `references/where-operators.md`
168
+
169
+ ---
170
+
171
+ ## Relationships
172
+
173
+ Link names are visible at Administration > Entity Manager > {Entity} > Relationships (4th column).
174
+
175
+ ### List Related Records
176
+ ```
177
+ GET Account/{id}/contacts?select=id,name,emailAddress&maxSize=50
178
+ ```
179
+ Same search params as list endpoint.
180
+
181
+ ### Link Records
182
+ ```
183
+ POST Account/{id}/contacts
184
+ {"id": "contactId"}
185
+ ```
186
+
187
+ Multiple at once:
188
+ ```json
189
+ {"ids": ["id1", "id2", "id3"]}
190
+ ```
191
+
192
+ Mass relate by filter:
193
+ ```json
194
+ {"massRelate": true, "where": [{"type": "equals", "attribute": "status", "value": "Active"}]}
195
+ ```
196
+
197
+ ### Unlink Records
198
+ ```
199
+ DELETE Account/{id}/contacts
200
+ {"id": "contactId"}
201
+ ```
202
+
203
+ Multiple: `{"ids": ["id1", "id2"]}`.
204
+
205
+ ---
206
+
207
+ ## Webhooks
208
+
209
+ ### Register
210
+ ```
211
+ POST Webhook
212
+ {"event": "Contact.create", "url": "https://your-server.com/hook"}
213
+ ```
214
+ Returns `{"id": "webhookId", "secretKey": "generatedKey"}`. Save both for signature verification.
215
+
216
+ ### Event Types
217
+ - `{Entity}.create` — record created (all attributes in payload)
218
+ - `{Entity}.update` — record updated (only changed attributes)
219
+ - `{Entity}.delete` — record removed (ID only)
220
+ - `{Entity}.fieldUpdate.{field}` — specific field changed
221
+
222
+ ### Payload Format
223
+ Always an array (even for single events):
224
+ ```json
225
+ [{"id": "abc123", "name": "Updated Name", "status": "Active"}]
226
+ ```
227
+
228
+ ### Signature Verification
229
+ The `Signature` header contains: `base64(webhookId + ':' + hmacSha256(rawBody, secretKey))`.
230
+
231
+ ```php
232
+ $expected = base64_encode($webhookId . ':' . hash_hmac('sha256', $rawBody, $secretKey));
233
+ $valid = hash_equals($expected, $_SERVER['HTTP_SIGNATURE']);
234
+ ```
235
+
236
+ ### Lifecycle
237
+ - Processed by scheduled job "Process Webhook Queue" (default: every 5 min)
238
+ - Failed deliveries are retried automatically
239
+ - Persistent failures deactivate the webhook
240
+ - Config: `webhookAllowedAddressList` in `data/config.php` for internal URLs
241
+
242
+ ### Delete
243
+ ```
244
+ DELETE Webhook/{id}
245
+ ```
246
+
247
+ ---
248
+
249
+ ## Mass Operations
250
+
251
+ ### Mass Update
252
+ ```
253
+ POST Lead/action/massUpdate
254
+ ```
255
+
256
+ By IDs:
257
+ ```json
258
+ {"ids": ["id1", "id2"], "data": {"assignedUserId": "userId", "status": "In Process"}}
259
+ ```
260
+
261
+ By filter:
262
+ ```json
263
+ {"where": [{"type": "equals", "attribute": "status", "value": "New"}], "data": {"status": "Assigned"}}
264
+ ```
265
+
266
+ ### Mass Delete
267
+ ```
268
+ POST Lead/action/massDelete
269
+ ```
270
+ Same payload patterns — `ids` array or `where` filter.
271
+
272
+ ### No Bulk Create
273
+ There is no native batch create endpoint. For bulk ingestion:
274
+ 1. Loop individual POST requests with reasonable pacing
275
+ 2. Use the built-in Import feature (Administration > Import) for CSV
276
+ 3. Write a custom API action for batch processing if volume demands it
277
+
278
+ ### Important
279
+ API Before-Save Scripts (Formula) are **not executed** during mass update operations.
280
+
281
+ ---
282
+
283
+ ## Error Handling
284
+
285
+ ### Status Codes
286
+
287
+ | Code | Meaning | Action |
288
+ |---|---|---|
289
+ | 200 | Success | — |
290
+ | 400 | Bad Request | Check required fields, validation |
291
+ | 401 | Unauthorized | Check auth headers/credentials |
292
+ | 403 | Forbidden | Check API User role/ACL |
293
+ | 404 | Not Found | Record doesn't exist or no read access |
294
+ | 409 | Conflict | Duplicate detected or record locked |
295
+ | 500 | Server Error | Check `data/log` on server |
296
+
297
+ ### Error Details
298
+ Error reason is in the `X-Status-Reason` response header (not always in the body).
299
+
300
+ ### Duplicate Detection (409)
301
+ ```json
302
+ {"reason": "Duplicate", "data": {"idList": ["existingId1"]}}
303
+ ```
304
+ Bypass with `X-Skip-Duplicate-Check: true` header.
305
+
306
+ ---
307
+
308
+ ## Performance Best Practices
309
+
310
+ 1. **Select only needed fields** — `?select=id,name,status` avoids loading all attributes
311
+ 2. **Skip total count** — `X-No-Total: true` header skips the COUNT query on list requests
312
+ 3. **Paginate** — use `offset` + `maxSize` (keep maxSize at 50-100)
313
+ 4. **Use primary filters** — server-optimized named filters are faster than complex WHERE
314
+ 5. **Minimal API User roles** — dedicated API users with only required scopes
315
+ 6. **No native rate limiter** — implement at reverse proxy level (nginx, Apache) if needed
316
+ 7. **OpenAPI spec** — `GET OpenApi` returns full schema for your instance including custom entities (v9.3+, admin only)
317
+
318
+ ---
319
+
320
+ ## Client Libraries
321
+
322
+ ### PHP (Official — Preferred)
323
+
324
+ `composer require espocrm/php-espo-api-client`
325
+
326
+ Class: `Espo\ApiClient\Client`. Constructor takes base URL. Auth via `setApiKey()` or `setApiKey()` + `setSecretKey()` for HMAC. All requests through `$client->request(METHOD, path, params, payload)`.
327
+
328
+ ### Node.js
329
+
330
+ No official npm package. Build a thin client around `fetch` with:
331
+ - Base URL + `/api/v1/` prefix
332
+ - `X-Api-Key` header (or compute HMAC per-request)
333
+ - `Content-Type: application/json`
334
+ - Search params as JSON-encoded `searchParams` query param
335
+ - Error extraction from `X-Status-Reason` header on non-2xx responses
336
+
337
+ ---
338
+
339
+ ## Field Types & API Representation
340
+
341
+ | Field Type | JSON Type | Example |
342
+ |---|---|---|
343
+ | varchar | string | `"name": "Test"` |
344
+ | text | string | `"description": "Long text"` |
345
+ | int | number | `"quantity": 5` |
346
+ | float | number | `"rate": 4.5` |
347
+ | boolean | boolean | `"isActive": true` |
348
+ | enum | string | `"status": "New"` |
349
+ | multiEnum | string[] | `"tags": ["A", "B"]` |
350
+ | date | string | `"closeDate": "2025-06-15"` |
351
+ | datetime | string (UTC) | `"createdAt": "2025-06-15 14:30:00"` |
352
+ | currency | number + string | `"amount": 1000, "amountCurrency": "USD"` |
353
+ | link | string (ID) | `"accountId": "someId"` |
354
+ | linkMultiple | string[] + object | `"teamsIds": ["id1"], "teamsNames": {"id1": "Sales"}` |
355
+ | email | string | `"emailAddress": "test@example.com"` |
356
+ | phone | string | `"phoneNumber": "+1234567890"` |
357
+ | address | multiple fields | `"billingAddressStreet": "123 Main", "billingAddressCity": "NYC"` |
358
+ | file | string (ID) | `"fileId": "attachmentId"` |
359
+
360
+ All datetime values are UTC. Date format: `YYYY-MM-DD`. Datetime: `YYYY-MM-DD HH:mm:ss`.
@@ -0,0 +1,84 @@
1
+ # EspoCRM WHERE Filter Operators
2
+
3
+ Complete reference for the `where` array filter objects used in list/search requests.
4
+
5
+ Each filter: `{"type": "...", "attribute": "...", "value": "..."}`
6
+
7
+ ## Equality & Comparison
8
+
9
+ | Type | Value | Example |
10
+ |---|---|---|
11
+ | `equals` | any | `{"type": "equals", "attribute": "status", "value": "New"}` |
12
+ | `notEquals` | any | `{"type": "notEquals", "attribute": "status", "value": "Canceled"}` |
13
+ | `greaterThan` | number/date | `{"type": "greaterThan", "attribute": "amount", "value": 1000}` |
14
+ | `lessThan` | number/date | `{"type": "lessThan", "attribute": "amount", "value": 5000}` |
15
+ | `greaterThanOrEquals` | number/date | `{"type": "greaterThanOrEquals", "attribute": "probability", "value": 50}` |
16
+ | `lessThanOrEquals` | number/date | `{"type": "lessThanOrEquals", "attribute": "probability", "value": 100}` |
17
+
18
+ ## Null & Boolean
19
+
20
+ | Type | Notes |
21
+ |---|---|
22
+ | `isNull` | No `value` needed |
23
+ | `isNotNull` | No `value` needed |
24
+ | `isTrue` | Boolean field check |
25
+ | `isFalse` | Boolean field check |
26
+
27
+ ## String Matching
28
+
29
+ | Type | Value | Behavior |
30
+ |---|---|---|
31
+ | `contains` | string | `%value%` |
32
+ | `notContains` | string | NOT `%value%` |
33
+ | `startsWith` | string | `value%` |
34
+ | `endsWith` | string | `%value` |
35
+ | `like` | string | Raw LIKE pattern (use `%` wildcards) |
36
+ | `notLike` | string | Raw NOT LIKE pattern |
37
+
38
+ ## Set Membership
39
+
40
+ | Type | Value |
41
+ |---|---|
42
+ | `in` | array of strings/numbers: `["New", "Assigned"]` |
43
+ | `notIn` | array of strings/numbers: `["Canceled", "Recycled"]` |
44
+
45
+ ## Relationship Filters
46
+
47
+ | Type | Value | Notes |
48
+ |---|---|---|
49
+ | `linkedWith` | array of IDs | Records linked to ANY of the given IDs |
50
+ | `notLinkedWith` | array of IDs | Records NOT linked to any of the given IDs |
51
+ | `isLinked` | — | Has at least one linked record |
52
+ | `isNotLinked` | — | Has no linked records |
53
+
54
+ ## Date/Time Helpers
55
+
56
+ No `value` needed unless noted:
57
+
58
+ | Type | Notes |
59
+ |---|---|
60
+ | `today` | |
61
+ | `past` | |
62
+ | `future` | |
63
+ | `lastSevenDays` | |
64
+ | `currentMonth` | |
65
+ | `lastMonth` | |
66
+ | `currentQuarter` | |
67
+ | `currentYear` | |
68
+ | `lastXDays` | `value`: number of days |
69
+ | `nextXDays` | `value`: number of days |
70
+ | `between` | `value`: `["YYYY-MM-DD", "YYYY-MM-DD"]` |
71
+
72
+ ## Logical Combinators
73
+
74
+ Multiple items in the `where` array are implicitly **ANDed**.
75
+
76
+ For OR logic, wrap in a combinator:
77
+ ```json
78
+ {"type": "or", "value": [
79
+ {"type": "equals", "attribute": "status", "value": "New"},
80
+ {"type": "equals", "attribute": "status", "value": "Assigned"}
81
+ ]}
82
+ ```
83
+
84
+ Combinators nest: `and` and `or` can contain other combinators.
@@ -158,6 +158,21 @@ Every abstraction has a cost:
158
158
 
159
159
  **The question to ask:** Does this abstraction pay for itself? Will I use it enough to justify the cost? Would a junior developer understand it?
160
160
 
161
+ ### File Size as a Smell
162
+
163
+ Keep files under 500 lines. This isn't an arbitrary limit — it's a smell detector. A file approaching 500 lines almost certainly has multiple responsibilities that should be separated.
164
+
165
+ **When a file grows too large:**
166
+ - Split by responsibility, not by arbitrary line count
167
+ - A 300-line file with two unrelated concerns is worse than a 480-line file with one
168
+ - The goal is cohesion: each file should have a single, clear reason to exist
169
+
170
+ **Why this matters:**
171
+ - Readability: Developers can hold one file's purpose in their head
172
+ - Testability: Smaller, focused files are easier to test in isolation
173
+ - Navigation: Finding what you need is faster in a well-structured codebase
174
+ - Review: Code review quality drops sharply beyond 500 lines
175
+
161
176
  ### Context-Appropriate Complexity
162
177
 
163
178
  A CLI script has different needs than a production API.