@withone/cli 1.17.2 → 1.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
|
@@ -225,6 +225,30 @@ one actions execute stripe <actionId> <connectionKey> \
|
|
|
225
225
|
| `--form-url-encoded` | Send as application/x-www-form-urlencoded |
|
|
226
226
|
| `--dry-run` | Show the request without executing it |
|
|
227
227
|
|
|
228
|
+
### `one cache`
|
|
229
|
+
|
|
230
|
+
Manage the local cache for knowledge and search responses. The CLI automatically caches `actions knowledge` and `actions search` results so repeated calls serve instantly from disk.
|
|
231
|
+
|
|
232
|
+
```bash
|
|
233
|
+
one cache list # List all cached entries with age and status
|
|
234
|
+
one cache list --expired # Show only expired entries
|
|
235
|
+
one cache clear # Clear all cached data
|
|
236
|
+
one cache clear <actionId> # Clear a specific entry
|
|
237
|
+
one cache update-all # Re-fetch fresh data for all cached entries
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
Knowledge and search commands also support cache flags:
|
|
241
|
+
|
|
242
|
+
```bash
|
|
243
|
+
one actions knowledge gmail <actionId> --no-cache # Skip cache, fetch fresh
|
|
244
|
+
one actions knowledge gmail <actionId> --cache-status # Check cache status
|
|
245
|
+
one actions search gmail "send email" --no-cache # Skip cache for search
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
Default TTL is 1 hour. Configure via `ONE_CACHE_TTL` environment variable or `cacheTtl` in `~/.one/config.json`.
|
|
249
|
+
|
|
250
|
+
Note: `actions execute` is never cached — it always hits the API fresh.
|
|
251
|
+
|
|
228
252
|
### `one guide [topic]`
|
|
229
253
|
|
|
230
254
|
Get the full CLI usage guide, designed for AI agents that only have the binary (no MCP, no IDE skills).
|
|
@@ -239,7 +263,7 @@ one --agent guide # full guide as structured JSON
|
|
|
239
263
|
one --agent guide flows # single topic as JSON
|
|
240
264
|
```
|
|
241
265
|
|
|
242
|
-
Topics: `overview`, `actions`, `flows`, `all` (default).
|
|
266
|
+
Topics: `overview`, `actions`, `flows`, `relay`, `cache`, `all` (default).
|
|
243
267
|
|
|
244
268
|
In agent mode (`--agent`), the JSON response includes the guide content and an `availableTopics` array so agents can discover what sections exist.
|
|
245
269
|
|
|
@@ -128,6 +128,81 @@ var OneApi = class {
|
|
|
128
128
|
method: action.method
|
|
129
129
|
};
|
|
130
130
|
}
|
|
131
|
+
async requestWithMeta(opts) {
|
|
132
|
+
let url = `${API_BASE}${opts.path}`;
|
|
133
|
+
if (opts.queryParams && Object.keys(opts.queryParams).length > 0) {
|
|
134
|
+
const params = new URLSearchParams(opts.queryParams);
|
|
135
|
+
url += `?${params.toString()}`;
|
|
136
|
+
}
|
|
137
|
+
const headers = {
|
|
138
|
+
"x-one-secret": this.apiKey,
|
|
139
|
+
"Content-Type": "application/json",
|
|
140
|
+
...opts.headers
|
|
141
|
+
};
|
|
142
|
+
if (opts.ifNoneMatch) {
|
|
143
|
+
headers["If-None-Match"] = opts.ifNoneMatch;
|
|
144
|
+
}
|
|
145
|
+
const fetchOpts = {
|
|
146
|
+
method: opts.method || "GET",
|
|
147
|
+
headers
|
|
148
|
+
};
|
|
149
|
+
if (opts.body !== void 0) {
|
|
150
|
+
fetchOpts.body = JSON.stringify(opts.body);
|
|
151
|
+
}
|
|
152
|
+
const response = await fetch(url, fetchOpts);
|
|
153
|
+
if (response.status === 304) {
|
|
154
|
+
return { data: null, etag: opts.ifNoneMatch ?? null, status: 304 };
|
|
155
|
+
}
|
|
156
|
+
if (!response.ok) {
|
|
157
|
+
const text2 = await response.text();
|
|
158
|
+
throw new ApiError(response.status, text2 || `HTTP ${response.status}`);
|
|
159
|
+
}
|
|
160
|
+
const etag = response.headers.get("etag") ?? null;
|
|
161
|
+
const text = await response.text();
|
|
162
|
+
const data = text ? JSON.parse(text) : {};
|
|
163
|
+
return { data, etag, status: response.status };
|
|
164
|
+
}
|
|
165
|
+
async getActionKnowledgeWithMeta(actionId, ifNoneMatch) {
|
|
166
|
+
const result = await this.requestWithMeta({
|
|
167
|
+
path: "/knowledge",
|
|
168
|
+
queryParams: { _id: actionId },
|
|
169
|
+
ifNoneMatch
|
|
170
|
+
});
|
|
171
|
+
if (result.status === 304) {
|
|
172
|
+
return { data: null, etag: result.etag, status: 304 };
|
|
173
|
+
}
|
|
174
|
+
const actions = result.data?.rows || [];
|
|
175
|
+
if (actions.length === 0) {
|
|
176
|
+
throw new ApiError(404, `Action with ID ${actionId} not found`);
|
|
177
|
+
}
|
|
178
|
+
const action = actions[0];
|
|
179
|
+
const knowledge = {
|
|
180
|
+
knowledge: action.knowledge || "No knowledge was found",
|
|
181
|
+
method: action.method || "No method was found"
|
|
182
|
+
};
|
|
183
|
+
return { data: knowledge, etag: result.etag, status: result.status };
|
|
184
|
+
}
|
|
185
|
+
async searchActionsWithMeta(platform, query, agentType, ifNoneMatch) {
|
|
186
|
+
const isKnowledgeAgent = !agentType || agentType === "knowledge";
|
|
187
|
+
const queryParams = {
|
|
188
|
+
query,
|
|
189
|
+
limit: "5"
|
|
190
|
+
};
|
|
191
|
+
if (isKnowledgeAgent) {
|
|
192
|
+
queryParams.knowledgeAgent = "true";
|
|
193
|
+
} else {
|
|
194
|
+
queryParams.executeAgent = "true";
|
|
195
|
+
}
|
|
196
|
+
const result = await this.requestWithMeta({
|
|
197
|
+
path: `/available-actions/search/${platform}`,
|
|
198
|
+
queryParams,
|
|
199
|
+
ifNoneMatch
|
|
200
|
+
});
|
|
201
|
+
if (result.status === 304) {
|
|
202
|
+
return { data: null, etag: result.etag, status: 304 };
|
|
203
|
+
}
|
|
204
|
+
return { data: result.data || [], etag: result.etag, status: result.status };
|
|
205
|
+
}
|
|
131
206
|
async executePassthroughRequest(args, preloadedAction) {
|
|
132
207
|
const action = preloadedAction ?? await this.getActionDetails(args.actionId);
|
|
133
208
|
const method = action.method;
|
|
@@ -703,7 +778,7 @@ async function executeSubflowStep(step, context, api, permissions, allowedAction
|
|
|
703
778
|
if (flowStack.includes(resolvedKey)) {
|
|
704
779
|
throw new Error(`Circular flow detected: ${[...flowStack, resolvedKey].join(" \u2192 ")}`);
|
|
705
780
|
}
|
|
706
|
-
const { loadFlow: loadFlow2 } = await import("./flow-runner-
|
|
781
|
+
const { loadFlow: loadFlow2 } = await import("./flow-runner-SU4JHSZW.js");
|
|
707
782
|
const subFlow = loadFlow2(resolvedKey);
|
|
708
783
|
const subContext = await executeFlow(
|
|
709
784
|
subFlow,
|
package/dist/index.js
CHANGED
|
@@ -11,7 +11,7 @@ import {
|
|
|
11
11
|
loadFlow,
|
|
12
12
|
resolveFlowPath,
|
|
13
13
|
saveFlow
|
|
14
|
-
} from "./chunk-
|
|
14
|
+
} from "./chunk-CTL2YHUH.js";
|
|
15
15
|
|
|
16
16
|
// src/index.ts
|
|
17
17
|
import { createRequire as createRequire2 } from "module";
|
|
@@ -101,6 +101,15 @@ function getAccessControlFromAllSources() {
|
|
|
101
101
|
function getAccessControl() {
|
|
102
102
|
return readConfig()?.accessControl ?? {};
|
|
103
103
|
}
|
|
104
|
+
function getCacheTtl() {
|
|
105
|
+
if (process.env.ONE_CACHE_TTL) {
|
|
106
|
+
const val = parseInt(process.env.ONE_CACHE_TTL, 10);
|
|
107
|
+
if (!isNaN(val) && val > 0) return val;
|
|
108
|
+
}
|
|
109
|
+
const config = readConfig();
|
|
110
|
+
if (config?.cacheTtl && config.cacheTtl > 0) return config.cacheTtl;
|
|
111
|
+
return 3600;
|
|
112
|
+
}
|
|
104
113
|
function updateAccessControl(settings) {
|
|
105
114
|
const config = readConfig();
|
|
106
115
|
if (!config) return;
|
|
@@ -1350,6 +1359,123 @@ async function platformsCommand(options) {
|
|
|
1350
1359
|
// src/commands/actions.ts
|
|
1351
1360
|
import * as p6 from "@clack/prompts";
|
|
1352
1361
|
import pc6 from "picocolors";
|
|
1362
|
+
|
|
1363
|
+
// src/lib/cache.ts
|
|
1364
|
+
import fs4 from "fs";
|
|
1365
|
+
import path4 from "path";
|
|
1366
|
+
import os4 from "os";
|
|
1367
|
+
var CACHE_BASE = path4.join(os4.homedir(), ".one", "cache");
|
|
1368
|
+
var KNOWLEDGE_DIR = path4.join(CACHE_BASE, "knowledge");
|
|
1369
|
+
var SEARCH_DIR = path4.join(CACHE_BASE, "search");
|
|
1370
|
+
function sanitizeFilename(input) {
|
|
1371
|
+
return input.replace(/[^a-zA-Z0-9_\-\.]/g, "_");
|
|
1372
|
+
}
|
|
1373
|
+
function knowledgeCachePath(actionId) {
|
|
1374
|
+
return path4.join(KNOWLEDGE_DIR, `${sanitizeFilename(actionId)}.json`);
|
|
1375
|
+
}
|
|
1376
|
+
function searchCachePath(platform, query, type) {
|
|
1377
|
+
const key = `${platform}_${sanitizeFilename(query)}_${type || "knowledge"}`;
|
|
1378
|
+
return path4.join(SEARCH_DIR, `${key}.json`);
|
|
1379
|
+
}
|
|
1380
|
+
function readCache(filePath) {
|
|
1381
|
+
try {
|
|
1382
|
+
const content = fs4.readFileSync(filePath, "utf-8");
|
|
1383
|
+
return JSON.parse(content);
|
|
1384
|
+
} catch {
|
|
1385
|
+
return null;
|
|
1386
|
+
}
|
|
1387
|
+
}
|
|
1388
|
+
function writeCache(filePath, entry) {
|
|
1389
|
+
try {
|
|
1390
|
+
const dir = path4.dirname(filePath);
|
|
1391
|
+
fs4.mkdirSync(dir, { recursive: true });
|
|
1392
|
+
fs4.writeFileSync(filePath, JSON.stringify(entry, null, 2));
|
|
1393
|
+
} catch {
|
|
1394
|
+
}
|
|
1395
|
+
}
|
|
1396
|
+
function isFresh(entry) {
|
|
1397
|
+
const cachedTime = new Date(entry.cachedAt).getTime();
|
|
1398
|
+
const now = Date.now();
|
|
1399
|
+
return now - cachedTime < entry.ttl * 1e3;
|
|
1400
|
+
}
|
|
1401
|
+
function getAge(entry) {
|
|
1402
|
+
return Math.floor((Date.now() - new Date(entry.cachedAt).getTime()) / 1e3);
|
|
1403
|
+
}
|
|
1404
|
+
function buildCacheMeta(entry, hit) {
|
|
1405
|
+
if (!entry) {
|
|
1406
|
+
return { hit: false, age: 0, fresh: false };
|
|
1407
|
+
}
|
|
1408
|
+
return {
|
|
1409
|
+
hit,
|
|
1410
|
+
age: getAge(entry),
|
|
1411
|
+
fresh: isFresh(entry)
|
|
1412
|
+
};
|
|
1413
|
+
}
|
|
1414
|
+
function formatAge(seconds) {
|
|
1415
|
+
if (seconds < 60) return `${seconds}s`;
|
|
1416
|
+
if (seconds < 3600) return `${Math.floor(seconds / 60)}m`;
|
|
1417
|
+
if (seconds < 86400) {
|
|
1418
|
+
const h2 = Math.floor(seconds / 3600);
|
|
1419
|
+
const m = Math.floor(seconds % 3600 / 60);
|
|
1420
|
+
return m > 0 ? `${h2}h ${m}m` : `${h2}h`;
|
|
1421
|
+
}
|
|
1422
|
+
const d = Math.floor(seconds / 86400);
|
|
1423
|
+
const h = Math.floor(seconds % 86400 / 3600);
|
|
1424
|
+
return h > 0 ? `${d}d ${h}h` : `${d}d`;
|
|
1425
|
+
}
|
|
1426
|
+
function listCacheEntries() {
|
|
1427
|
+
const entries = [];
|
|
1428
|
+
for (const [dir, type] of [[KNOWLEDGE_DIR, "knowledge"], [SEARCH_DIR, "search"]]) {
|
|
1429
|
+
try {
|
|
1430
|
+
const files = fs4.readdirSync(dir);
|
|
1431
|
+
for (const file of files) {
|
|
1432
|
+
if (!file.endsWith(".json")) continue;
|
|
1433
|
+
const filePath = path4.join(dir, file);
|
|
1434
|
+
const entry = readCache(filePath);
|
|
1435
|
+
if (entry) {
|
|
1436
|
+
entries.push({ type, filePath, entry });
|
|
1437
|
+
}
|
|
1438
|
+
}
|
|
1439
|
+
} catch {
|
|
1440
|
+
}
|
|
1441
|
+
}
|
|
1442
|
+
return entries;
|
|
1443
|
+
}
|
|
1444
|
+
function clearAll() {
|
|
1445
|
+
let count = 0;
|
|
1446
|
+
for (const dir of [KNOWLEDGE_DIR, SEARCH_DIR]) {
|
|
1447
|
+
try {
|
|
1448
|
+
const files = fs4.readdirSync(dir);
|
|
1449
|
+
for (const file of files) {
|
|
1450
|
+
fs4.unlinkSync(path4.join(dir, file));
|
|
1451
|
+
count++;
|
|
1452
|
+
}
|
|
1453
|
+
fs4.rmdirSync(dir);
|
|
1454
|
+
} catch {
|
|
1455
|
+
}
|
|
1456
|
+
}
|
|
1457
|
+
return count;
|
|
1458
|
+
}
|
|
1459
|
+
function clearEntry(actionId) {
|
|
1460
|
+
const filePath = knowledgeCachePath(actionId);
|
|
1461
|
+
try {
|
|
1462
|
+
fs4.unlinkSync(filePath);
|
|
1463
|
+
return true;
|
|
1464
|
+
} catch {
|
|
1465
|
+
return false;
|
|
1466
|
+
}
|
|
1467
|
+
}
|
|
1468
|
+
function makeCacheEntry(key, data, etag) {
|
|
1469
|
+
return {
|
|
1470
|
+
key,
|
|
1471
|
+
etag,
|
|
1472
|
+
cachedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1473
|
+
ttl: getCacheTtl(),
|
|
1474
|
+
data
|
|
1475
|
+
};
|
|
1476
|
+
}
|
|
1477
|
+
|
|
1478
|
+
// src/commands/actions.ts
|
|
1353
1479
|
function getConfig() {
|
|
1354
1480
|
const apiKey = getApiKey();
|
|
1355
1481
|
if (!apiKey) {
|
|
@@ -1377,17 +1503,65 @@ async function actionsSearchCommand(platform, query, options) {
|
|
|
1377
1503
|
spinner5.start(`Searching actions on ${pc6.cyan(platform)} for "${query}"...`);
|
|
1378
1504
|
try {
|
|
1379
1505
|
const agentType = knowledgeAgent ? "knowledge" : options.type;
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
}
|
|
1506
|
+
const useCache = options.cache !== false;
|
|
1507
|
+
const cachePath = searchCachePath(platform, query, agentType || "knowledge");
|
|
1508
|
+
const cached = useCache ? readCache(cachePath) : null;
|
|
1509
|
+
let cleanedActions;
|
|
1510
|
+
let cacheHit = false;
|
|
1511
|
+
if (cached && isFresh(cached)) {
|
|
1512
|
+
cleanedActions = cached.data.actions;
|
|
1513
|
+
cacheHit = true;
|
|
1514
|
+
} else {
|
|
1515
|
+
try {
|
|
1516
|
+
const result = await api.searchActionsWithMeta(
|
|
1517
|
+
platform,
|
|
1518
|
+
query,
|
|
1519
|
+
agentType,
|
|
1520
|
+
cached?.etag ?? void 0
|
|
1521
|
+
);
|
|
1522
|
+
if (result.status === 304 && cached) {
|
|
1523
|
+
cached.cachedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
1524
|
+
writeCache(cachePath, cached);
|
|
1525
|
+
cleanedActions = cached.data.actions;
|
|
1526
|
+
cacheHit = true;
|
|
1527
|
+
} else {
|
|
1528
|
+
let actions2 = result.data;
|
|
1529
|
+
actions2 = filterByPermissions(actions2, permissions);
|
|
1530
|
+
actions2 = actions2.filter((a) => isActionAllowed(a.systemId, actionIds));
|
|
1531
|
+
cleanedActions = actions2.map((action) => ({
|
|
1532
|
+
actionId: action.systemId,
|
|
1533
|
+
title: action.title,
|
|
1534
|
+
method: action.method,
|
|
1535
|
+
path: action.path
|
|
1536
|
+
}));
|
|
1537
|
+
writeCache(cachePath, makeCacheEntry(
|
|
1538
|
+
`${platform}_${query}_${agentType || "knowledge"}`,
|
|
1539
|
+
{ actions: cleanedActions },
|
|
1540
|
+
result.etag
|
|
1541
|
+
));
|
|
1542
|
+
}
|
|
1543
|
+
} catch (fetchError) {
|
|
1544
|
+
if (cached) {
|
|
1545
|
+
process.stderr.write(
|
|
1546
|
+
`Warning: serving cached search results (network unavailable, cached ${formatAge(getAge(cached))} ago)
|
|
1547
|
+
`
|
|
1548
|
+
);
|
|
1549
|
+
cleanedActions = cached.data.actions;
|
|
1550
|
+
cacheHit = true;
|
|
1551
|
+
} else {
|
|
1552
|
+
throw fetchError;
|
|
1553
|
+
}
|
|
1554
|
+
}
|
|
1555
|
+
}
|
|
1389
1556
|
if (isAgentMode()) {
|
|
1390
|
-
|
|
1557
|
+
const response = { actions: cleanedActions };
|
|
1558
|
+
if (cacheHit && cached) {
|
|
1559
|
+
response._cache = buildCacheMeta(cached, true);
|
|
1560
|
+
} else {
|
|
1561
|
+
const freshEntry = readCache(cachePath);
|
|
1562
|
+
response._cache = buildCacheMeta(freshEntry, false);
|
|
1563
|
+
}
|
|
1564
|
+
json(response);
|
|
1391
1565
|
return;
|
|
1392
1566
|
}
|
|
1393
1567
|
if (cleanedActions.length === 0) {
|
|
@@ -1441,7 +1615,29 @@ Execute: ${pc6.cyan(`one actions execute ${platform} <actionId> <connectionK
|
|
|
1441
1615
|
);
|
|
1442
1616
|
}
|
|
1443
1617
|
}
|
|
1444
|
-
async function actionsKnowledgeCommand(platform, actionId) {
|
|
1618
|
+
async function actionsKnowledgeCommand(platform, actionId, options) {
|
|
1619
|
+
const cachePath = knowledgeCachePath(actionId);
|
|
1620
|
+
if (options.cacheStatus) {
|
|
1621
|
+
const entry = readCache(cachePath);
|
|
1622
|
+
if (!entry) {
|
|
1623
|
+
json({
|
|
1624
|
+
cached: false,
|
|
1625
|
+
path: cachePath
|
|
1626
|
+
});
|
|
1627
|
+
} else {
|
|
1628
|
+
const age = getAge(entry);
|
|
1629
|
+
json({
|
|
1630
|
+
cached: true,
|
|
1631
|
+
cachedAt: entry.cachedAt,
|
|
1632
|
+
age: formatAge(age),
|
|
1633
|
+
ttl: entry.ttl,
|
|
1634
|
+
expired: !isFresh(entry),
|
|
1635
|
+
etag: entry.etag,
|
|
1636
|
+
path: cachePath
|
|
1637
|
+
});
|
|
1638
|
+
}
|
|
1639
|
+
return;
|
|
1640
|
+
}
|
|
1445
1641
|
intro2(pc6.bgCyan(pc6.black(" One ")));
|
|
1446
1642
|
const { apiKey, actionIds, connectionKeys } = getConfig();
|
|
1447
1643
|
const api = new OneApi(apiKey);
|
|
@@ -1469,15 +1665,57 @@ async function actionsKnowledgeCommand(platform, actionId) {
|
|
|
1469
1665
|
const spinner5 = createSpinner();
|
|
1470
1666
|
spinner5.start(`Loading knowledge for action ${pc6.dim(actionId)}...`);
|
|
1471
1667
|
try {
|
|
1472
|
-
const
|
|
1668
|
+
const useCache = options.cache !== false;
|
|
1669
|
+
const cached = useCache ? readCache(cachePath) : null;
|
|
1670
|
+
let knowledgeData;
|
|
1671
|
+
let cacheHit = false;
|
|
1672
|
+
let cacheEntry = cached;
|
|
1673
|
+
if (cached && isFresh(cached) && useCache) {
|
|
1674
|
+
knowledgeData = cached.data;
|
|
1675
|
+
cacheHit = true;
|
|
1676
|
+
} else {
|
|
1677
|
+
try {
|
|
1678
|
+
const result = await api.getActionKnowledgeWithMeta(
|
|
1679
|
+
actionId,
|
|
1680
|
+
cached?.etag ?? void 0
|
|
1681
|
+
);
|
|
1682
|
+
if (result.status === 304 && cached) {
|
|
1683
|
+
cached.cachedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
1684
|
+
writeCache(cachePath, cached);
|
|
1685
|
+
knowledgeData = cached.data;
|
|
1686
|
+
cacheHit = true;
|
|
1687
|
+
} else {
|
|
1688
|
+
knowledgeData = result.data;
|
|
1689
|
+
const newEntry = makeCacheEntry(actionId, knowledgeData, result.etag);
|
|
1690
|
+
writeCache(cachePath, newEntry);
|
|
1691
|
+
cacheEntry = newEntry;
|
|
1692
|
+
}
|
|
1693
|
+
} catch (fetchError) {
|
|
1694
|
+
if (cached) {
|
|
1695
|
+
process.stderr.write(
|
|
1696
|
+
`Warning: serving cached knowledge (network unavailable, cached ${formatAge(getAge(cached))} ago)
|
|
1697
|
+
`
|
|
1698
|
+
);
|
|
1699
|
+
knowledgeData = cached.data;
|
|
1700
|
+
cacheHit = true;
|
|
1701
|
+
} else {
|
|
1702
|
+
throw fetchError;
|
|
1703
|
+
}
|
|
1704
|
+
}
|
|
1705
|
+
}
|
|
1473
1706
|
const knowledgeWithGuidance = buildActionKnowledgeWithGuidance(
|
|
1474
|
-
knowledge,
|
|
1475
|
-
method,
|
|
1707
|
+
knowledgeData.knowledge,
|
|
1708
|
+
knowledgeData.method,
|
|
1476
1709
|
platform,
|
|
1477
1710
|
actionId
|
|
1478
1711
|
);
|
|
1479
1712
|
if (isAgentMode()) {
|
|
1480
|
-
|
|
1713
|
+
const response = {
|
|
1714
|
+
knowledge: knowledgeWithGuidance,
|
|
1715
|
+
method: knowledgeData.method,
|
|
1716
|
+
_cache: buildCacheMeta(cacheEntry, cacheHit)
|
|
1717
|
+
};
|
|
1718
|
+
json(response);
|
|
1481
1719
|
return;
|
|
1482
1720
|
}
|
|
1483
1721
|
spinner5.stop("Knowledge loaded");
|
|
@@ -2197,26 +2435,26 @@ function validateStepsArray(steps, pathPrefix, errors) {
|
|
|
2197
2435
|
const validTypes = FLOW_SCHEMA.stepTypes.map((st) => st.type);
|
|
2198
2436
|
for (let i = 0; i < steps.length; i++) {
|
|
2199
2437
|
const step = steps[i];
|
|
2200
|
-
const
|
|
2438
|
+
const path5 = `${pathPrefix}[${i}]`;
|
|
2201
2439
|
if (!step || typeof step !== "object" || Array.isArray(step)) {
|
|
2202
|
-
errors.push({ path:
|
|
2440
|
+
errors.push({ path: path5, message: "Step must be an object" });
|
|
2203
2441
|
continue;
|
|
2204
2442
|
}
|
|
2205
2443
|
const s = step;
|
|
2206
2444
|
if (!s.id || typeof s.id !== "string") {
|
|
2207
|
-
errors.push({ path: `${
|
|
2445
|
+
errors.push({ path: `${path5}.id`, message: 'Step must have a string "id"' });
|
|
2208
2446
|
}
|
|
2209
2447
|
if (!s.name || typeof s.name !== "string") {
|
|
2210
|
-
errors.push({ path: `${
|
|
2448
|
+
errors.push({ path: `${path5}.name`, message: 'Step must have a string "name"' });
|
|
2211
2449
|
}
|
|
2212
2450
|
if (!s.type || !validTypes.includes(s.type)) {
|
|
2213
|
-
errors.push({ path: `${
|
|
2451
|
+
errors.push({ path: `${path5}.type`, message: `Step type must be one of: ${validTypes.join(", ")}` });
|
|
2214
2452
|
continue;
|
|
2215
2453
|
}
|
|
2216
2454
|
if (s.onError && typeof s.onError === "object") {
|
|
2217
2455
|
const oe = s.onError;
|
|
2218
2456
|
if (!FLOW_SCHEMA.errorStrategies.includes(oe.strategy)) {
|
|
2219
|
-
errors.push({ path: `${
|
|
2457
|
+
errors.push({ path: `${path5}.onError.strategy`, message: `Error strategy must be one of: ${FLOW_SCHEMA.errorStrategies.join(", ")}` });
|
|
2220
2458
|
}
|
|
2221
2459
|
}
|
|
2222
2460
|
const descriptor = getStepTypeDescriptor(s.type);
|
|
@@ -2226,14 +2464,14 @@ function validateStepsArray(steps, pathPrefix, errors) {
|
|
|
2226
2464
|
if (!configObj || typeof configObj !== "object") {
|
|
2227
2465
|
const hint = detectFlatConfigHint(s, descriptor);
|
|
2228
2466
|
errors.push({
|
|
2229
|
-
path: `${
|
|
2467
|
+
path: `${path5}.${configKey}`,
|
|
2230
2468
|
message: `${capitalize(descriptor.type)} step must have a "${configKey}" config object${hint}`
|
|
2231
2469
|
});
|
|
2232
2470
|
continue;
|
|
2233
2471
|
}
|
|
2234
2472
|
const config = configObj;
|
|
2235
2473
|
for (const [fieldName, fd] of Object.entries(descriptor.fields)) {
|
|
2236
|
-
const fieldPath = `${
|
|
2474
|
+
const fieldPath = `${path5}.${configKey}.${fieldName}`;
|
|
2237
2475
|
const value = config[fieldName];
|
|
2238
2476
|
if (fd.required && (value === void 0 || value === null || value === "")) {
|
|
2239
2477
|
errors.push({ path: fieldPath, message: `${capitalize(descriptor.type)} must have ${fd.type === "string" ? "a string" : fd.type === "array" ? "a" : "a"} "${fieldName}"` });
|
|
@@ -2285,16 +2523,16 @@ function validateStepIds(flow2) {
|
|
|
2285
2523
|
function collectIds(steps, pathPrefix) {
|
|
2286
2524
|
for (let i = 0; i < steps.length; i++) {
|
|
2287
2525
|
const step = steps[i];
|
|
2288
|
-
const
|
|
2526
|
+
const path5 = `${pathPrefix}[${i}]`;
|
|
2289
2527
|
if (seen.has(step.id)) {
|
|
2290
|
-
errors.push({ path: `${
|
|
2528
|
+
errors.push({ path: `${path5}.id`, message: `Duplicate step ID: "${step.id}"` });
|
|
2291
2529
|
} else {
|
|
2292
2530
|
seen.add(step.id);
|
|
2293
2531
|
}
|
|
2294
2532
|
for (const { configKey, fieldName } of nestedKeys) {
|
|
2295
2533
|
const config = step[configKey];
|
|
2296
2534
|
if (config && Array.isArray(config[fieldName])) {
|
|
2297
|
-
collectIds(config[fieldName], `${
|
|
2535
|
+
collectIds(config[fieldName], `${path5}.${configKey}.${fieldName}`);
|
|
2298
2536
|
}
|
|
2299
2537
|
}
|
|
2300
2538
|
}
|
|
@@ -2341,7 +2579,7 @@ function validateSelectorReferences(flow2) {
|
|
|
2341
2579
|
}
|
|
2342
2580
|
return selectors;
|
|
2343
2581
|
}
|
|
2344
|
-
function checkSelectors(selectors,
|
|
2582
|
+
function checkSelectors(selectors, path5) {
|
|
2345
2583
|
for (const selector of selectors) {
|
|
2346
2584
|
const parts = selector.split(".");
|
|
2347
2585
|
if (parts.length < 3) continue;
|
|
@@ -2349,12 +2587,12 @@ function validateSelectorReferences(flow2) {
|
|
|
2349
2587
|
if (root === "input") {
|
|
2350
2588
|
const inputName = parts[2];
|
|
2351
2589
|
if (!inputNames.has(inputName)) {
|
|
2352
|
-
errors.push({ path:
|
|
2590
|
+
errors.push({ path: path5, message: `Selector "${selector}" references undefined input "${inputName}"` });
|
|
2353
2591
|
}
|
|
2354
2592
|
} else if (root === "steps") {
|
|
2355
2593
|
const stepId = parts[2];
|
|
2356
2594
|
if (!allStepIds.has(stepId)) {
|
|
2357
|
-
errors.push({ path:
|
|
2595
|
+
errors.push({ path: path5, message: `Selector "${selector}" references undefined step "${stepId}"` });
|
|
2358
2596
|
}
|
|
2359
2597
|
}
|
|
2360
2598
|
}
|
|
@@ -2402,7 +2640,7 @@ function validateFlow(flow2) {
|
|
|
2402
2640
|
}
|
|
2403
2641
|
|
|
2404
2642
|
// src/commands/flow.ts
|
|
2405
|
-
import
|
|
2643
|
+
import fs5 from "fs";
|
|
2406
2644
|
function getConfig2() {
|
|
2407
2645
|
const apiKey = getApiKey();
|
|
2408
2646
|
if (!apiKey) {
|
|
@@ -2460,7 +2698,7 @@ async function flowCreateCommand(key, options) {
|
|
|
2460
2698
|
if (raw.startsWith("@")) {
|
|
2461
2699
|
const filePath = raw.slice(1);
|
|
2462
2700
|
try {
|
|
2463
|
-
raw =
|
|
2701
|
+
raw = fs5.readFileSync(filePath, "utf-8");
|
|
2464
2702
|
} catch (err) {
|
|
2465
2703
|
error(`Cannot read file "${filePath}": ${err.message}`);
|
|
2466
2704
|
}
|
|
@@ -2643,7 +2881,7 @@ async function flowValidateCommand(keyOrPath) {
|
|
|
2643
2881
|
let flowData;
|
|
2644
2882
|
try {
|
|
2645
2883
|
const flowPath = resolveFlowPath(keyOrPath);
|
|
2646
|
-
const content =
|
|
2884
|
+
const content = fs5.readFileSync(flowPath, "utf-8");
|
|
2647
2885
|
flowData = JSON.parse(content);
|
|
2648
2886
|
} catch (err) {
|
|
2649
2887
|
spinner5.stop("Validation failed");
|
|
@@ -3312,8 +3550,122 @@ async function relayEventTypesCommand(platform) {
|
|
|
3312
3550
|
}
|
|
3313
3551
|
}
|
|
3314
3552
|
|
|
3315
|
-
// src/commands/
|
|
3553
|
+
// src/commands/cache.ts
|
|
3316
3554
|
import pc9 from "picocolors";
|
|
3555
|
+
async function cacheClearCommand(actionId) {
|
|
3556
|
+
if (actionId) {
|
|
3557
|
+
const deleted = clearEntry(actionId);
|
|
3558
|
+
if (isAgentMode()) {
|
|
3559
|
+
json({ cleared: deleted, actionId });
|
|
3560
|
+
return;
|
|
3561
|
+
}
|
|
3562
|
+
if (deleted) {
|
|
3563
|
+
console.log(`Cleared cache for ${pc9.cyan(actionId)}`);
|
|
3564
|
+
} else {
|
|
3565
|
+
console.log(`No cache entry found for ${pc9.dim(actionId)}`);
|
|
3566
|
+
}
|
|
3567
|
+
} else {
|
|
3568
|
+
const count = clearAll();
|
|
3569
|
+
if (isAgentMode()) {
|
|
3570
|
+
json({ cleared: true, count });
|
|
3571
|
+
return;
|
|
3572
|
+
}
|
|
3573
|
+
console.log(`Cleared ${count} cached ${count === 1 ? "entry" : "entries"}`);
|
|
3574
|
+
}
|
|
3575
|
+
}
|
|
3576
|
+
async function cacheListCommand(options) {
|
|
3577
|
+
const entries = listCacheEntries();
|
|
3578
|
+
const filtered = options.expired ? entries.filter((e) => !isFresh(e.entry)) : entries;
|
|
3579
|
+
if (isAgentMode()) {
|
|
3580
|
+
json({
|
|
3581
|
+
entries: filtered.map((e) => ({
|
|
3582
|
+
type: e.type,
|
|
3583
|
+
key: e.entry.key,
|
|
3584
|
+
cachedAt: e.entry.cachedAt,
|
|
3585
|
+
age: formatAge(getAge(e.entry)),
|
|
3586
|
+
ttl: e.entry.ttl,
|
|
3587
|
+
fresh: isFresh(e.entry),
|
|
3588
|
+
etag: e.entry.etag,
|
|
3589
|
+
path: e.filePath
|
|
3590
|
+
}))
|
|
3591
|
+
});
|
|
3592
|
+
return;
|
|
3593
|
+
}
|
|
3594
|
+
if (filtered.length === 0) {
|
|
3595
|
+
console.log(options.expired ? "No expired cache entries" : "No cached entries");
|
|
3596
|
+
return;
|
|
3597
|
+
}
|
|
3598
|
+
const rows = filtered.map((e) => ({
|
|
3599
|
+
type: e.type,
|
|
3600
|
+
key: e.entry.key,
|
|
3601
|
+
age: formatAge(getAge(e.entry)),
|
|
3602
|
+
status: isFresh(e.entry) ? pc9.green("fresh") : pc9.yellow("expired")
|
|
3603
|
+
}));
|
|
3604
|
+
printTable(
|
|
3605
|
+
[
|
|
3606
|
+
{ key: "type", label: "Type" },
|
|
3607
|
+
{ key: "key", label: "Key" },
|
|
3608
|
+
{ key: "age", label: "Age" },
|
|
3609
|
+
{ key: "status", label: "Status" }
|
|
3610
|
+
],
|
|
3611
|
+
rows
|
|
3612
|
+
);
|
|
3613
|
+
}
|
|
3614
|
+
async function cacheUpdateAllCommand() {
|
|
3615
|
+
const apiKey = getApiKey();
|
|
3616
|
+
if (!apiKey) {
|
|
3617
|
+
error("Not configured. Run `one init` first.");
|
|
3618
|
+
}
|
|
3619
|
+
const api = new OneApi(apiKey);
|
|
3620
|
+
const entries = listCacheEntries();
|
|
3621
|
+
if (entries.length === 0) {
|
|
3622
|
+
if (isAgentMode()) {
|
|
3623
|
+
json({ updated: 0, failed: 0, entries: [] });
|
|
3624
|
+
return;
|
|
3625
|
+
}
|
|
3626
|
+
console.log("No cached entries to update");
|
|
3627
|
+
return;
|
|
3628
|
+
}
|
|
3629
|
+
const spinner5 = createSpinner();
|
|
3630
|
+
spinner5.start(`Updating ${entries.length} cached ${entries.length === 1 ? "entry" : "entries"}...`);
|
|
3631
|
+
let updated = 0;
|
|
3632
|
+
let failed = 0;
|
|
3633
|
+
const errors = [];
|
|
3634
|
+
for (const e of entries) {
|
|
3635
|
+
try {
|
|
3636
|
+
if (e.type === "knowledge") {
|
|
3637
|
+
const result = await api.getActionKnowledgeWithMeta(e.entry.key);
|
|
3638
|
+
const newEntry = makeCacheEntry(e.entry.key, result.data, result.etag);
|
|
3639
|
+
writeCache(e.filePath, newEntry);
|
|
3640
|
+
updated++;
|
|
3641
|
+
} else {
|
|
3642
|
+
const refreshed = { ...e.entry, cachedAt: (/* @__PURE__ */ new Date()).toISOString() };
|
|
3643
|
+
writeCache(e.filePath, refreshed);
|
|
3644
|
+
updated++;
|
|
3645
|
+
}
|
|
3646
|
+
} catch (err) {
|
|
3647
|
+
failed++;
|
|
3648
|
+
errors.push({
|
|
3649
|
+
key: e.entry.key,
|
|
3650
|
+
error: err instanceof Error ? err.message : "Unknown error"
|
|
3651
|
+
});
|
|
3652
|
+
}
|
|
3653
|
+
}
|
|
3654
|
+
spinner5.stop(`Updated ${updated} ${updated === 1 ? "entry" : "entries"}${failed > 0 ? `, ${failed} failed` : ""}`);
|
|
3655
|
+
if (isAgentMode()) {
|
|
3656
|
+
json({ updated, failed, errors: errors.length > 0 ? errors : void 0 });
|
|
3657
|
+
return;
|
|
3658
|
+
}
|
|
3659
|
+
if (errors.length > 0) {
|
|
3660
|
+
console.log();
|
|
3661
|
+
for (const e of errors) {
|
|
3662
|
+
console.log(` ${pc9.red("\u2717")} ${e.key}: ${pc9.dim(e.error)}`);
|
|
3663
|
+
}
|
|
3664
|
+
}
|
|
3665
|
+
}
|
|
3666
|
+
|
|
3667
|
+
// src/commands/guide.ts
|
|
3668
|
+
import pc10 from "picocolors";
|
|
3317
3669
|
|
|
3318
3670
|
// src/lib/guide-content.ts
|
|
3319
3671
|
var GUIDE_OVERVIEW = `# One CLI \u2014 Agent Guide
|
|
@@ -3404,6 +3756,7 @@ Request specific sections:
|
|
|
3404
3756
|
- \`one guide actions\` \u2014 Actions reference (search, knowledge, execute)
|
|
3405
3757
|
- \`one guide flows\` \u2014 Workflow engine reference (step types, selectors, examples)
|
|
3406
3758
|
- \`one guide relay\` \u2014 Webhook relay reference (templates, passthrough actions)
|
|
3759
|
+
- \`one guide cache\` \u2014 Cache management (TTL, flags, commands)
|
|
3407
3760
|
- \`one guide all\` \u2014 Everything
|
|
3408
3761
|
|
|
3409
3762
|
## Important Notes
|
|
@@ -3555,11 +3908,93 @@ Any connected platform can be a destination via passthrough actions.
|
|
|
3555
3908
|
3. \`relay deliveries --event-id <id>\` \u2014 check delivery status and errors
|
|
3556
3909
|
4. \`relay event <id>\` \u2014 inspect full payload to verify template paths
|
|
3557
3910
|
`;
|
|
3911
|
+
var GUIDE_CACHE = `# One Cache \u2014 Reference
|
|
3912
|
+
|
|
3913
|
+
## Overview
|
|
3914
|
+
|
|
3915
|
+
The One CLI caches \`actions knowledge\` and \`actions search\` responses locally so repeated calls serve instantly from disk instead of hitting the API. This is the single biggest latency win for agents who call knowledge for the same actions repeatedly.
|
|
3916
|
+
|
|
3917
|
+
Cache location: \`~/.one/cache/knowledge/\` and \`~/.one/cache/search/\`
|
|
3918
|
+
|
|
3919
|
+
## How It Works
|
|
3920
|
+
|
|
3921
|
+
- **First call**: fetches from the API, writes to cache, serves the response
|
|
3922
|
+
- **Subsequent calls (within TTL)**: serves from cache instantly, no API call
|
|
3923
|
+
- **After TTL expires**: makes a conditional request (ETag). If content unchanged, refreshes the cache timestamp. If changed, writes fresh data.
|
|
3924
|
+
- **Network failure with stale cache**: serves the stale cache with a warning \u2014 never fails hard when a cache exists
|
|
3925
|
+
|
|
3926
|
+
Default TTL: 3600 seconds (1 hour). Configure via \`ONE_CACHE_TTL\` env var or \`cacheTtl\` in \`~/.one/config.json\`.
|
|
3927
|
+
|
|
3928
|
+
## What Gets Cached
|
|
3929
|
+
|
|
3930
|
+
| Cached | Not Cached |
|
|
3931
|
+
|--------|-----------|
|
|
3932
|
+
| \`actions knowledge\` (API docs, change infrequently) | \`actions execute\` (live data, always fresh) |
|
|
3933
|
+
| \`actions search\` results | \`connection list\` (changes with add/remove) |
|
|
3934
|
+
|
|
3935
|
+
## Agent Mode \`_cache\` Metadata
|
|
3936
|
+
|
|
3937
|
+
In \`--agent\` mode, knowledge and search responses include a \`_cache\` field:
|
|
3938
|
+
|
|
3939
|
+
\`\`\`json
|
|
3940
|
+
{
|
|
3941
|
+
"knowledge": "...",
|
|
3942
|
+
"method": "POST",
|
|
3943
|
+
"_cache": {
|
|
3944
|
+
"hit": true,
|
|
3945
|
+
"age": 1423,
|
|
3946
|
+
"fresh": true
|
|
3947
|
+
}
|
|
3948
|
+
}
|
|
3949
|
+
\`\`\`
|
|
3950
|
+
|
|
3951
|
+
Use this to programmatically decide whether to force-refresh.
|
|
3952
|
+
|
|
3953
|
+
## Cache Flags
|
|
3954
|
+
|
|
3955
|
+
\`\`\`bash
|
|
3956
|
+
# Skip cache, fetch fresh (result still gets cached for next time)
|
|
3957
|
+
one --agent actions knowledge <platform> <actionId> --no-cache
|
|
3958
|
+
|
|
3959
|
+
# Check cache status without fetching
|
|
3960
|
+
one --agent actions knowledge <platform> <actionId> --cache-status
|
|
3961
|
+
|
|
3962
|
+
# Same for search
|
|
3963
|
+
one --agent actions search <platform> "<query>" --no-cache
|
|
3964
|
+
\`\`\`
|
|
3965
|
+
|
|
3966
|
+
## Cache Management Commands
|
|
3967
|
+
|
|
3968
|
+
\`\`\`bash
|
|
3969
|
+
one cache list # List all cached entries with age and status
|
|
3970
|
+
one cache list --expired # List only expired entries
|
|
3971
|
+
one cache clear # Delete all cached knowledge and search data
|
|
3972
|
+
one cache clear <actionId> # Delete one specific entry
|
|
3973
|
+
one cache update-all # Re-fetch fresh data for all cached entries
|
|
3974
|
+
\`\`\`
|
|
3975
|
+
|
|
3976
|
+
All cache commands respect \`--agent\` for JSON output.
|
|
3977
|
+
|
|
3978
|
+
## When to Force-Refresh
|
|
3979
|
+
|
|
3980
|
+
- After a platform updates its API docs (rare)
|
|
3981
|
+
- If you suspect stale data is causing issues
|
|
3982
|
+
- Use \`one cache update-all\` to proactively warm the entire cache
|
|
3983
|
+
|
|
3984
|
+
## Configuration
|
|
3985
|
+
|
|
3986
|
+
| Setting | Source | Example |
|
|
3987
|
+
|---------|--------|---------|
|
|
3988
|
+
| TTL (seconds) | \`ONE_CACHE_TTL\` env var | \`ONE_CACHE_TTL=7200\` |
|
|
3989
|
+
| TTL (seconds) | \`cacheTtl\` in \`~/.one/config.json\` | \`"cacheTtl": 7200\` |
|
|
3990
|
+
| Default | \u2014 | 3600 (1 hour) |
|
|
3991
|
+
`;
|
|
3558
3992
|
var TOPICS = [
|
|
3559
3993
|
{ topic: "overview", description: "Setup, features, and quick start for each" },
|
|
3560
3994
|
{ topic: "actions", description: "Search, read docs, and execute platform actions" },
|
|
3561
3995
|
{ topic: "flows", description: "Build and execute multi-step workflows" },
|
|
3562
3996
|
{ topic: "relay", description: "Receive webhooks and forward to other platforms" },
|
|
3997
|
+
{ topic: "cache", description: "Local caching for knowledge and search responses" },
|
|
3563
3998
|
{ topic: "all", description: "Complete guide (all topics combined)" }
|
|
3564
3999
|
];
|
|
3565
4000
|
function getGuideContent(topic) {
|
|
@@ -3572,10 +4007,12 @@ function getGuideContent(topic) {
|
|
|
3572
4007
|
return { title: "One CLI \u2014 Agent Guide: Workflows", content: GUIDE_FLOWS };
|
|
3573
4008
|
case "relay":
|
|
3574
4009
|
return { title: "One CLI \u2014 Agent Guide: Relay", content: GUIDE_RELAY };
|
|
4010
|
+
case "cache":
|
|
4011
|
+
return { title: "One CLI \u2014 Agent Guide: Cache", content: GUIDE_CACHE };
|
|
3575
4012
|
case "all":
|
|
3576
4013
|
return {
|
|
3577
4014
|
title: "One CLI \u2014 Agent Guide: Complete",
|
|
3578
|
-
content: [GUIDE_OVERVIEW, GUIDE_ACTIONS, GUIDE_FLOWS, GUIDE_RELAY].join("\n---\n\n")
|
|
4015
|
+
content: [GUIDE_OVERVIEW, GUIDE_ACTIONS, GUIDE_FLOWS, GUIDE_RELAY, GUIDE_CACHE].join("\n---\n\n")
|
|
3579
4016
|
};
|
|
3580
4017
|
}
|
|
3581
4018
|
}
|
|
@@ -3584,7 +4021,7 @@ function getAvailableTopics() {
|
|
|
3584
4021
|
}
|
|
3585
4022
|
|
|
3586
4023
|
// src/commands/guide.ts
|
|
3587
|
-
var VALID_TOPICS = ["overview", "actions", "flows", "relay", "all"];
|
|
4024
|
+
var VALID_TOPICS = ["overview", "actions", "flows", "relay", "cache", "all"];
|
|
3588
4025
|
async function guideCommand(topic = "all") {
|
|
3589
4026
|
if (!VALID_TOPICS.includes(topic)) {
|
|
3590
4027
|
error(
|
|
@@ -3597,14 +4034,14 @@ async function guideCommand(topic = "all") {
|
|
|
3597
4034
|
json({ topic, title, content, availableTopics });
|
|
3598
4035
|
return;
|
|
3599
4036
|
}
|
|
3600
|
-
intro2(
|
|
4037
|
+
intro2(pc10.bgCyan(pc10.black(" One Guide ")));
|
|
3601
4038
|
console.log();
|
|
3602
4039
|
console.log(content);
|
|
3603
|
-
console.log(
|
|
4040
|
+
console.log(pc10.dim("\u2500".repeat(60)));
|
|
3604
4041
|
console.log(
|
|
3605
|
-
|
|
4042
|
+
pc10.dim("Available topics: ") + availableTopics.map((t) => pc10.cyan(t.topic)).join(", ")
|
|
3606
4043
|
);
|
|
3607
|
-
console.log(
|
|
4044
|
+
console.log(pc10.dim(`Run ${pc10.cyan("one guide <topic>")} for a specific section.`));
|
|
3608
4045
|
}
|
|
3609
4046
|
|
|
3610
4047
|
// src/lib/platform-meta.ts
|
|
@@ -3947,14 +4384,14 @@ async function fetchLatestVersionInfo() {
|
|
|
3947
4384
|
return null;
|
|
3948
4385
|
}
|
|
3949
4386
|
}
|
|
3950
|
-
function
|
|
4387
|
+
function readCache3() {
|
|
3951
4388
|
try {
|
|
3952
4389
|
return JSON.parse(readFileSync(CACHE_PATH, "utf8"));
|
|
3953
4390
|
} catch {
|
|
3954
4391
|
return null;
|
|
3955
4392
|
}
|
|
3956
4393
|
}
|
|
3957
|
-
function
|
|
4394
|
+
function writeCache2(latestVersion, publishedAt) {
|
|
3958
4395
|
try {
|
|
3959
4396
|
mkdirSync(join(homedir(), ".one"), { recursive: true });
|
|
3960
4397
|
writeFileSync(CACHE_PATH, JSON.stringify({ lastCheck: Date.now(), latestVersion, publishedAt }));
|
|
@@ -3963,16 +4400,16 @@ function writeCache(latestVersion, publishedAt) {
|
|
|
3963
4400
|
}
|
|
3964
4401
|
async function checkLatestVersion() {
|
|
3965
4402
|
const info = await fetchLatestVersionInfo();
|
|
3966
|
-
if (info)
|
|
4403
|
+
if (info) writeCache2(info.version, info.publishedAt);
|
|
3967
4404
|
return info?.version ?? null;
|
|
3968
4405
|
}
|
|
3969
4406
|
async function checkLatestVersionCached() {
|
|
3970
|
-
const
|
|
3971
|
-
if (
|
|
3972
|
-
return { version:
|
|
4407
|
+
const cache2 = readCache3();
|
|
4408
|
+
if (cache2 && Date.now() - cache2.lastCheck < CHECK_INTERVAL_MS) {
|
|
4409
|
+
return { version: cache2.latestVersion, publishedAt: cache2.publishedAt ?? null };
|
|
3973
4410
|
}
|
|
3974
4411
|
const info = await fetchLatestVersionInfo();
|
|
3975
|
-
if (info)
|
|
4412
|
+
if (info) writeCache2(info.version, info.publishedAt);
|
|
3976
4413
|
return info;
|
|
3977
4414
|
}
|
|
3978
4415
|
function getCurrentVersion() {
|
|
@@ -4054,6 +4491,11 @@ program.name("one").option("--agent", "Machine-readable JSON output (no colors,
|
|
|
4054
4491
|
one flow execute <key> Execute a workflow
|
|
4055
4492
|
one flow validate <key> Validate a flow
|
|
4056
4493
|
|
|
4494
|
+
Cache:
|
|
4495
|
+
one cache list List cached entries with age and status
|
|
4496
|
+
one cache clear Clear all cached knowledge and search data
|
|
4497
|
+
one cache update-all Re-fetch fresh data for all cached entries
|
|
4498
|
+
|
|
4057
4499
|
Webhook Relay:
|
|
4058
4500
|
one relay create Create a relay endpoint for a connection
|
|
4059
4501
|
one relay list List relay endpoints
|
|
@@ -4113,11 +4555,11 @@ program.command("platforms").alias("p").description("List available platforms").
|
|
|
4113
4555
|
await platformsCommand(options);
|
|
4114
4556
|
});
|
|
4115
4557
|
var actions = program.command("actions").alias("a").description("Search, explore, and execute platform actions (workflow: search \u2192 knowledge \u2192 execute)");
|
|
4116
|
-
actions.command("search <platform> <query>").description('Search for actions on a platform (e.g. one actions search gmail "send email")').option("-t, --type <type>", "execute (to run it) or knowledge (to learn about it). Default: knowledge").action(async (platform, query, options) => {
|
|
4558
|
+
actions.command("search <platform> <query>").description('Search for actions on a platform (e.g. one actions search gmail "send email")').option("-t, --type <type>", "execute (to run it) or knowledge (to learn about it). Default: knowledge").option("--no-cache", "Skip cache, fetch fresh from API").action(async (platform, query, options) => {
|
|
4117
4559
|
await actionsSearchCommand(platform, query, options);
|
|
4118
4560
|
});
|
|
4119
|
-
actions.command("knowledge <platform> <actionId>").alias("k").description("Get full docs for an action \u2014 MUST call before execute to know required params").action(async (platform, actionId) => {
|
|
4120
|
-
await actionsKnowledgeCommand(platform, actionId);
|
|
4561
|
+
actions.command("knowledge <platform> <actionId>").alias("k").description("Get full docs for an action \u2014 MUST call before execute to know required params").option("--no-cache", "Skip cache, fetch fresh from API").option("--cache-status", "Print cache metadata without fetching").action(async (platform, actionId, options) => {
|
|
4562
|
+
await actionsKnowledgeCommand(platform, actionId, options);
|
|
4121
4563
|
});
|
|
4122
4564
|
actions.command("execute <platform> <actionId> <connectionKey>").alias("x").description('Execute an action \u2014 pass connectionKey from "one list", actionId from "actions search"').option("-d, --data <json>", "Request body as JSON").option("--path-vars <json>", "Path variables as JSON").option("--query-params <json>", "Query parameters as JSON").option("--headers <json>", "Additional headers as JSON").option("--form-data", "Send as multipart/form-data").option("--form-url-encoded", "Send as application/x-www-form-urlencoded").option("--dry-run", "Show request that would be sent without executing").action(async (platform, actionId, connectionKey, options) => {
|
|
4123
4565
|
await actionsExecuteCommand(platform, actionId, connectionKey, {
|
|
@@ -4183,6 +4625,16 @@ relay.command("deliveries").description("List delivery attempts for an endpoint
|
|
|
4183
4625
|
relay.command("event-types <platform>").description("List supported webhook event types for a platform").action(async (platform) => {
|
|
4184
4626
|
await relayEventTypesCommand(platform);
|
|
4185
4627
|
});
|
|
4628
|
+
var cache = program.command("cache").description("Manage the local knowledge and search cache");
|
|
4629
|
+
cache.command("clear [actionId]").description("Clear all cached data, or a specific action by ID").action(async (actionId) => {
|
|
4630
|
+
await cacheClearCommand(actionId);
|
|
4631
|
+
});
|
|
4632
|
+
cache.command("list").alias("ls").description("List all cached entries with age and status").option("--expired", "Show only expired entries").action(async (options) => {
|
|
4633
|
+
await cacheListCommand(options);
|
|
4634
|
+
});
|
|
4635
|
+
cache.command("update-all").description("Re-fetch fresh data for all cached entries").action(async () => {
|
|
4636
|
+
await cacheUpdateAllCommand();
|
|
4637
|
+
});
|
|
4186
4638
|
program.command("guide [topic]").description("Full CLI usage guide for agents (topics: overview, actions, flows, relay, all)").action(async (topic) => {
|
|
4187
4639
|
await guideCommand(topic);
|
|
4188
4640
|
});
|
package/package.json
CHANGED
package/skills/one/SKILL.md
CHANGED
|
@@ -100,6 +100,18 @@ All errors return JSON: `{"error": "message"}`. Parse output as JSON and check f
|
|
|
100
100
|
- JSON values passed to `-d`, `--path-vars`, `--query-params` must be valid JSON (use single quotes around JSON to avoid shell escaping)
|
|
101
101
|
- Do NOT pass path or query parameters inside the `-d` body flag
|
|
102
102
|
|
|
103
|
+
## Caching
|
|
104
|
+
|
|
105
|
+
Knowledge and search responses are cached locally (`~/.one/cache/`). Subsequent calls for the same action serve instantly from disk.
|
|
106
|
+
|
|
107
|
+
- Cache is automatic — no setup required
|
|
108
|
+
- Default TTL: 1 hour (configurable via `ONE_CACHE_TTL` env var)
|
|
109
|
+
- In `--agent` mode, responses include a `_cache` field: `{"hit": true, "age": 1423, "fresh": true}`
|
|
110
|
+
- Use `--no-cache` to force a fresh fetch: `one --agent actions knowledge <platform> <actionId> --no-cache`
|
|
111
|
+
- Use `--cache-status` to check cache state without fetching
|
|
112
|
+
- Manage cache: `one cache list`, `one cache clear`, `one cache update-all`
|
|
113
|
+
- `actions execute` is NEVER cached — always fresh
|
|
114
|
+
|
|
103
115
|
## Beyond Single Actions
|
|
104
116
|
|
|
105
117
|
One also supports more advanced patterns. Read the relevant reference file before using these:
|