gufi-cli 0.1.49 → 0.1.51
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/dist/commands/docs.js +1 -5
- package/dist/lib/docs-resolver.d.ts +8 -0
- package/dist/lib/docs-resolver.js +27 -0
- package/dist/lib/security.js +5 -0
- package/dist/mcp.js +56 -43
- package/docs/dev-guide/1-01-architecture.md +358 -0
- package/docs/dev-guide/1-02-multi-tenant.md +415 -0
- package/docs/dev-guide/1-03-column-types.md +594 -0
- package/docs/dev-guide/1-04-json-config.md +442 -0
- package/docs/dev-guide/1-05-authentication.md +427 -0
- package/docs/dev-guide/2-01-api-reference.md +564 -0
- package/docs/dev-guide/2-02-automations.md +508 -0
- package/docs/dev-guide/2-03-gufi-cli.md +568 -0
- package/docs/dev-guide/2-04-realtime.md +401 -0
- package/docs/dev-guide/2-05-permissions.md +497 -0
- package/docs/dev-guide/2-06-integrations-overview.md +104 -0
- package/docs/dev-guide/2-07-stripe.md +173 -0
- package/docs/dev-guide/2-08-nayax.md +297 -0
- package/docs/dev-guide/2-09-ourvend.md +226 -0
- package/docs/dev-guide/2-10-tns.md +177 -0
- package/docs/dev-guide/2-11-custom-http.md +268 -0
- package/docs/dev-guide/3-01-custom-views.md +555 -0
- package/docs/dev-guide/3-02-webhooks-api.md +446 -0
- package/docs/mcp/00-overview.md +329 -0
- package/docs/mcp/01-architecture.md +226 -0
- package/docs/mcp/02-modules.md +285 -0
- package/docs/mcp/03-fields.md +357 -0
- package/docs/mcp/04-views.md +613 -0
- package/docs/mcp/05-automations.md +461 -0
- package/docs/mcp/06-api.md +531 -0
- package/docs/mcp/07-packages.md +246 -0
- package/docs/mcp/08-common-errors.md +284 -0
- package/docs/mcp/09-examples.md +453 -0
- package/docs/mcp/README.md +71 -0
- package/docs/mcp/tool-descriptions.json +64 -0
- package/package.json +3 -2
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: tns
|
|
3
|
+
title: "TNS Integration"
|
|
4
|
+
description: "Colombian product catalog"
|
|
5
|
+
icon: Zap
|
|
6
|
+
category: dev
|
|
7
|
+
part: 2
|
|
8
|
+
group: integrations
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
# TNS Integration
|
|
12
|
+
|
|
13
|
+
Colombian accounting and product catalog
|
|
14
|
+
|
|
15
|
+
## Overview
|
|
16
|
+
|
|
17
|
+
TNS is a Colombian accounting system that provides product catalog information. This integration syncs products and their categories from the TNS catalog.
|
|
18
|
+
|
|
19
|
+
## Setup
|
|
20
|
+
|
|
21
|
+
No API credentials needed - TNS provides a public product catalog.
|
|
22
|
+
|
|
23
|
+
Create these tables in your module:
|
|
24
|
+
- `products` - Product information
|
|
25
|
+
- `categories` - Product categories
|
|
26
|
+
|
|
27
|
+
## Available Methods
|
|
28
|
+
|
|
29
|
+
| Method | Description | Use Case |
|
|
30
|
+
|--------|-------------|----------|
|
|
31
|
+
| `sync_products` | Sync specific products | When you know product codes |
|
|
32
|
+
| `sync_products_from_sales` | Sync products from sales | **Recommended** - efficient |
|
|
33
|
+
| `sync_all_products` | Sync entire catalog | Large, slow - avoid if possible |
|
|
34
|
+
|
|
35
|
+
:::info
|
|
36
|
+
**Recommended:** Use `sync_products_from_sales` - it only syncs products that appear in your sales data, which is much faster than syncing the entire catalog.
|
|
37
|
+
:::
|
|
38
|
+
|
|
39
|
+
## Method: sync_products
|
|
40
|
+
|
|
41
|
+
Syncs products by their codes.
|
|
42
|
+
|
|
43
|
+
```javascript
|
|
44
|
+
const result = await gufi.integrations.tns.sync_products({
|
|
45
|
+
productsTable: 'tns.products',
|
|
46
|
+
categoriesTable: 'tns.categories',
|
|
47
|
+
env: 'production' // or 'sandbox'
|
|
48
|
+
});
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## Method: sync_products_from_sales
|
|
52
|
+
|
|
53
|
+
Syncs only products that appear in your sales data. Best for OurVend integration.
|
|
54
|
+
|
|
55
|
+
| Parameter | Required | Description |
|
|
56
|
+
|-----------|----------|-------------|
|
|
57
|
+
| productsTable | Yes | Target products table |
|
|
58
|
+
| categoriesTable | Yes | Target categories table |
|
|
59
|
+
| salesTable | Yes | Source sales table (e.g., `ourvend.sales`) |
|
|
60
|
+
| env | No | TNS environment (default: production) |
|
|
61
|
+
|
|
62
|
+
```javascript
|
|
63
|
+
const result = await gufi.integrations.tns.sync_products_from_sales({
|
|
64
|
+
productsTable: 'tns.products',
|
|
65
|
+
categoriesTable: 'tns.categories',
|
|
66
|
+
salesTable: 'ourvend.sales'
|
|
67
|
+
});
|
|
68
|
+
// Returns: { synced: 85, created: 12, productCodes: [...] }
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
## Method: sync_all_products
|
|
72
|
+
|
|
73
|
+
Syncs the entire TNS product catalog. Can be slow for large catalogs.
|
|
74
|
+
|
|
75
|
+
```javascript
|
|
76
|
+
const result = await gufi.integrations.tns.sync_all_products({
|
|
77
|
+
productsTable: 'tns.products',
|
|
78
|
+
categoriesTable: 'tns.categories',
|
|
79
|
+
env: 'production'
|
|
80
|
+
});
|
|
81
|
+
// Returns: { synced: 5000, created: 200, updated: 4800 }
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
:::warning
|
|
85
|
+
`sync_all_products` can be slow and may timeout for large catalogs. Use `sync_products_from_sales` for incremental syncing.
|
|
86
|
+
:::
|
|
87
|
+
|
|
88
|
+
## Example: Sync from OurVend Sales
|
|
89
|
+
|
|
90
|
+
After syncing OurVend sales, sync TNS product info:
|
|
91
|
+
|
|
92
|
+
```javascript
|
|
93
|
+
async function sync_tns_from_sales(gufi) {
|
|
94
|
+
const result = await gufi.integrations.tns.sync_products_from_sales({
|
|
95
|
+
productsTable: 'tns.products',
|
|
96
|
+
categoriesTable: 'tns.categories',
|
|
97
|
+
salesTable: 'ourvend.sales'
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
gufi.logger.info('TNS products synced', {
|
|
101
|
+
synced: result.synced,
|
|
102
|
+
created: result.created
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
return result;
|
|
106
|
+
}
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
## Complete OurVend + TNS Pipeline
|
|
110
|
+
|
|
111
|
+
Trigger: `scheduled` with cron `0 6 * * *` (6 AM daily)
|
|
112
|
+
|
|
113
|
+
```javascript
|
|
114
|
+
async function daily_vending_sync(gufi) {
|
|
115
|
+
const { env } = gufi.context;
|
|
116
|
+
|
|
117
|
+
// 1. Sync OurVend data
|
|
118
|
+
await gufi.integrations.ourvend.sync_groups({
|
|
119
|
+
user: env.OURVEND_USER,
|
|
120
|
+
password: env.OURVEND_PASSWORD,
|
|
121
|
+
tableName: 'ourvend.groups'
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
await gufi.integrations.ourvend.sync_machines({
|
|
125
|
+
user: env.OURVEND_USER,
|
|
126
|
+
password: env.OURVEND_PASSWORD,
|
|
127
|
+
tableName: 'ourvend.machines',
|
|
128
|
+
groupsTable: 'ourvend.groups'
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
await gufi.integrations.ourvend.sync_products({
|
|
132
|
+
user: env.OURVEND_USER,
|
|
133
|
+
password: env.OURVEND_PASSWORD,
|
|
134
|
+
tableName: 'ourvend.products'
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
const yesterday = new Date(Date.now() - 86400000).toISOString().split('T')[0];
|
|
138
|
+
const today = new Date().toISOString().split('T')[0];
|
|
139
|
+
|
|
140
|
+
const sales = await gufi.integrations.ourvend.sync_sales({
|
|
141
|
+
user: env.OURVEND_USER,
|
|
142
|
+
password: env.OURVEND_PASSWORD,
|
|
143
|
+
tableName: 'ourvend.sales',
|
|
144
|
+
machinesTable: 'ourvend.machines',
|
|
145
|
+
productsTable: 'ourvend.products',
|
|
146
|
+
groupsTable: 'ourvend.groups',
|
|
147
|
+
dateFrom: yesterday,
|
|
148
|
+
dateTo: today
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
// 2. Sync TNS products from new sales
|
|
152
|
+
const tns = await gufi.integrations.tns.sync_products_from_sales({
|
|
153
|
+
productsTable: 'tns.products',
|
|
154
|
+
categoriesTable: 'tns.categories',
|
|
155
|
+
salesTable: 'ourvend.sales'
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
gufi.logger.info('Daily sync complete', {
|
|
159
|
+
sales: sales.synced,
|
|
160
|
+
tnsProducts: tns.synced
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
return {
|
|
164
|
+
sales: sales.synced,
|
|
165
|
+
tns_products: tns.synced
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
## Troubleshooting
|
|
171
|
+
|
|
172
|
+
| Error | Cause | Solution |
|
|
173
|
+
|-------|-------|----------|
|
|
174
|
+
| Product not found | Code doesn't exist in TNS | The product code from OurVend may not be in TNS catalog |
|
|
175
|
+
| Category not created | Missing categoriesTable | Ensure `categoriesTable` parameter is correct |
|
|
176
|
+
| Timeout | sync_all_products too slow | Use `sync_products_from_sales` for incremental sync |
|
|
177
|
+
| Duplicate products | Re-syncing | Products are matched by code - duplicates are updated, not created |
|
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: custom-http
|
|
3
|
+
title: "Custom HTTP Requests"
|
|
4
|
+
description: "Connect to any REST API"
|
|
5
|
+
icon: Zap
|
|
6
|
+
category: dev
|
|
7
|
+
part: 2
|
|
8
|
+
group: integrations
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
# Custom HTTP Requests
|
|
12
|
+
|
|
13
|
+
Connect to any REST API with gufi.http()
|
|
14
|
+
|
|
15
|
+
## Overview
|
|
16
|
+
|
|
17
|
+
For services without built-in integrations, use `gufi.http()` to make HTTP requests to any REST API.
|
|
18
|
+
|
|
19
|
+
## Basic Syntax
|
|
20
|
+
|
|
21
|
+
```javascript
|
|
22
|
+
const response = await gufi.http({
|
|
23
|
+
url: 'https://api.example.com/endpoint',
|
|
24
|
+
method: 'POST',
|
|
25
|
+
headers: {
|
|
26
|
+
'Authorization': 'Bearer ' + env.API_KEY,
|
|
27
|
+
'Content-Type': 'application/json'
|
|
28
|
+
},
|
|
29
|
+
body: {
|
|
30
|
+
key: 'value'
|
|
31
|
+
}
|
|
32
|
+
});
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Parameters
|
|
36
|
+
|
|
37
|
+
| Parameter | Type | Required | Default | Description |
|
|
38
|
+
|-----------|------|----------|---------|-------------|
|
|
39
|
+
| url | string | Yes | - | Full URL to call |
|
|
40
|
+
| method | string | No | GET | HTTP method: GET, POST, PUT, DELETE, PATCH |
|
|
41
|
+
| headers | object | No | {} | HTTP headers |
|
|
42
|
+
| body | object | No | - | Request body (auto JSON stringified) |
|
|
43
|
+
| timeout | number | No | 30000 | Timeout in milliseconds |
|
|
44
|
+
|
|
45
|
+
## GET Request
|
|
46
|
+
|
|
47
|
+
```javascript
|
|
48
|
+
async function fetch_data(gufi) {
|
|
49
|
+
const { env } = gufi.context;
|
|
50
|
+
|
|
51
|
+
const response = await gufi.http({
|
|
52
|
+
url: 'https://api.example.com/users',
|
|
53
|
+
headers: {
|
|
54
|
+
'Authorization': 'Bearer ' + env.API_KEY
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
return { users: response.data };
|
|
59
|
+
}
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## POST Request
|
|
63
|
+
|
|
64
|
+
```javascript
|
|
65
|
+
async function create_record(gufi) {
|
|
66
|
+
const { env, row } = gufi.context;
|
|
67
|
+
|
|
68
|
+
const response = await gufi.http({
|
|
69
|
+
url: 'https://api.example.com/orders',
|
|
70
|
+
method: 'POST',
|
|
71
|
+
headers: {
|
|
72
|
+
'Authorization': 'Bearer ' + env.API_KEY,
|
|
73
|
+
'Content-Type': 'application/json'
|
|
74
|
+
},
|
|
75
|
+
body: {
|
|
76
|
+
customer_id: row.customer_id,
|
|
77
|
+
items: row.items,
|
|
78
|
+
total: row.total
|
|
79
|
+
}
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
return { external_id: response.id };
|
|
83
|
+
}
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
## Webhook Notification
|
|
87
|
+
|
|
88
|
+
Trigger: `on_create` - Notify external system when record created
|
|
89
|
+
|
|
90
|
+
```javascript
|
|
91
|
+
async function notify_webhook(gufi) {
|
|
92
|
+
const { row, env, table } = gufi.context;
|
|
93
|
+
|
|
94
|
+
await gufi.http({
|
|
95
|
+
url: env.WEBHOOK_URL,
|
|
96
|
+
method: 'POST',
|
|
97
|
+
headers: {
|
|
98
|
+
'Content-Type': 'application/json',
|
|
99
|
+
'X-Webhook-Secret': env.WEBHOOK_SECRET
|
|
100
|
+
},
|
|
101
|
+
body: {
|
|
102
|
+
event: 'record.created',
|
|
103
|
+
table: table,
|
|
104
|
+
data: row,
|
|
105
|
+
timestamp: new Date().toISOString()
|
|
106
|
+
}
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
gufi.logger.info('Webhook sent', { table, id: row.id });
|
|
110
|
+
}
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
## Slack Notification
|
|
114
|
+
|
|
115
|
+
```javascript
|
|
116
|
+
async function send_slack_alert(gufi) {
|
|
117
|
+
const { row, env } = gufi.context;
|
|
118
|
+
|
|
119
|
+
await gufi.http({
|
|
120
|
+
url: env.SLACK_WEBHOOK_URL,
|
|
121
|
+
method: 'POST',
|
|
122
|
+
body: {
|
|
123
|
+
text: `New order #${row.id} received!`,
|
|
124
|
+
blocks: [
|
|
125
|
+
{
|
|
126
|
+
type: 'section',
|
|
127
|
+
text: {
|
|
128
|
+
type: 'mrkdwn',
|
|
129
|
+
text: `*New Order*\nOrder: #${row.id}\nCustomer: ${row.customer_name}\nTotal: €${row.total}`
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
]
|
|
133
|
+
}
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
## Error Handling
|
|
139
|
+
|
|
140
|
+
Always wrap HTTP calls in try/catch:
|
|
141
|
+
|
|
142
|
+
```javascript
|
|
143
|
+
async function safe_api_call(gufi) {
|
|
144
|
+
const { env, row } = gufi.context;
|
|
145
|
+
|
|
146
|
+
try {
|
|
147
|
+
const response = await gufi.http({
|
|
148
|
+
url: 'https://api.example.com/process',
|
|
149
|
+
method: 'POST',
|
|
150
|
+
headers: { 'Authorization': 'Bearer ' + env.API_KEY },
|
|
151
|
+
body: { id: row.id }
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
return { success: true, data: response };
|
|
155
|
+
|
|
156
|
+
} catch (error) {
|
|
157
|
+
gufi.logger.error('API call failed', {
|
|
158
|
+
error: error.message,
|
|
159
|
+
rowId: row.id
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
return { success: false, error: error.message };
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
## Retry Logic
|
|
168
|
+
|
|
169
|
+
For unreliable APIs, implement retry:
|
|
170
|
+
|
|
171
|
+
```javascript
|
|
172
|
+
async function api_with_retry(gufi) {
|
|
173
|
+
const { env } = gufi.context;
|
|
174
|
+
const maxRetries = 3;
|
|
175
|
+
|
|
176
|
+
for (let attempt = 1; attempt <= maxRetries; attempt++) {
|
|
177
|
+
try {
|
|
178
|
+
const response = await gufi.http({
|
|
179
|
+
url: 'https://api.example.com/data',
|
|
180
|
+
headers: { 'Authorization': 'Bearer ' + env.API_KEY }
|
|
181
|
+
});
|
|
182
|
+
return response;
|
|
183
|
+
|
|
184
|
+
} catch (error) {
|
|
185
|
+
gufi.logger.warn(`Attempt ${attempt} failed`, { error: error.message });
|
|
186
|
+
|
|
187
|
+
if (attempt === maxRetries) {
|
|
188
|
+
throw error; // Final attempt failed
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// Wait before retry (exponential backoff)
|
|
192
|
+
await new Promise(r => setTimeout(r, 1000 * attempt));
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
## Pagination
|
|
199
|
+
|
|
200
|
+
For APIs with paginated results:
|
|
201
|
+
|
|
202
|
+
```javascript
|
|
203
|
+
async function fetch_all_pages(gufi) {
|
|
204
|
+
const { env } = gufi.context;
|
|
205
|
+
const allData = [];
|
|
206
|
+
let page = 1;
|
|
207
|
+
let hasMore = true;
|
|
208
|
+
|
|
209
|
+
while (hasMore) {
|
|
210
|
+
const response = await gufi.http({
|
|
211
|
+
url: `https://api.example.com/items?page=${page}&limit=100`,
|
|
212
|
+
headers: { 'Authorization': 'Bearer ' + env.API_KEY }
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
allData.push(...response.items);
|
|
216
|
+
hasMore = response.has_more;
|
|
217
|
+
page++;
|
|
218
|
+
|
|
219
|
+
// Safety limit
|
|
220
|
+
if (page > 100) break;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
return { total: allData.length, data: allData };
|
|
224
|
+
}
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
## OAuth Token Refresh
|
|
228
|
+
|
|
229
|
+
```javascript
|
|
230
|
+
async function call_with_oauth(gufi) {
|
|
231
|
+
const { env } = gufi.context;
|
|
232
|
+
|
|
233
|
+
// First, refresh the token
|
|
234
|
+
const tokenResponse = await gufi.http({
|
|
235
|
+
url: 'https://oauth.example.com/token',
|
|
236
|
+
method: 'POST',
|
|
237
|
+
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
|
238
|
+
body: {
|
|
239
|
+
grant_type: 'refresh_token',
|
|
240
|
+
refresh_token: env.OAUTH_REFRESH_TOKEN,
|
|
241
|
+
client_id: env.OAUTH_CLIENT_ID,
|
|
242
|
+
client_secret: env.OAUTH_CLIENT_SECRET
|
|
243
|
+
}
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
// Then use the new token
|
|
247
|
+
const response = await gufi.http({
|
|
248
|
+
url: 'https://api.example.com/data',
|
|
249
|
+
headers: {
|
|
250
|
+
'Authorization': 'Bearer ' + tokenResponse.access_token
|
|
251
|
+
}
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
return response;
|
|
255
|
+
}
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
## Best Practices
|
|
259
|
+
|
|
260
|
+
1. **Store credentials in env** - Never hardcode API keys
|
|
261
|
+
2. **Handle errors** - Always use try/catch
|
|
262
|
+
3. **Log important events** - Use logger for debugging
|
|
263
|
+
4. **Set timeouts** - Don't wait forever for slow APIs
|
|
264
|
+
5. **Validate responses** - Check response structure before using
|
|
265
|
+
|
|
266
|
+
:::warning
|
|
267
|
+
Never log sensitive data like API keys or passwords. Only log non-sensitive identifiers.
|
|
268
|
+
:::
|