@docyrus/docyrus 0.0.30 → 0.0.31

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 (20) hide show
  1. package/main.js +3 -3
  2. package/main.js.map +1 -1
  3. package/package.json +3 -3
  4. package/resources/pi-agent/skills/docyrus-api-dev/SKILL.md +161 -0
  5. package/resources/pi-agent/skills/docyrus-api-dev/references/api-client.md +349 -0
  6. package/resources/pi-agent/skills/docyrus-api-dev/references/authentication.md +299 -0
  7. package/resources/pi-agent/skills/docyrus-api-dev/references/data-source-query-guide.md +2063 -0
  8. package/resources/pi-agent/skills/docyrus-api-dev/references/formula-design-guide-llm.md +312 -0
  9. package/resources/pi-agent/skills/docyrus-api-dev/references/query-and-formulas.md +592 -0
  10. package/resources/pi-agent/skills/docyrus-app-dev-react/SKILL.md +334 -0
  11. package/resources/pi-agent/skills/docyrus-app-dev-react/references/README.md +28 -0
  12. package/resources/pi-agent/skills/docyrus-app-dev-react/references/api-client-and-auth.md +326 -0
  13. package/resources/pi-agent/skills/docyrus-app-dev-react/references/collections-and-patterns.md +352 -0
  14. package/resources/pi-agent/skills/docyrus-app-dev-react/references/component-selection-guide.md +602 -0
  15. package/resources/pi-agent/skills/docyrus-app-dev-react/references/icon-usage-guide.md +463 -0
  16. package/resources/pi-agent/skills/docyrus-app-dev-react/references/preferred-components-catalog.md +242 -0
  17. package/resources/pi-agent/skills/docyrus-platform/SKILL.md +2 -2
  18. package/resources/pi-agent/skills/docyrus-platform/references/auth-and-multi-tenancy.md +9 -1
  19. package/resources/pi-agent/skills/docyrus-platform/references/developer-tools.md +3 -2
  20. package/resources/pi-agent/extensions/multi-edit.ts +0 -835
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@docyrus/docyrus",
3
- "version": "0.0.30",
3
+ "version": "0.0.31",
4
4
  "private": false,
5
5
  "description": "Docyrus API CLI",
6
6
  "main": "./main.js",
