@zeyos/client 0.1.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 (110) hide show
  1. package/CHANGELOG.md +31 -0
  2. package/LICENSE +21 -0
  3. package/README.md +458 -0
  4. package/agents/README.md +66 -0
  5. package/agents/shared/business-app-benchmarks.md +111 -0
  6. package/agents/shared/zeyos-entity-map.md +142 -0
  7. package/agents/shared/zeyos-entity-reference.md +570 -0
  8. package/agents/shared/zeyos-query-patterns.md +89 -0
  9. package/agents/zeyos-account-intelligence/SKILL.md +34 -0
  10. package/agents/zeyos-account-intelligence/agents/openai.yaml +4 -0
  11. package/agents/zeyos-account-intelligence/references/workflows.md +84 -0
  12. package/agents/zeyos-billing-insights/SKILL.md +41 -0
  13. package/agents/zeyos-billing-insights/agents/openai.yaml +4 -0
  14. package/agents/zeyos-billing-insights/references/workflows.md +106 -0
  15. package/agents/zeyos-campaign-and-outreach/SKILL.md +44 -0
  16. package/agents/zeyos-campaign-and-outreach/agents/openai.yaml +4 -0
  17. package/agents/zeyos-campaign-and-outreach/references/workflows.md +100 -0
  18. package/agents/zeyos-collaboration-and-activity/SKILL.md +37 -0
  19. package/agents/zeyos-collaboration-and-activity/agents/openai.yaml +4 -0
  20. package/agents/zeyos-collaboration-and-activity/references/workflows.md +104 -0
  21. package/agents/zeyos-collections-and-dunning/SKILL.md +46 -0
  22. package/agents/zeyos-collections-and-dunning/agents/openai.yaml +4 -0
  23. package/agents/zeyos-collections-and-dunning/references/workflows.md +132 -0
  24. package/agents/zeyos-commerce-and-inventory/SKILL.md +38 -0
  25. package/agents/zeyos-commerce-and-inventory/agents/openai.yaml +4 -0
  26. package/agents/zeyos-commerce-and-inventory/references/workflows.md +101 -0
  27. package/agents/zeyos-mail-operations/SKILL.md +35 -0
  28. package/agents/zeyos-mail-operations/agents/openai.yaml +4 -0
  29. package/agents/zeyos-mail-operations/references/workflows.md +110 -0
  30. package/agents/zeyos-notes-and-sops/SKILL.md +31 -0
  31. package/agents/zeyos-notes-and-sops/agents/openai.yaml +4 -0
  32. package/agents/zeyos-notes-and-sops/references/workflows.md +85 -0
  33. package/agents/zeyos-platform-and-schema/SKILL.md +37 -0
  34. package/agents/zeyos-platform-and-schema/agents/openai.yaml +4 -0
  35. package/agents/zeyos-platform-and-schema/references/workflows.md +97 -0
  36. package/agents/zeyos-work-management/SKILL.md +45 -0
  37. package/agents/zeyos-work-management/agents/openai.yaml +4 -0
  38. package/agents/zeyos-work-management/references/workflows.md +148 -0
  39. package/docs/01-api-reference/01-data-retrieval.md +601 -0
  40. package/docs/01-api-reference/02-authentication.md +288 -0
  41. package/docs/01-api-reference/03-resources.md +270 -0
  42. package/docs/01-api-reference/04-schema.md +539 -0
  43. package/docs/01-api-reference/_category_.json +9 -0
  44. package/docs/02-javascript-client/01-getting-started.md +146 -0
  45. package/docs/02-javascript-client/02-authentication.md +287 -0
  46. package/docs/02-javascript-client/03-making-requests.md +572 -0
  47. package/docs/02-javascript-client/04-practical-guide.md +348 -0
  48. package/docs/02-javascript-client/_category_.json +9 -0
  49. package/docs/03-cli/01-getting-started.md +219 -0
  50. package/docs/03-cli/02-commands.md +407 -0
  51. package/docs/03-cli/03-configuration.md +220 -0
  52. package/docs/03-cli/_category_.json +9 -0
  53. package/docs/04-agent-workflows/00-coding-agents.md +35 -0
  54. package/docs/04-agent-workflows/01-agent-quickstart.md +147 -0
  55. package/docs/04-agent-workflows/02-agent-recipes.md +109 -0
  56. package/docs/04-agent-workflows/03-cli-coverage-and-escalation.md +65 -0
  57. package/docs/04-agent-workflows/_category_.json +9 -0
  58. package/docs/04-sample-apps/01-kanban.md +89 -0
  59. package/docs/04-sample-apps/02-crm.md +81 -0
  60. package/docs/04-sample-apps/03-dashboard.md +80 -0
  61. package/docs/04-sample-apps/_category_.json +9 -0
  62. package/docs/05-tutorials/00-application-developers.md +43 -0
  63. package/docs/05-tutorials/01-integration-architecture.md +60 -0
  64. package/docs/05-tutorials/02-build-your-own-zeyos-frontend.md +517 -0
  65. package/docs/05-tutorials/03-server-side-integrations.md +185 -0
  66. package/docs/05-tutorials/_category_.json +9 -0
  67. package/docs/intro.md +197 -0
  68. package/openapi/api.json +24308 -0
  69. package/openapi/auth.json +415 -0
  70. package/openapi/dbref.json +56223 -0
  71. package/openapi/oauth2.json +781 -0
  72. package/openapi/sdk.json +949 -0
  73. package/openapi/views.txt +642 -0
  74. package/package.json +49 -0
  75. package/samples/crm/README.md +28 -0
  76. package/samples/crm/index.html +327 -0
  77. package/samples/crm/js/api.js +208 -0
  78. package/samples/crm/js/auth.js +61 -0
  79. package/samples/crm/js/main.js +545 -0
  80. package/samples/crm/js/state.js +90 -0
  81. package/samples/crm/js/ui.js +51 -0
  82. package/samples/dashboard/README.md +28 -0
  83. package/samples/dashboard/index.html +280 -0
  84. package/samples/dashboard/js/api.js +197 -0
  85. package/samples/dashboard/js/auth.js +59 -0
  86. package/samples/dashboard/js/main.js +382 -0
  87. package/samples/dashboard/js/state.js +81 -0
  88. package/samples/dashboard/js/ui.js +48 -0
  89. package/samples/kanban/README.md +28 -0
  90. package/samples/kanban/index.html +263 -0
  91. package/samples/kanban/js/api.js +152 -0
  92. package/samples/kanban/js/auth.js +59 -0
  93. package/samples/kanban/js/constants.js +40 -0
  94. package/samples/kanban/js/kanban.js +246 -0
  95. package/samples/kanban/js/main.js +362 -0
  96. package/samples/kanban/js/modals.js +474 -0
  97. package/samples/kanban/js/settings.js +82 -0
  98. package/samples/kanban/js/state.js +118 -0
  99. package/samples/kanban/js/ui.js +49 -0
  100. package/scripts/generate-client.mjs +344 -0
  101. package/src/generated/operations.js +9772 -0
  102. package/src/generated/schema.js +8982 -0
  103. package/src/index.js +85 -0
  104. package/src/runtime/client.js +1208 -0
  105. package/src/runtime/error.js +29 -0
  106. package/src/runtime/http.js +174 -0
  107. package/src/runtime/request-shape.js +35 -0
  108. package/src/runtime/schema.js +206 -0
  109. package/src/runtime/suggest.js +74 -0
  110. package/src/runtime/token-store.js +105 -0
@@ -0,0 +1,327 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>ZeyOS CRM Contacts</title>
7
+ <script src="https://cdn.tailwindcss.com"></script>
8
+ <style>
9
+ /* Native dialog reset */
10
+ dialog { padding: 0; border: none; border-radius: 1rem; max-width: 90vw; }
11
+ dialog::backdrop { background: rgba(0,0,0,0.5); }
12
+
13
+ /* Sortable column header indicators */
14
+ .sort-asc::after { content: ' \25B2'; font-size: 0.65em; opacity: 0.7; }
15
+ .sort-desc::after { content: ' \25BC'; font-size: 0.65em; opacity: 0.7; }
16
+
17
+ /* Clickable table rows */
18
+ #accounts-tbody tr { cursor: pointer; }
19
+ #accounts-tbody tr:hover { background: #f1f5f9; }
20
+ </style>
21
+ </head>
22
+
23
+ <!--
24
+ +-----------------------------------------------------------------------+
25
+ | CONFIGURATION |
26
+ | |
27
+ | Set data-zeyos-url to your ZeyOS instance URL. |
28
+ | Optionally provide tokens obtained via the CLI (zeyos login). |
29
+ | |
30
+ | Alternatively, use the browser console: |
31
+ | ZeyOS.setUrl('https://cloud.zeyos.com/demo/') |
32
+ | ZeyOS.setToken('your-access-token') |
33
+ | ZeyOS.reconnect() |
34
+ | |
35
+ | Session mode only works from the same origin or with credentialed |
36
+ | CORS enabled on the ZeyOS instance. Use token mode for localhost. |
37
+ +-----------------------------------------------------------------------+
38
+ -->
39
+ <body class="bg-slate-100 min-h-screen font-sans antialiased"
40
+ data-zeyos-url=""
41
+ data-zeyos-accesstoken=""
42
+ data-zeyos-refreshtoken="">
43
+
44
+ <!-- == Connection Screen ================================================ -->
45
+ <div id="connection-screen" class="hidden min-h-screen flex items-center justify-center p-4">
46
+ <div class="bg-white rounded-2xl shadow-xl p-8 w-full max-w-md">
47
+
48
+ <!-- Logo / title -->
49
+ <div class="text-center mb-6">
50
+ <div class="w-14 h-14 rounded-2xl bg-blue-600 flex items-center justify-center mx-auto mb-4">
51
+ <svg class="w-8 h-8 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
52
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
53
+ d="M17 20h5v-2a3 3 0 00-5.356-1.857M17 20H7m10 0v-2c0-.656-.126-1.283-.356-1.857M7 20H2v-2a3 3 0 015.356-1.857M7 20v-2c0-.656.126-1.283.356-1.857m0 0a5.002 5.002 0 019.288 0M15 7a3 3 0 11-6 0 3 3 0 016 0z" />
54
+ </svg>
55
+ </div>
56
+ <h1 class="text-2xl font-bold text-slate-900">ZeyOS CRM Accounts</h1>
57
+ <p id="connection-message" class="text-slate-500 text-sm mt-1">Not connected</p>
58
+ </div>
59
+
60
+ <!-- Setup instructions -->
61
+ <div class="space-y-4 text-sm text-slate-600">
62
+
63
+ <div class="p-4 bg-blue-50 border border-blue-200 rounded-xl">
64
+ <h3 class="font-semibold text-blue-800 mb-2">Option 1 &mdash; Body Attributes</h3>
65
+ <p class="mb-2 leading-relaxed">Set connection details directly in <code class="text-xs bg-blue-100 px-1 py-0.5 rounded">index.html</code>:</p>
66
+ <pre class="text-xs bg-white p-3 rounded-lg border border-blue-100 overflow-x-auto leading-relaxed"><code>&lt;body
67
+ data-zeyos-url="https://cloud.zeyos.com/demo/"
68
+ data-zeyos-accesstoken="your-token"&gt;</code></pre>
69
+ </div>
70
+
71
+ <div class="p-4 bg-emerald-50 border border-emerald-200 rounded-xl">
72
+ <h3 class="font-semibold text-emerald-800 mb-2">Option 2 &mdash; Console API</h3>
73
+ <p class="mb-2 leading-relaxed">Open DevTools (<kbd class="text-xs bg-emerald-100 px-1.5 py-0.5 rounded font-mono">F12</kbd>) and run:</p>
74
+ <pre class="text-xs bg-white p-3 rounded-lg border border-emerald-100 overflow-x-auto leading-relaxed"><code>ZeyOS.setUrl('https://cloud.zeyos.com/demo/')
75
+ ZeyOS.setToken('your-access-token')
76
+ ZeyOS.reconnect()</code></pre>
77
+ </div>
78
+
79
+ <div class="p-4 bg-amber-50 border border-amber-200 rounded-xl">
80
+ <h3 class="font-semibold text-amber-800 mb-2">Option 3 &mdash; Session Mode</h3>
81
+ <p class="leading-relaxed">
82
+ Session mode only works from the same origin or when the ZeyOS instance
83
+ allows credentialed CORS. For localhost demos, use token mode.
84
+ </p>
85
+ </div>
86
+
87
+ <div class="p-3 bg-slate-50 border border-slate-200 rounded-xl text-xs text-slate-500 space-y-1.5">
88
+ <p>
89
+ <strong>Tip:</strong> Use the CLI to obtain a token with
90
+ <code class="bg-slate-100 px-1 py-0.5 rounded font-mono">zeyos login</code>, then explicitly print one with
91
+ <code class="bg-slate-100 px-1 py-0.5 rounded font-mono">zeyos whoami --show-token --json</code>
92
+ </p>
93
+ <p>
94
+ <strong>Debug:</strong> Run <code class="bg-slate-100 px-1 py-0.5 rounded font-mono">ZeyOS.status()</code>
95
+ in the console to see the current config.
96
+ </p>
97
+ </div>
98
+
99
+ </div>
100
+ </div>
101
+ </div>
102
+
103
+ <!-- == App Shell ======================================================== -->
104
+ <div id="app-shell" class="hidden flex flex-col" style="height:100dvh">
105
+
106
+ <!-- Navbar -->
107
+ <header class="bg-white border-b border-slate-200 px-4 py-2.5 flex items-center gap-3 flex-shrink-0 shadow-sm">
108
+
109
+ <!-- Logo -->
110
+ <div class="flex items-center gap-2 mr-2">
111
+ <div class="w-7 h-7 rounded-lg bg-blue-600 flex items-center justify-center">
112
+ <svg class="w-4 h-4 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
113
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
114
+ d="M17 20h5v-2a3 3 0 00-5.356-1.857M17 20H7m10 0v-2c0-.656-.126-1.283-.356-1.857M7 20H2v-2a3 3 0 015.356-1.857M7 20v-2c0-.656.126-1.283.356-1.857m0 0a5.002 5.002 0 019.288 0M15 7a3 3 0 11-6 0 3 3 0 016 0z" />
115
+ </svg>
116
+ </div>
117
+ <span class="font-semibold text-slate-800 hidden sm:inline">ZeyOS CRM</span>
118
+ </div>
119
+
120
+ <!-- Search bar -->
121
+ <div class="flex-1 max-w-md relative">
122
+ <svg class="w-4 h-4 text-slate-400 absolute left-3 top-1/2 -translate-y-1/2 pointer-events-none" fill="none" stroke="currentColor" viewBox="0 0 24 24">
123
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
124
+ d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" />
125
+ </svg>
126
+ <input id="search-input" type="text" placeholder="Search accounts..."
127
+ class="w-full pl-9 pr-3 py-1.5 rounded-lg bg-slate-100 border border-slate-200 text-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent placeholder-slate-400">
128
+ </div>
129
+
130
+ <div class="flex-1"></div>
131
+
132
+ <!-- Reload button -->
133
+ <button id="btn-reload" title="Reload accounts"
134
+ class="p-2 rounded-lg hover:bg-slate-100 text-slate-500 hover:text-slate-700 transition-colors">
135
+ <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
136
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
137
+ d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15" />
138
+ </svg>
139
+ </button>
140
+
141
+ <!-- New account -->
142
+ <button id="btn-new-account"
143
+ class="flex items-center gap-1.5 px-3 py-1.5 bg-blue-600 text-white rounded-lg text-sm font-medium hover:bg-blue-700 active:bg-blue-800 transition-colors">
144
+ <span class="text-base leading-none">+</span>
145
+ <span class="hidden sm:inline">New Account</span>
146
+ </button>
147
+
148
+ <!-- Logout -->
149
+ <button id="btn-logout" title="Log out"
150
+ class="p-2 rounded-lg hover:bg-slate-100 text-slate-500 hover:text-red-500 transition-colors">
151
+ <svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
152
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
153
+ d="M17 16l4-4m0 0l-4-4m4 4H7m6 4v1a3 3 0 01-3 3H6a3 3 0 01-3-3V7a3 3 0 013-3h4a3 3 0 013 3v1" />
154
+ </svg>
155
+ </button>
156
+
157
+ </header>
158
+
159
+ <!-- Main content area -->
160
+ <main class="flex-1 overflow-auto p-4">
161
+ <div class="bg-white rounded-xl shadow-sm border border-slate-200 overflow-hidden">
162
+
163
+ <!-- Table -->
164
+ <div class="overflow-x-auto">
165
+ <table class="w-full text-sm text-left">
166
+ <thead>
167
+ <tr class="bg-slate-50 border-b border-slate-200">
168
+ <th class="sortable-col px-4 py-3 font-semibold text-slate-600 cursor-pointer hover:text-blue-600 whitespace-nowrap select-none" data-sort="AccountNum">Account #</th>
169
+ <th class="sortable-col px-4 py-3 font-semibold text-slate-600 cursor-pointer hover:text-blue-600 whitespace-nowrap select-none" data-sort="Name">Name</th>
170
+ <th class="sortable-col px-4 py-3 font-semibold text-slate-600 cursor-pointer hover:text-blue-600 whitespace-nowrap select-none" data-sort="Email">Email</th>
171
+ <th class="sortable-col px-4 py-3 font-semibold text-slate-600 cursor-pointer hover:text-blue-600 whitespace-nowrap select-none" data-sort="Phone">Phone</th>
172
+ <th class="sortable-col px-4 py-3 font-semibold text-slate-600 cursor-pointer hover:text-blue-600 whitespace-nowrap select-none" data-sort="City">City</th>
173
+ <th class="sortable-col px-4 py-3 font-semibold text-slate-600 cursor-pointer hover:text-blue-600 whitespace-nowrap select-none" data-sort="AssignedUser">Assigned User</th>
174
+ <th class="sortable-col px-4 py-3 font-semibold text-slate-600 cursor-pointer hover:text-blue-600 whitespace-nowrap select-none" data-sort="Type">Type</th>
175
+ <th class="sortable-col px-4 py-3 font-semibold text-slate-600 cursor-pointer hover:text-blue-600 whitespace-nowrap select-none" data-sort="LastModified">Last Modified</th>
176
+ </tr>
177
+ </thead>
178
+ <tbody id="accounts-tbody" class="divide-y divide-slate-100">
179
+ <!-- Rows injected by main.js -->
180
+ </tbody>
181
+ </table>
182
+ </div>
183
+
184
+ <!-- Empty state -->
185
+ <div id="empty-state" class="hidden py-16 text-center">
186
+ <svg class="w-12 h-12 text-slate-300 mx-auto mb-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
187
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5"
188
+ d="M17 20h5v-2a3 3 0 00-5.356-1.857M17 20H7m10 0v-2c0-.656-.126-1.283-.356-1.857M7 20H2v-2a3 3 0 015.356-1.857M7 20v-2c0-.656.126-1.283.356-1.857m0 0a5.002 5.002 0 019.288 0M15 7a3 3 0 11-6 0 3 3 0 016 0z" />
189
+ </svg>
190
+ <p class="text-slate-500 font-medium">No accounts found</p>
191
+ <p class="text-slate-400 text-sm mt-1">Try adjusting your search or create a new account.</p>
192
+ </div>
193
+
194
+ <!-- Pagination -->
195
+ <div id="pagination-bar" class="flex items-center justify-between px-4 py-3 border-t border-slate-200 bg-slate-50">
196
+ <div class="text-xs text-slate-500">
197
+ <span id="pagination-info">Showing 0 accounts</span>
198
+ </div>
199
+ <div class="flex items-center gap-2">
200
+ <button id="btn-prev-page"
201
+ class="px-3 py-1.5 text-xs font-medium rounded-lg border border-slate-300 bg-white text-slate-600 hover:bg-slate-50 disabled:opacity-40 disabled:cursor-not-allowed transition-colors"
202
+ disabled>
203
+ &larr; Previous
204
+ </button>
205
+ <span id="page-indicator" class="text-xs text-slate-500 font-medium min-w-[60px] text-center">Page 1</span>
206
+ <button id="btn-next-page"
207
+ class="px-3 py-1.5 text-xs font-medium rounded-lg border border-slate-300 bg-white text-slate-600 hover:bg-slate-50 disabled:opacity-40 disabled:cursor-not-allowed transition-colors"
208
+ disabled>
209
+ Next &rarr;
210
+ </button>
211
+ </div>
212
+ </div>
213
+
214
+ </div>
215
+ </main>
216
+
217
+ </div>
218
+
219
+ <!-- == Account Edit / Create Dialog ===================================== -->
220
+ <dialog id="account-modal" style="width:min(560px,92vw)">
221
+ <div class="p-6">
222
+ <!-- Header -->
223
+ <div class="flex items-center justify-between mb-5">
224
+ <h2 id="modal-title" class="text-lg font-semibold text-slate-900">Edit Account</h2>
225
+ <button id="modal-close"
226
+ class="p-1.5 rounded-lg hover:bg-slate-100 text-slate-500 leading-none text-lg">&times;</button>
227
+ </div>
228
+
229
+ <!-- Form -->
230
+ <form id="account-form" class="space-y-4">
231
+ <input type="hidden" id="form-id" value="">
232
+
233
+ <!-- Name row -->
234
+ <div class="grid grid-cols-2 gap-4">
235
+ <div>
236
+ <label for="form-firstname" class="block text-xs font-medium text-slate-600 mb-1">First Name</label>
237
+ <input id="form-firstname" type="text"
238
+ class="w-full px-3 py-2 rounded-lg border border-slate-300 text-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent">
239
+ </div>
240
+ <div>
241
+ <label for="form-lastname" class="block text-xs font-medium text-slate-600 mb-1">Last Name / Company</label>
242
+ <input id="form-lastname" type="text" required
243
+ class="w-full px-3 py-2 rounded-lg border border-slate-300 text-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent">
244
+ </div>
245
+ </div>
246
+
247
+ <!-- Type -->
248
+ <div>
249
+ <label for="form-type" class="block text-xs font-medium text-slate-600 mb-1">Account Type</label>
250
+ <select id="form-type"
251
+ class="w-full px-3 py-2 rounded-lg border border-slate-300 text-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent bg-white">
252
+ <option value="0">Prospect</option>
253
+ <option value="1">Customer</option>
254
+ <option value="2">Supplier</option>
255
+ <option value="3">Customer &amp; Supplier</option>
256
+ <option value="4">Competitor</option>
257
+ <option value="5">Employee</option>
258
+ </select>
259
+ </div>
260
+
261
+ <!-- Description -->
262
+ <div>
263
+ <label for="form-description" class="block text-xs font-medium text-slate-600 mb-1">Description</label>
264
+ <textarea id="form-description" rows="3"
265
+ class="w-full px-3 py-2 rounded-lg border border-slate-300 text-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent resize-none"></textarea>
266
+ </div>
267
+
268
+ <!-- Contact info (read-only when editing, hidden on create) -->
269
+ <div id="contact-section" class="hidden">
270
+ <div class="border-t border-slate-200 pt-4 mt-4">
271
+ <h3 class="text-xs font-semibold uppercase tracking-widest text-slate-400 mb-3">Contact Info (read-only)</h3>
272
+ <p class="text-xs text-slate-400 mb-3">Contact details are managed on the linked contact record.</p>
273
+ <div class="grid grid-cols-2 gap-4">
274
+ <div>
275
+ <label class="block text-xs font-medium text-slate-500 mb-1">Email</label>
276
+ <div id="contact-email" class="px-3 py-2 rounded-lg bg-slate-50 border border-slate-200 text-sm text-slate-600 min-h-[38px]">&mdash;</div>
277
+ </div>
278
+ <div>
279
+ <label class="block text-xs font-medium text-slate-500 mb-1">Phone</label>
280
+ <div id="contact-phone" class="px-3 py-2 rounded-lg bg-slate-50 border border-slate-200 text-sm text-slate-600 min-h-[38px]">&mdash;</div>
281
+ </div>
282
+ <div class="col-span-2">
283
+ <label class="block text-xs font-medium text-slate-500 mb-1">City</label>
284
+ <div id="contact-city" class="px-3 py-2 rounded-lg bg-slate-50 border border-slate-200 text-sm text-slate-600 min-h-[38px]">&mdash;</div>
285
+ </div>
286
+ </div>
287
+ </div>
288
+ </div>
289
+
290
+ <!-- Actions -->
291
+ <div class="flex items-center justify-between pt-2">
292
+ <button type="button" id="btn-delete-account"
293
+ class="hidden text-sm text-red-500 hover:text-red-700 font-medium transition-colors">
294
+ Delete Account
295
+ </button>
296
+ <div class="flex items-center gap-3 ml-auto">
297
+ <button type="button" id="btn-cancel"
298
+ class="px-4 py-2 text-sm font-medium text-slate-600 hover:text-slate-800 transition-colors">
299
+ Cancel
300
+ </button>
301
+ <button type="submit" id="btn-save"
302
+ class="px-5 py-2 bg-blue-600 text-white rounded-lg text-sm font-medium hover:bg-blue-700 active:bg-blue-800 transition-colors">
303
+ Save
304
+ </button>
305
+ </div>
306
+ </div>
307
+ </form>
308
+ </div>
309
+ </dialog>
310
+
311
+ <!-- == Toast Container ================================================== -->
312
+ <div id="toast-container"
313
+ class="fixed bottom-5 right-5 z-50 flex flex-col gap-2 pointer-events-none">
314
+ </div>
315
+
316
+ <!-- == Loading Overlay ================================================== -->
317
+ <div id="loading-overlay"
318
+ class="hidden fixed inset-0 bg-white/60 backdrop-blur-sm z-50 flex items-center justify-center">
319
+ <div class="flex flex-col items-center gap-3">
320
+ <div class="w-8 h-8 border-4 border-blue-600 border-t-transparent rounded-full animate-spin"></div>
321
+ <span class="text-sm text-slate-600 font-medium">Loading&hellip;</span>
322
+ </div>
323
+ </div>
324
+
325
+ <script type="module" src="./js/main.js"></script>
326
+ </body>
327
+ </html>
@@ -0,0 +1,208 @@
1
+ /**
2
+ * ZeyOS API client singleton + high-level helper functions for the CRM sample.
3
+ * Supports two initialization modes:
4
+ * - Token mode: uses pre-obtained browser tokens via MemoryTokenStore
5
+ * - Session mode: uses browser session cookies
6
+ *
7
+ * Demonstrates:
8
+ * - Dot-notation joins (contact.email, contact.phone, contact.city, assigneduser.name)
9
+ * - Field aliasing via object-form fields
10
+ * - Full-text search via the `query` parameter
11
+ * - Pagination via limit/offset
12
+ * - Sorting via the sort parameter
13
+ *
14
+ * IMPORTANT implementation notes (from project memory):
15
+ * - Use `filters` (plural) NOT `filter` for list queries
16
+ * - Always include `visibility: 0` in filters
17
+ * - Normalise list responses via normalizeListResult()
18
+ */
19
+ import { createZeyosClient, MemoryTokenStore, normalizeListResult } from '../../../src/index.js';
20
+ import { loadTokens, saveTokens } from './state.js';
21
+
22
+ /**
23
+ * @typedef {null|boolean|number|string|JsonValue[]|Record<string, JsonValue>} JsonValue
24
+ * @typedef {Record<string, JsonValue>} ZeyosRecord
25
+ * @typedef {'AccountNum'|'Name'|'Email'|'Phone'|'City'|'AssignedUser'|'Type'|'LastModified'} AccountSortField
26
+ * @typedef {'asc'|'desc'} SortDirection
27
+ * @typedef {{ search?: string, sortField?: AccountSortField, sortDir?: SortDirection, limit?: number, offset?: number }} AccountListOptions
28
+ * @typedef {{ Id?: number|string, AccountNum?: string|number, Name?: string, FirstName?: string|null, Email?: string|null, Phone?: string|null, City?: string|null, AssignedUser?: string|null, Type?: number|string, LastModified?: number|string|null }} AccountListRow
29
+ * @typedef {{ firstname?: string|null, lastname?: string, type?: number, description?: string|null }} AccountWriteFields
30
+ */
31
+
32
+ export let client = null;
33
+ export let tokenStore = null;
34
+
35
+ // ── Client Initialization ──────────────────────────────────────────────────
36
+
37
+ /**
38
+ * Initialize client in token/OAuth mode.
39
+ * Uses pre-obtained tokens from localStorage / body attributes.
40
+ */
41
+ export function initTokenClient(url) {
42
+ const stored = loadTokens();
43
+ tokenStore = new MemoryTokenStore(stored ?? undefined);
44
+
45
+ client = createZeyosClient({
46
+ platform: url,
47
+ auth: {
48
+ mode: 'oauth',
49
+ oauth: {
50
+ tokenStore,
51
+ },
52
+ },
53
+ });
54
+ return client;
55
+ }
56
+
57
+ /**
58
+ * Initialize client in session mode.
59
+ * Uses browser cookies via credentials:'include'.
60
+ * Works when the user is already logged into ZeyOS in the same browser.
61
+ */
62
+ export function initSessionClient(url) {
63
+ tokenStore = null;
64
+
65
+ client = createZeyosClient({
66
+ platform: url,
67
+ auth: {
68
+ mode: 'session',
69
+ session: {
70
+ enabled: true,
71
+ credentials: 'include',
72
+ },
73
+ },
74
+ });
75
+ return client;
76
+ }
77
+
78
+ /**
79
+ * Persist the current token set back to localStorage (no-op in session mode).
80
+ * Called after API requests to keep stored token state aligned.
81
+ */
82
+ export async function syncTokens() {
83
+ if (!tokenStore) return; // session mode -- no tokens to sync
84
+ try {
85
+ const ts = await client.auth.getTokenSet();
86
+ if (ts?.accessToken) {
87
+ saveTokens({
88
+ accessToken: ts.accessToken,
89
+ refreshToken: ts.refreshToken,
90
+ expiresAt: ts.expiresAt,
91
+ refreshTokenExpiresAt: ts.refreshTokenExpiresAt,
92
+ });
93
+ }
94
+ } catch {
95
+ // non-critical
96
+ }
97
+ }
98
+
99
+ // ── Field-to-sort mapping ──────────────────────────────────────────────────
100
+ // Maps the aliased field names used in the UI to the raw API field paths
101
+ // needed by the `sort` parameter.
102
+
103
+ const SORT_MAP = {
104
+ AccountNum: 'customernum',
105
+ Name: 'lastname',
106
+ Email: 'contact.email',
107
+ Phone: 'contact.phone',
108
+ City: 'contact.city',
109
+ AssignedUser: 'assigneduser.name',
110
+ Type: 'type',
111
+ LastModified: 'lastmodified',
112
+ };
113
+
114
+ // ── Accounts ───────────────────────────────────────────────────────────────
115
+
116
+ /**
117
+ * Fetch a page of accounts with dot-notation joins and optional search.
118
+ *
119
+ * @param {AccountListOptions} [opts]
120
+ * @param {string} [opts.search] - Full-text search query
121
+ * @param {AccountSortField} [opts.sortField] - Aliased field name (e.g. 'Name', 'Email')
122
+ * @param {SortDirection} [opts.sortDir] - Sort direction
123
+ * @param {number} [opts.limit=25] - Records per page
124
+ * @param {number} [opts.offset=0] - Offset for pagination
125
+ * @returns {Promise<AccountListRow[]>} Account records with joined fields
126
+ */
127
+ export async function fetchAccounts({ search, sortField, sortDir, limit = 25, offset = 0 } = {}) {
128
+ // Build the sort parameter.
129
+ // Prefix with + (asc) or - (desc) followed by the raw API field path.
130
+ const rawSort = SORT_MAP[sortField] ?? 'lastmodified';
131
+ const prefix = sortDir === 'asc' ? '+' : '-';
132
+ const sort = [`${prefix}${rawSort}`];
133
+
134
+ // Build request parameters with aliased field names (object form).
135
+ // This demonstrates the field aliasing feature: the keys become the
136
+ // property names in the response, the values are the actual DB paths.
137
+ const params = {
138
+ fields: {
139
+ Id: 'ID',
140
+ AccountNum: 'customernum',
141
+ Name: 'lastname',
142
+ FirstName: 'firstname',
143
+ Email: 'contact.email',
144
+ Phone: 'contact.phone',
145
+ City: 'contact.city',
146
+ AssignedUser: 'assigneduser.name',
147
+ Type: 'type',
148
+ LastModified: 'lastmodified',
149
+ },
150
+ filters: { visibility: 0 },
151
+ sort,
152
+ limit,
153
+ offset,
154
+ };
155
+
156
+ // Full-text search via the `query` parameter (server-side search)
157
+ if (search && search.trim()) {
158
+ params.query = search.trim();
159
+ }
160
+
161
+ const result = await client.api.listAccounts(params);
162
+ await syncTokens();
163
+
164
+ return normalizeListResult(result).data;
165
+ }
166
+
167
+ /**
168
+ * Fetch a single account by ID with extended data.
169
+ * @param {number|string} id - Account ID
170
+ * @returns {Promise<ZeyosRecord>}
171
+ */
172
+ export async function getAccount(id) {
173
+ const result = await client.api.getAccount({ ID: id, extdata: 1 });
174
+ await syncTokens();
175
+ return result;
176
+ }
177
+
178
+ /**
179
+ * Create a new account.
180
+ * @param {AccountWriteFields} data - Account fields (lastname, firstname, type, description, etc.)
181
+ * @returns {Promise<ZeyosRecord>}
182
+ */
183
+ export async function createAccount(data) {
184
+ const result = await client.api.createAccount(data);
185
+ await syncTokens();
186
+ return result;
187
+ }
188
+
189
+ /**
190
+ * Update an existing account.
191
+ * @param {number|string} id - Account ID
192
+ * @param {AccountWriteFields} data - Fields to update
193
+ * @returns {Promise<ZeyosRecord>}
194
+ */
195
+ export async function updateAccount(id, data) {
196
+ const result = await client.api.updateAccount({ ID: id, body: data });
197
+ await syncTokens();
198
+ return result;
199
+ }
200
+
201
+ /**
202
+ * Delete an account by ID.
203
+ * @param {number|string} id - Account ID
204
+ */
205
+ export async function deleteAccount(id) {
206
+ await client.api.deleteAccount({ ID: id });
207
+ await syncTokens();
208
+ }
@@ -0,0 +1,61 @@
1
+ /**
2
+ * Authentication helpers.
3
+ * Supports two modes:
4
+ * - Token mode: pre-obtained browser tokens provided via body attributes or console API
5
+ * - Session mode: browser cookies sent to ZeyOS (user already logged in)
6
+ *
7
+ * Session detection probes /oauth2/v1/userinfo with credentials:'include'.
8
+ */
9
+ import { client } from './api.js';
10
+ import { loadTokens, clearTokens } from './state.js';
11
+
12
+ // ── Public API ─────────────────────────────────────────────────────────────
13
+
14
+ /**
15
+ * Probe the ZeyOS instance for an active browser session.
16
+ * Calls GET {url}/oauth2/v1/userinfo with credentials:'include' to
17
+ * check whether the user is already logged in via a session cookie.
18
+ * Returns the user info object on success, null otherwise.
19
+ */
20
+ export async function trySessionAuth(url) {
21
+ try {
22
+ const endpoint = `${url.replace(/\/+$/, '')}/oauth2/v1/userinfo`;
23
+ const res = await fetch(endpoint, {
24
+ method: 'GET',
25
+ credentials: 'include',
26
+ headers: { 'Accept': 'application/json' },
27
+ });
28
+ if (!res.ok) return null;
29
+ const data = await res.json();
30
+ // A valid userinfo response must have a subject (user identifier)
31
+ return (data && (data.sub || data.ID)) ? data : null;
32
+ } catch {
33
+ return null;
34
+ }
35
+ }
36
+
37
+ /**
38
+ * Returns true if a valid (non-expired) access token exists in localStorage.
39
+ */
40
+ export function isAuthenticated() {
41
+ const tokens = loadTokens();
42
+ if (!tokens?.accessToken) return false;
43
+ if (!tokens.expiresAt) return true;
44
+ // Token still valid with 2-minute buffer (expiresAt is Unix seconds)
45
+ return Date.now() / 1000 < tokens.expiresAt - 120;
46
+ }
47
+
48
+ /**
49
+ * Clear tokens and revoke the access token if possible.
50
+ */
51
+ export async function logout() {
52
+ const tokens = loadTokens();
53
+ if (tokens?.accessToken && client) {
54
+ try {
55
+ await client.oauth2.revokeToken({ token: tokens.accessToken });
56
+ } catch {
57
+ // best-effort revocation
58
+ }
59
+ }
60
+ clearTokens();
61
+ }