@@ -10,8 +10,8 @@
10
10
  "dependencies": {
11
11
  "@clack/prompts": "^0.11.0",
12
12
  "@hono/node-server": "^1.14.1",
13
- "@mariozechner/pi-ai": "0.63.1",
14
- "@mariozechner/pi-coding-agent": "0.63.1",
13
+ "@mariozechner/pi-ai": "0.63.2",
14
+ "@mariozechner/pi-coding-agent": "0.63.2",
15
15
  "@modelcontextprotocol/ext-apps": "^1.2.2",
16
16
  "@modelcontextprotocol/sdk": "^1.25.1",
17
17
  "@mozilla/readability": "^0.6.0",
@@ -0,0 +1,161 @@
1
+ ---
2
+ name: docyrus-api-dev
3
+ description: Develop applications using the Docyrus API with @docyrus/api-client and @docyrus/signin libraries. Use when building apps that authenticate with Docyrus OAuth2 (PKCE, iframe, client credentials, device code), make REST API calls to Docyrus data source endpoints, or construct query payloads with filters, aggregations, formulas, pivots, and child queries. Triggers on tasks involving Docyrus API integration, @docyrus/api-client usage, @docyrus/signin authentication, data source query building, or Docyrus REST endpoint consumption.
4
+ ---
5
+
6
+ # Docyrus API Developer
7
+
8
+ Integrate with the Docyrus API using `@docyrus/api-client` (REST client) and `@docyrus/signin` (React auth provider). Authenticate via OAuth2 PKCE, query data sources with powerful filtering/aggregation, and consume REST endpoints.
9
+
10
+ ## Authentication Quick Start
11
+
12
+ ### React Apps — Use @docyrus/signin
13
+
14
+ ```tsx
15
+ import { DocyrusAuthProvider, useDocyrusAuth, useDocyrusClient, SignInButton } from '@docyrus/signin'
16
+
17
+ // 1. Wrap root
18
+ <DocyrusAuthProvider
19
+ apiUrl={import.meta.env.VITE_API_BASE_URL}
20
+ clientId={import.meta.env.VITE_OAUTH2_CLIENT_ID}
21
+ redirectUri={import.meta.env.VITE_OAUTH2_REDIRECT_URI}
22
+ scopes={['offline_access', 'Read.All', 'DS.ReadWrite.All', 'Users.Read']}
23
+ callbackPath="/auth/callback"
24
+ >
25
+ <App />
26
+ </DocyrusAuthProvider>
27
+
28
+ // 2. Use hooks
29
+ function App() {
30
+ const { status, signOut } = useDocyrusAuth()
31
+ const client = useDocyrusClient() // RestApiClient | null
32
+
33
+ if (status === 'loading') return <Spinner />
34
+ if (status === 'unauthenticated') return <SignInButton />
35
+
36
+ // client is ready — make API calls
37
+ const user = await client!.get('/v1/users/me')
38
+ }
39
+ ```
40
+
41
+ ### Non-React / Server — Use OAuth2Client Directly
42
+
43
+ ```typescript
44
+ import { RestApiClient, OAuth2Client, OAuth2TokenManagerAdapter, BrowserOAuth2TokenStorage } from '@docyrus/api-client'
45
+
46
+ const tokenStorage = new BrowserOAuth2TokenStorage(localStorage)
47
+ const oauth2 = new OAuth2Client({
48
+ baseURL: 'https://api.docyrus.com',
49
+ clientId: 'your-client-id',
50
+ redirectUri: 'http://localhost:3000/callback',
51
+ usePKCE: true,
52
+ tokenStorage,
53
+ })
54
+
55
+ // Auth Code flow
56
+ const { url } = await oauth2.getAuthorizationUrl({ scope: 'openid offline_access Users.Read' })
57
+ window.location.href = url
58
+ // After redirect:
59
+ const tokens = await oauth2.handleCallback(window.location.href)
60
+
61
+ // Create API client with auto-refresh
62
+ const client = new RestApiClient({
63
+ baseURL: 'https://api.docyrus.com',
64
+ tokenManager: new OAuth2TokenManagerAdapter(tokenStorage, async () => {
65
+ return (await oauth2.refreshAccessToken()).accessToken
66
+ }),
67
+ })
68
+ ```
69
+
70
+ ## API Endpoints
71
+
72
+ ### Data Source Items (Dynamic per tenant)
73
+ ```
74
+ GET /v1/apps/{appSlug}/data-sources/{slug}/items — List with query payload
75
+ GET /v1/apps/{appSlug}/data-sources/{slug}/items/{id} — Get one
76
+ POST /v1/apps/{appSlug}/data-sources/{slug}/items — Create
77
+ PATCH /v1/apps/{appSlug}/data-sources/{slug}/items/{id} — Update
78
+ DELETE /v1/apps/{appSlug}/data-sources/{slug}/items/{id} — Delete one
79
+ DELETE /v1/apps/{appSlug}/data-sources/{slug}/items — Delete many (body: { recordIds })
80
+ ```
81
+
82
+ Endpoints exist only if the data source is defined in the tenant. Check the tenant's OpenAPI spec at `GET /v1/api/openapi.json`.
83
+
84
+ ### System Endpoints (Always Available)
85
+ ```
86
+ GET /v1/users — List users
87
+ POST /v1/users — Create user
88
+ GET /v1/users/me — Current user profile
89
+ PATCH /v1/users/me — Update current user
90
+ ```
91
+
92
+ ### Making API Calls
93
+
94
+ ```typescript
95
+ // List items with query payload
96
+ const items = await client.get('/v1/apps/base/data-sources/project/items', {
97
+ columns: 'name, status, record_owner(firstname,lastname)',
98
+ filters: { rules: [{ field: 'status', operator: '!=', value: 'archived' }] },
99
+ orderBy: 'created_on DESC',
100
+ limit: 50,
101
+ })
102
+
103
+ // Get single item
104
+ const item = await client.get('/v1/apps/base/data-sources/project/items/uuid-here', {
105
+ columns: 'name, description, status',
106
+ })
107
+
108
+ // Create
109
+ const newItem = await client.post('/v1/apps/base/data-sources/project/items', {
110
+ name: 'New Project',
111
+ status: 'status-enum-id',
112
+ })
113
+
114
+ // Update
115
+ await client.patch('/v1/apps/base/data-sources/project/items/uuid-here', {
116
+ name: 'Updated Name',
117
+ })
118
+
119
+ // Delete
120
+ await client.delete('/v1/apps/base/data-sources/project/items/uuid-here')
121
+ ```
122
+
123
+ ## Query Payload Summary
124
+
125
+ The GET items endpoint accepts a powerful query payload:
126
+
127
+ | Feature | Purpose |
128
+ |---------|---------|
129
+ | `columns` | Select fields, expand relations `field(subfields)`, alias `alias:field`, spread `...field()` |
130
+ | `filters` | Nested AND/OR groups with 50+ operators (comparison, date shortcuts, user-related) |
131
+ | `filterKeyword` | Full-text search across all searchable fields |
132
+ | `orderBy` | Sort by fields with direction, including related fields |
133
+ | `limit`/`offset` | Pagination (default limit: 100) |
134
+ | `fullCount` | Return total matching count alongside results |
135
+ | `calculations` | Aggregations: count, sum, avg, min, max with grouping |
136
+ | `formulas` | Computed virtual columns (simple functions, block AST, correlated subqueries) |
137
+ | `childQueries` | Fetch related child records as nested JSON arrays |
138
+ | `pivot` | Cross-tab matrix queries with date range series |
139
+ | `expand` | Return full objects for relation/user/enum fields instead of IDs |
140
+
141
+ **For full query and formula references, read**:
142
+ - `references/data-source-query-guide.md`
143
+ - `references/formula-design-guide-llm.md`
144
+
145
+ ## Critical Rules
146
+
147
+ 1. **Always send `columns`** in list/get calls. Without it, only `id` is returned.
148
+ 2. **Data source endpoints are dynamic** — they exist only for data sources defined in the tenant.
149
+ 3. **Use `id` field** for `count` calculations. Use the actual field slug for `sum`, `avg`, `min`, `max`.
150
+ 4. **Child query keys must appear in `columns`** — if childQuery key is `orders`, include `orders` in columns.
151
+ 5. **Formula keys must appear in `columns`** — if formula key is `total`, include `total` in columns.
152
+ 6. **Filter by related field** using `rel_{{relation_field}}/{{field}}` syntax.
153
+
154
+ ## References
155
+
156
+ Read these files when you need detailed information:
157
+
158
+ - **`references/api-client.md`** — Full RestApiClient API, OAuth2Client (all flows: PKCE, client credentials, device code), token managers, interceptors, error classes, SSE/streaming, file upload/download, HTML to PDF, retry logic
159
+ - **`references/authentication.md`** — @docyrus/signin React provider, useDocyrusAuth/useDocyrusClient hooks, hasRole/hasPermission authorization helpers, SignInButton, standalone vs iframe auth modes, env vars, API client access pattern
160
+ - **`references/data-source-query-guide.md`** — Up-to-date query payload guide: columns, filters, orderBy, pagination, calculations, formulas, child queries, pivots, and operator reference
161
+ - **`references/formula-design-guide-llm.md`** — Up-to-date formula design guide for building and validating `formulas` payloads
@@ -0,0 +1,349 @@
1
+ # @docyrus/api-client Reference
2
+
3
+ ## Table of Contents
4
+
5
+ 1. [RestApiClient](#restapiclient)
6
+ 2. [HTTP Methods](#http-methods)
7
+ 3. [Configuration](#configuration)
8
+ 4. [Token Management](#token-management)
9
+ 5. [OAuth2Client](#oauth2client)
10
+ 6. [Interceptors](#interceptors)
11
+ 7. [Error Handling](#error-handling)
12
+ 8. [Streaming](#streaming)
13
+ 9. [File Operations](#file-operations)
14
+ 10. [Utilities](#utilities)
15
+
16
+ ---
17
+
18
+ ## RestApiClient
19
+
20
+ ```typescript
21
+ import { RestApiClient, MemoryTokenManager } from '@docyrus/api-client'
22
+
23
+ const client = new RestApiClient({
24
+ baseURL: 'https://api.docyrus.com',
25
+ tokenManager: new MemoryTokenManager(),
26
+ timeout: 5000,
27
+ headers: { 'X-API-Version': '1.0' },
28
+ })
29
+ ```
30
+
31
+ ---
32
+
33
+ ## HTTP Methods
34
+
35
+ ```typescript
36
+ // GET with query params
37
+ const users = await client.get<User[]>('/v1/users', { params: { page: 1, limit: 10 } })
38
+
39
+ // POST with body
40
+ const newUser = await client.post<User>('/v1/users', { name: 'John', email: 'john@example.com' })
41
+
42
+ // PATCH (partial update)
43
+ const updated = await client.patch<User>('/v1/users/123', { name: 'Jane' })
44
+
45
+ // PUT (full replace)
46
+ await client.put('/v1/users/123', { name: 'Jane', email: 'jane@example.com' })
47
+
48
+ // DELETE
49
+ await client.delete('/v1/users/123')
50
+
51
+ // DELETE with body
52
+ await client.delete('/v1/items', { recordIds: ['id1', 'id2'] })
53
+ ```
54
+
55
+ ### Typed Responses
56
+
57
+ ```typescript
58
+ interface ApiResponse<T> { data: T; meta: { page: number; total: number } }
59
+ const response = await client.get<ApiResponse<User[]>>('/v1/users')
60
+ const users: User[] = response.data.data
61
+ ```
62
+
63
+ ---
64
+
65
+ ## Configuration
66
+
67
+ ```typescript
68
+ interface ApiClientConfig {
69
+ baseURL?: string // Base URL for all requests
70
+ tokenManager?: TokenManager // Token manager instance
71
+ headers?: Record<string, string> // Default headers
72
+ timeout?: number // Request timeout in ms
73
+ fetch?: typeof fetch // Custom fetch implementation
74
+ FormData?: typeof FormData // Custom FormData
75
+ AbortController?: typeof AbortController
76
+ storage?: Storage // Browser storage for persistence
77
+ }
78
+ ```
79
+
80
+ ---
81
+
82
+ ## Token Management
83
+
84
+ ### MemoryTokenManager (default)
85
+ ```typescript
86
+ import { MemoryTokenManager } from '@docyrus/api-client'
87
+ const tokenManager = new MemoryTokenManager()
88
+ ```
89
+
90
+ ### StorageTokenManager (persistent)
91
+ ```typescript
92
+ import { StorageTokenManager } from '@docyrus/api-client'
93
+ const tokenManager = new StorageTokenManager(localStorage, 'auth_token')
94
+ ```
95
+
96
+ ### AsyncTokenManager (custom)
97
+ ```typescript
98
+ import { AsyncTokenManager } from '@docyrus/api-client'
99
+ const tokenManager = new AsyncTokenManager({
100
+ async getToken() { return await secureStorage.get('token') },
101
+ async setToken(token) { await secureStorage.set('token', token) },
102
+ async clearToken() { await secureStorage.remove('token') },
103
+ })
104
+ ```
105
+
106
+ ### Set Token Directly
107
+ ```typescript
108
+ await client.setAccessToken('your-auth-token')
109
+ ```
110
+
111
+ ---
112
+
113
+ ## OAuth2Client
114
+
115
+ Full OAuth2 support with PKCE, Device Code, and Client Credentials flows.
116
+
117
+ ### Setup
118
+ ```typescript
119
+ import { OAuth2Client, BrowserOAuth2TokenStorage } from '@docyrus/api-client'
120
+
121
+ const oauth2 = new OAuth2Client({
122
+ baseURL: 'https://api.docyrus.com',
123
+ clientId: 'your-client-id',
124
+ clientSecret: 'your-client-secret', // optional for public clients
125
+ redirectUri: 'http://localhost:3000/callback',
126
+ defaultScopes: ['openid', 'offline_access'],
127
+ usePKCE: true, // default: true
128
+ tokenStorage: new BrowserOAuth2TokenStorage(localStorage),
129
+ })
130
+ ```
131
+
132
+ ### Authorization Code Flow (PKCE)
133
+ ```typescript
134
+ // Step 1: Generate auth URL
135
+ const { url, state, codeVerifier } = await oauth2.getAuthorizationUrl({
136
+ scope: 'openid offline_access Users.Read',
137
+ })
138
+
139
+ // Step 2: Redirect user
140
+ window.location.href = url
141
+
142
+ // Step 3: Handle callback
143
+ const tokens = await oauth2.handleCallback(window.location.href)
144
+ // tokens: { accessToken, refreshToken, ... }
145
+ ```
146
+
147
+ ### Client Credentials Flow (server-to-server)
148
+ ```typescript
149
+ const tokens = await oauth2.getClientCredentialsToken({
150
+ scope: 'Read.All',
151
+ delegatedUserId: 'user-id-to-impersonate',
152
+ })
153
+ ```
154
+
155
+ ### Device Code Flow (CLI/headless)
156
+ ```typescript
157
+ const deviceAuth = await oauth2.startDeviceAuthorization('openid offline_access')
158
+ console.log(`Go to: ${deviceAuth.verification_uri}`)
159
+ console.log(`Enter code: ${deviceAuth.user_code}`)
160
+
161
+ const tokens = await oauth2.pollDeviceAuthorization(
162
+ deviceAuth.device_code, deviceAuth.interval, deviceAuth.expires_in,
163
+ { onExpired: () => console.log('Code expired'), signal: abortController.signal },
164
+ )
165
+ ```
166
+
167
+ ### Token Operations
168
+ ```typescript
169
+ const tokens = await oauth2.getTokens()
170
+ const isExpired = await oauth2.isTokenExpired()
171
+ const accessToken = await oauth2.getValidAccessToken() // auto-refreshes
172
+ const newTokens = await oauth2.refreshAccessToken()
173
+ await oauth2.revokeToken(tokens.refreshToken)
174
+ const tokenInfo = await oauth2.introspectToken(tokens.accessToken)
175
+ await oauth2.logout()
176
+ ```
177
+
178
+ ### Integrate OAuth2 with RestApiClient
179
+ ```typescript
180
+ import { RestApiClient, OAuth2Client, OAuth2TokenManagerAdapter, BrowserOAuth2TokenStorage } from '@docyrus/api-client'
181
+
182
+ const tokenStorage = new BrowserOAuth2TokenStorage(localStorage)
183
+ const oauth2 = new OAuth2Client({ baseURL: 'https://api.docyrus.com', clientId: 'id', tokenStorage })
184
+
185
+ const tokenManager = new OAuth2TokenManagerAdapter(tokenStorage, async () => {
186
+ const tokens = await oauth2.refreshAccessToken()
187
+ return tokens.accessToken
188
+ })
189
+
190
+ const apiClient = new RestApiClient({ baseURL: 'https://api.docyrus.com', tokenManager })
191
+ ```
192
+
193
+ ### Rate Limit Check
194
+ ```typescript
195
+ const rateLimit = await oauth2.checkRateLimit()
196
+ // { remaining, limit, reset }
197
+ ```
198
+
199
+ ### PKCE Utilities
200
+ ```typescript
201
+ import { generatePKCEChallenge, generateCodeVerifier, generateCodeChallenge, generateState, generateNonce } from '@docyrus/api-client'
202
+
203
+ const pkce = await generatePKCEChallenge()
204
+ // { codeVerifier, codeChallenge, codeChallengeMethod: 'S256' }
205
+ ```
206
+
207
+ ---
208
+
209
+ ## Interceptors
210
+
211
+ ```typescript
212
+ client.use({
213
+ // Transform outgoing requests
214
+ async request(config) {
215
+ config.headers = { ...config.headers, 'X-Request-Time': new Date().toISOString() }
216
+ return config
217
+ },
218
+ // Transform incoming responses
219
+ async response(response, request) {
220
+ console.log(`${request.url} took ${Date.now() - request.timestamp}ms`)
221
+ return response
222
+ },
223
+ // Handle errors globally
224
+ async error(error, request, response) {
225
+ if (error.status === 401) { await refreshToken() }
226
+ return { error, request, response }
227
+ },
228
+ })
229
+ ```
230
+
231
+ ### Common Interceptor: Unwrap Response Data
232
+ ```typescript
233
+ client.use({
234
+ response: (response) => {
235
+ if (response.data?.data && typeof response.data === 'object' && !Array.isArray(response.data)) {
236
+ response.data = response.data.data
237
+ }
238
+ return response
239
+ },
240
+ })
241
+ ```
242
+
243
+ ---
244
+
245
+ ## Error Handling
246
+
247
+ ```typescript
248
+ import {
249
+ ApiError, NetworkError, TimeoutError,
250
+ AuthenticationError, // 401
251
+ AuthorizationError, // 403
252
+ NotFoundError, // 404
253
+ RateLimitError, // 429 — has error.retryAfter
254
+ ValidationError,
255
+ // OAuth2-specific
256
+ OAuth2Error, InvalidGrantError, InvalidClientError,
257
+ AccessDeniedError, ExpiredTokenError, AuthorizationPendingError,
258
+ } from '@docyrus/api-client'
259
+
260
+ try {
261
+ await client.get('/resource')
262
+ } catch (error) {
263
+ if (error instanceof AuthenticationError) { /* re-login */ }
264
+ else if (error instanceof AuthorizationError) { /* forbidden */ }
265
+ else if (error instanceof NotFoundError) { /* 404 */ }
266
+ else if (error instanceof RateLimitError) { /* retry after error.retryAfter */ }
267
+ else if (error instanceof NetworkError) { /* offline */ }
268
+ else if (error instanceof TimeoutError) { /* timed out */ }
269
+ }
270
+ ```
271
+
272
+ ---
273
+
274
+ ## Streaming
275
+
276
+ ### Server-Sent Events (SSE)
277
+ ```typescript
278
+ const eventSource = client.sse('/events', {
279
+ onMessage(data) { console.log('Received:', data) },
280
+ onError(error) { console.error(error) },
281
+ onComplete() { console.log('Stream completed') },
282
+ })
283
+ eventSource.close()
284
+ ```
285
+
286
+ ### Chunked Streaming
287
+ ```typescript
288
+ for await (const chunk of client.stream('/stream', {
289
+ method: 'POST',
290
+ body: { query: 'stream data' },
291
+ })) {
292
+ console.log('Chunk:', chunk)
293
+ }
294
+ ```
295
+
296
+ ---
297
+
298
+ ## File Operations
299
+
300
+ ### Upload
301
+ ```typescript
302
+ const formData = new FormData()
303
+ formData.append('file', fileInput.files[0])
304
+ formData.append('description', 'My file')
305
+ await client.post('/upload', formData)
306
+ ```
307
+
308
+ ### Download
309
+ ```typescript
310
+ const response = await client.get('/download/file.pdf', { responseType: 'blob' })
311
+ const url = URL.createObjectURL(response.data)
312
+ const link = document.createElement('a')
313
+ link.href = url
314
+ link.download = 'file.pdf'
315
+ link.click()
316
+ ```
317
+
318
+ ### HTML to PDF
319
+ ```typescript
320
+ await client.html2pdf({
321
+ html: '<html><body>Content</body></html>',
322
+ // or: url: 'https://example.com',
323
+ options: { format: 'A4', margin: { top: 10, bottom: 10, left: 10, right: 10 }, landscape: false },
324
+ })
325
+ ```
326
+
327
+ ### Custom Query/Report
328
+ ```typescript
329
+ const results = await client.runCustomQuery(customQueryId, options)
330
+ // PUT reports/runCustomQuery/:customQueryId
331
+ ```
332
+
333
+ ---
334
+
335
+ ## Utilities
336
+
337
+ ```typescript
338
+ import { buildUrl, isAbortError, parseContentDisposition, createAbortSignal, jsonToQueryString, withRetry } from '@docyrus/api-client'
339
+
340
+ const url = buildUrl('/api/users', { page: 1, limit: 10 })
341
+ // '/api/users?page=1&limit=10'
342
+
343
+ const signal = createAbortSignal(5000) // 5s timeout
344
+
345
+ const response = await withRetry(() => client.get('/flaky'), {
346
+ retries: 3, retryDelay: 1000,
347
+ retryCondition: (error) => error.status >= 500,
348
+ })
349
+ ```