@withone/cli 1.17.1 → 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-AJGIUT6E.js");
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,
@@ -4,7 +4,7 @@ import {
4
4
  loadFlow,
5
5
  resolveFlowPath,
6
6
  saveFlow
7
- } from "./chunk-QW4MBV4F.js";
7
+ } from "./chunk-CTL2YHUH.js";
8
8
  export {
9
9
  FlowRunner,
10
10
  listFlows,
package/dist/index.js CHANGED
@@ -11,7 +11,7 @@ import {
11
11
  loadFlow,
12
12
  resolveFlowPath,
13
13
  saveFlow
14
- } from "./chunk-QW4MBV4F.js";
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;
@@ -223,12 +232,6 @@ var AGENTS = [
223
232
  projectConfigPath: ".kiro/settings/mcp.json"
224
233
  }
225
234
  ];
226
- function getAllAgents() {
227
- return AGENTS;
228
- }
229
- function supportsProjectScope(agent) {
230
- return agent.projectConfigPath !== void 0;
231
- }
232
235
  function getAgentConfigPath(agent, scope = "global") {
233
236
  if (scope === "project" && agent.projectConfigPath) {
234
237
  return path2.join(process.cwd(), agent.projectConfigPath);
@@ -589,11 +592,6 @@ async function handleExistingConfig(apiKey, options) {
589
592
  label: "Configure access control",
590
593
  hint: "permissions, connections, actions"
591
594
  });
592
- actionOptions.push({
593
- value: "install-mcp",
594
- label: "Install MCP server",
595
- hint: "not recommended \u2014 use skills instead"
596
- });
597
595
  actionOptions.push({
598
596
  value: "start-fresh",
599
597
  label: "Start fresh (reconfigure everything)"
@@ -631,10 +629,6 @@ async function handleExistingConfig(apiKey, options) {
631
629
  case "access-control":
632
630
  await configCommand();
633
631
  break;
634
- case "install-mcp":
635
- await promptAndInstallMcp(apiKey, options);
636
- p3.outro("Done.");
637
- break;
638
632
  case "start-fresh":
639
633
  await freshSetup({ yes: true });
640
634
  break;
@@ -859,102 +853,6 @@ function printOnboardingPrompt() {
859
853
  console.log(pc2.cyan(" \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
860
854
  console.log();
861
855
  }
862
- async function promptAndInstallMcp(apiKey, options) {
863
- const allAgents = getAllAgents();
864
- const agentChoice = await p3.select({
865
- message: "Where do you want to install the MCP?",
866
- options: [
867
- {
868
- value: "all",
869
- label: "All agents",
870
- hint: allAgents.map((a) => a.name).join(", ")
871
- },
872
- ...allAgents.map((agent) => ({
873
- value: agent.id,
874
- label: agent.name
875
- }))
876
- ]
877
- });
878
- if (p3.isCancel(agentChoice)) {
879
- p3.log.info("Skipped MCP installation.");
880
- return;
881
- }
882
- const selectedAgents = agentChoice === "all" ? allAgents : allAgents.filter((a) => a.id === agentChoice);
883
- let scope = "global";
884
- const hasProjectScopeAgent = selectedAgents.some((a) => supportsProjectScope(a));
885
- if (options.global) {
886
- scope = "global";
887
- } else if (options.project) {
888
- scope = "project";
889
- } else if (hasProjectScopeAgent) {
890
- const scopeChoice = await p3.select({
891
- message: "How do you want to install it?",
892
- options: [
893
- {
894
- value: "global",
895
- label: "Global (Recommended)",
896
- hint: "Available in all your projects"
897
- },
898
- {
899
- value: "project",
900
- label: "Project only",
901
- hint: "Creates config files in current directory"
902
- }
903
- ]
904
- });
905
- if (p3.isCancel(scopeChoice)) {
906
- p3.log.info("Skipped MCP installation.");
907
- return;
908
- }
909
- scope = scopeChoice;
910
- }
911
- if (scope === "project") {
912
- const projectAgents = selectedAgents.filter((a) => supportsProjectScope(a));
913
- const nonProjectAgents = selectedAgents.filter((a) => !supportsProjectScope(a));
914
- if (projectAgents.length === 0) {
915
- const supported = allAgents.filter((a) => supportsProjectScope(a)).map((a) => a.name).join(", ");
916
- p3.note(
917
- `${selectedAgents.map((a) => a.name).join(", ")} does not support project-level MCP.
918
- Project scope is supported by: ${supported}`,
919
- "Not Supported"
920
- );
921
- p3.log.warn("Run again and choose global scope or a different agent.");
922
- return;
923
- }
924
- for (const agent of projectAgents) {
925
- const wasInstalled = isMcpInstalled(agent, "project");
926
- installMcpConfig(agent, apiKey, "project");
927
- const configPath = getAgentConfigPath(agent, "project");
928
- const status = wasInstalled ? "updated" : "created";
929
- p3.log.success(`${agent.name}: ${configPath} ${status}`);
930
- }
931
- if (nonProjectAgents.length > 0) {
932
- p3.log.info(`Installing globally for agents without project scope support:`);
933
- for (const agent of nonProjectAgents) {
934
- const wasInstalled = isMcpInstalled(agent, "global");
935
- installMcpConfig(agent, apiKey, "global");
936
- const status = wasInstalled ? "updated" : "installed";
937
- p3.log.success(`${agent.name}: MCP ${status} (global)`);
938
- }
939
- }
940
- const allInstalled = [...projectAgents, ...nonProjectAgents];
941
- updateConfigAgentsList(allInstalled.map((a) => a.id));
942
- p3.note(
943
- pc2.yellow("Project config files can be committed to share with your team.\n") + pc2.yellow("Team members will need their own API key."),
944
- "Tip"
945
- );
946
- return;
947
- }
948
- const installedAgentIds = [];
949
- for (const agent of selectedAgents) {
950
- const wasInstalled = isMcpInstalled(agent, "global");
951
- installMcpConfig(agent, apiKey, "global");
952
- installedAgentIds.push(agent.id);
953
- const status = wasInstalled ? "updated" : "installed";
954
- p3.log.success(`${agent.name}: MCP ${status}`);
955
- }
956
- updateConfigAgentsList(installedAgentIds);
957
- }
958
856
  async function freshSetup(options) {
959
857
  p3.note(`Get your API key at:
960
858
  ${pc2.cyan(getApiKeyUrl())}`, "API Key");
@@ -1001,13 +899,6 @@ ${pc2.cyan(getApiKeyUrl())}`, "API Key");
1001
899
  });
1002
900
  await promptSkillInstall();
1003
901
  await promptConnectIntegrations(apiKey);
1004
- const installMcp = await p3.confirm({
1005
- message: "Install One MCP server to your AI agents? (not recommended)",
1006
- initialValue: false
1007
- });
1008
- if (!p3.isCancel(installMcp) && installMcp) {
1009
- await promptAndInstallMcp(apiKey, options);
1010
- }
1011
902
  p3.note(
1012
903
  `Config saved to: ${pc2.dim(getConfigPath())}`,
1013
904
  "Setup Complete"
@@ -1112,16 +1003,6 @@ function maskApiKey(key) {
1112
1003
  if (key.length <= 12) return key.slice(0, 8) + "...";
1113
1004
  return key.slice(0, 8) + "..." + key.slice(-4);
1114
1005
  }
1115
- function updateConfigAgentsList(agentIds) {
1116
- const config = readConfig();
1117
- if (!config) return;
1118
- for (const id of agentIds) {
1119
- if (!config.installedAgents.includes(id)) {
1120
- config.installedAgents.push(id);
1121
- }
1122
- }
1123
- writeConfig(config);
1124
- }
1125
1006
 
1126
1007
  // src/commands/connection.ts
1127
1008
  import * as p4 from "@clack/prompts";
@@ -1478,6 +1359,123 @@ async function platformsCommand(options) {
1478
1359
  // src/commands/actions.ts
1479
1360
  import * as p6 from "@clack/prompts";
1480
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
1481
1479
  function getConfig() {
1482
1480
  const apiKey = getApiKey();
1483
1481
  if (!apiKey) {
@@ -1505,17 +1503,65 @@ async function actionsSearchCommand(platform, query, options) {
1505
1503
  spinner5.start(`Searching actions on ${pc6.cyan(platform)} for "${query}"...`);
1506
1504
  try {
1507
1505
  const agentType = knowledgeAgent ? "knowledge" : options.type;
1508
- let actions2 = await api.searchActions(platform, query, agentType);
1509
- actions2 = filterByPermissions(actions2, permissions);
1510
- actions2 = actions2.filter((a) => isActionAllowed(a.systemId, actionIds));
1511
- const cleanedActions = actions2.map((action) => ({
1512
- actionId: action.systemId,
1513
- title: action.title,
1514
- method: action.method,
1515
- path: action.path
1516
- }));
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
+ }
1517
1556
  if (isAgentMode()) {
1518
- json({ actions: cleanedActions });
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);
1519
1565
  return;
1520
1566
  }
1521
1567
  if (cleanedActions.length === 0) {
@@ -1569,7 +1615,29 @@ Execute: ${pc6.cyan(`one actions execute ${platform} <actionId> <connectionK
1569
1615
  );
1570
1616
  }
1571
1617
  }
1572
- 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
+ }
1573
1641
  intro2(pc6.bgCyan(pc6.black(" One ")));
1574
1642
  const { apiKey, actionIds, connectionKeys } = getConfig();
1575
1643
  const api = new OneApi(apiKey);
@@ -1597,15 +1665,57 @@ async function actionsKnowledgeCommand(platform, actionId) {
1597
1665
  const spinner5 = createSpinner();
1598
1666
  spinner5.start(`Loading knowledge for action ${pc6.dim(actionId)}...`);
1599
1667
  try {
1600
- const { knowledge, method } = await api.getActionKnowledge(actionId);
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
+ }
1601
1706
  const knowledgeWithGuidance = buildActionKnowledgeWithGuidance(
1602
- knowledge,
1603
- method,
1707
+ knowledgeData.knowledge,
1708
+ knowledgeData.method,
1604
1709
  platform,
1605
1710
  actionId
1606
1711
  );
1607
1712
  if (isAgentMode()) {
1608
- json({ knowledge: knowledgeWithGuidance, method });
1713
+ const response = {
1714
+ knowledge: knowledgeWithGuidance,
1715
+ method: knowledgeData.method,
1716
+ _cache: buildCacheMeta(cacheEntry, cacheHit)
1717
+ };
1718
+ json(response);
1609
1719
  return;
1610
1720
  }
1611
1721
  spinner5.stop("Knowledge loaded");
@@ -2325,26 +2435,26 @@ function validateStepsArray(steps, pathPrefix, errors) {
2325
2435
  const validTypes = FLOW_SCHEMA.stepTypes.map((st) => st.type);
2326
2436
  for (let i = 0; i < steps.length; i++) {
2327
2437
  const step = steps[i];
2328
- const path4 = `${pathPrefix}[${i}]`;
2438
+ const path5 = `${pathPrefix}[${i}]`;
2329
2439
  if (!step || typeof step !== "object" || Array.isArray(step)) {
2330
- errors.push({ path: path4, message: "Step must be an object" });
2440
+ errors.push({ path: path5, message: "Step must be an object" });
2331
2441
  continue;
2332
2442
  }
2333
2443
  const s = step;
2334
2444
  if (!s.id || typeof s.id !== "string") {
2335
- errors.push({ path: `${path4}.id`, message: 'Step must have a string "id"' });
2445
+ errors.push({ path: `${path5}.id`, message: 'Step must have a string "id"' });
2336
2446
  }
2337
2447
  if (!s.name || typeof s.name !== "string") {
2338
- errors.push({ path: `${path4}.name`, message: 'Step must have a string "name"' });
2448
+ errors.push({ path: `${path5}.name`, message: 'Step must have a string "name"' });
2339
2449
  }
2340
2450
  if (!s.type || !validTypes.includes(s.type)) {
2341
- errors.push({ path: `${path4}.type`, message: `Step type must be one of: ${validTypes.join(", ")}` });
2451
+ errors.push({ path: `${path5}.type`, message: `Step type must be one of: ${validTypes.join(", ")}` });
2342
2452
  continue;
2343
2453
  }
2344
2454
  if (s.onError && typeof s.onError === "object") {
2345
2455
  const oe = s.onError;
2346
2456
  if (!FLOW_SCHEMA.errorStrategies.includes(oe.strategy)) {
2347
- errors.push({ path: `${path4}.onError.strategy`, message: `Error strategy must be one of: ${FLOW_SCHEMA.errorStrategies.join(", ")}` });
2457
+ errors.push({ path: `${path5}.onError.strategy`, message: `Error strategy must be one of: ${FLOW_SCHEMA.errorStrategies.join(", ")}` });
2348
2458
  }
2349
2459
  }
2350
2460
  const descriptor = getStepTypeDescriptor(s.type);
@@ -2354,14 +2464,14 @@ function validateStepsArray(steps, pathPrefix, errors) {
2354
2464
  if (!configObj || typeof configObj !== "object") {
2355
2465
  const hint = detectFlatConfigHint(s, descriptor);
2356
2466
  errors.push({
2357
- path: `${path4}.${configKey}`,
2467
+ path: `${path5}.${configKey}`,
2358
2468
  message: `${capitalize(descriptor.type)} step must have a "${configKey}" config object${hint}`
2359
2469
  });
2360
2470
  continue;
2361
2471
  }
2362
2472
  const config = configObj;
2363
2473
  for (const [fieldName, fd] of Object.entries(descriptor.fields)) {
2364
- const fieldPath = `${path4}.${configKey}.${fieldName}`;
2474
+ const fieldPath = `${path5}.${configKey}.${fieldName}`;
2365
2475
  const value = config[fieldName];
2366
2476
  if (fd.required && (value === void 0 || value === null || value === "")) {
2367
2477
  errors.push({ path: fieldPath, message: `${capitalize(descriptor.type)} must have ${fd.type === "string" ? "a string" : fd.type === "array" ? "a" : "a"} "${fieldName}"` });
@@ -2413,16 +2523,16 @@ function validateStepIds(flow2) {
2413
2523
  function collectIds(steps, pathPrefix) {
2414
2524
  for (let i = 0; i < steps.length; i++) {
2415
2525
  const step = steps[i];
2416
- const path4 = `${pathPrefix}[${i}]`;
2526
+ const path5 = `${pathPrefix}[${i}]`;
2417
2527
  if (seen.has(step.id)) {
2418
- errors.push({ path: `${path4}.id`, message: `Duplicate step ID: "${step.id}"` });
2528
+ errors.push({ path: `${path5}.id`, message: `Duplicate step ID: "${step.id}"` });
2419
2529
  } else {
2420
2530
  seen.add(step.id);
2421
2531
  }
2422
2532
  for (const { configKey, fieldName } of nestedKeys) {
2423
2533
  const config = step[configKey];
2424
2534
  if (config && Array.isArray(config[fieldName])) {
2425
- collectIds(config[fieldName], `${path4}.${configKey}.${fieldName}`);
2535
+ collectIds(config[fieldName], `${path5}.${configKey}.${fieldName}`);
2426
2536
  }
2427
2537
  }
2428
2538
  }
@@ -2469,7 +2579,7 @@ function validateSelectorReferences(flow2) {
2469
2579
  }
2470
2580
  return selectors;
2471
2581
  }
2472
- function checkSelectors(selectors, path4) {
2582
+ function checkSelectors(selectors, path5) {
2473
2583
  for (const selector of selectors) {
2474
2584
  const parts = selector.split(".");
2475
2585
  if (parts.length < 3) continue;
@@ -2477,12 +2587,12 @@ function validateSelectorReferences(flow2) {
2477
2587
  if (root === "input") {
2478
2588
  const inputName = parts[2];
2479
2589
  if (!inputNames.has(inputName)) {
2480
- errors.push({ path: path4, message: `Selector "${selector}" references undefined input "${inputName}"` });
2590
+ errors.push({ path: path5, message: `Selector "${selector}" references undefined input "${inputName}"` });
2481
2591
  }
2482
2592
  } else if (root === "steps") {
2483
2593
  const stepId = parts[2];
2484
2594
  if (!allStepIds.has(stepId)) {
2485
- errors.push({ path: path4, message: `Selector "${selector}" references undefined step "${stepId}"` });
2595
+ errors.push({ path: path5, message: `Selector "${selector}" references undefined step "${stepId}"` });
2486
2596
  }
2487
2597
  }
2488
2598
  }
@@ -2530,7 +2640,7 @@ function validateFlow(flow2) {
2530
2640
  }
2531
2641
 
2532
2642
  // src/commands/flow.ts
2533
- import fs4 from "fs";
2643
+ import fs5 from "fs";
2534
2644
  function getConfig2() {
2535
2645
  const apiKey = getApiKey();
2536
2646
  if (!apiKey) {
@@ -2588,7 +2698,7 @@ async function flowCreateCommand(key, options) {
2588
2698
  if (raw.startsWith("@")) {
2589
2699
  const filePath = raw.slice(1);
2590
2700
  try {
2591
- raw = fs4.readFileSync(filePath, "utf-8");
2701
+ raw = fs5.readFileSync(filePath, "utf-8");
2592
2702
  } catch (err) {
2593
2703
  error(`Cannot read file "${filePath}": ${err.message}`);
2594
2704
  }
@@ -2771,7 +2881,7 @@ async function flowValidateCommand(keyOrPath) {
2771
2881
  let flowData;
2772
2882
  try {
2773
2883
  const flowPath = resolveFlowPath(keyOrPath);
2774
- const content = fs4.readFileSync(flowPath, "utf-8");
2884
+ const content = fs5.readFileSync(flowPath, "utf-8");
2775
2885
  flowData = JSON.parse(content);
2776
2886
  } catch (err) {
2777
2887
  spinner5.stop("Validation failed");
@@ -3440,8 +3550,122 @@ async function relayEventTypesCommand(platform) {
3440
3550
  }
3441
3551
  }
3442
3552
 
3443
- // src/commands/guide.ts
3553
+ // src/commands/cache.ts
3444
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";
3445
3669
 
3446
3670
  // src/lib/guide-content.ts
3447
3671
  var GUIDE_OVERVIEW = `# One CLI \u2014 Agent Guide
@@ -3532,6 +3756,7 @@ Request specific sections:
3532
3756
  - \`one guide actions\` \u2014 Actions reference (search, knowledge, execute)
3533
3757
  - \`one guide flows\` \u2014 Workflow engine reference (step types, selectors, examples)
3534
3758
  - \`one guide relay\` \u2014 Webhook relay reference (templates, passthrough actions)
3759
+ - \`one guide cache\` \u2014 Cache management (TTL, flags, commands)
3535
3760
  - \`one guide all\` \u2014 Everything
3536
3761
 
3537
3762
  ## Important Notes
@@ -3683,11 +3908,93 @@ Any connected platform can be a destination via passthrough actions.
3683
3908
  3. \`relay deliveries --event-id <id>\` \u2014 check delivery status and errors
3684
3909
  4. \`relay event <id>\` \u2014 inspect full payload to verify template paths
3685
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
+ `;
3686
3992
  var TOPICS = [
3687
3993
  { topic: "overview", description: "Setup, features, and quick start for each" },
3688
3994
  { topic: "actions", description: "Search, read docs, and execute platform actions" },
3689
3995
  { topic: "flows", description: "Build and execute multi-step workflows" },
3690
3996
  { topic: "relay", description: "Receive webhooks and forward to other platforms" },
3997
+ { topic: "cache", description: "Local caching for knowledge and search responses" },
3691
3998
  { topic: "all", description: "Complete guide (all topics combined)" }
3692
3999
  ];
3693
4000
  function getGuideContent(topic) {
@@ -3700,10 +4007,12 @@ function getGuideContent(topic) {
3700
4007
  return { title: "One CLI \u2014 Agent Guide: Workflows", content: GUIDE_FLOWS };
3701
4008
  case "relay":
3702
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 };
3703
4012
  case "all":
3704
4013
  return {
3705
4014
  title: "One CLI \u2014 Agent Guide: Complete",
3706
- 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")
3707
4016
  };
3708
4017
  }
3709
4018
  }
@@ -3712,7 +4021,7 @@ function getAvailableTopics() {
3712
4021
  }
3713
4022
 
3714
4023
  // src/commands/guide.ts
3715
- var VALID_TOPICS = ["overview", "actions", "flows", "relay", "all"];
4024
+ var VALID_TOPICS = ["overview", "actions", "flows", "relay", "cache", "all"];
3716
4025
  async function guideCommand(topic = "all") {
3717
4026
  if (!VALID_TOPICS.includes(topic)) {
3718
4027
  error(
@@ -3725,14 +4034,14 @@ async function guideCommand(topic = "all") {
3725
4034
  json({ topic, title, content, availableTopics });
3726
4035
  return;
3727
4036
  }
3728
- intro2(pc9.bgCyan(pc9.black(" One Guide ")));
4037
+ intro2(pc10.bgCyan(pc10.black(" One Guide ")));
3729
4038
  console.log();
3730
4039
  console.log(content);
3731
- console.log(pc9.dim("\u2500".repeat(60)));
4040
+ console.log(pc10.dim("\u2500".repeat(60)));
3732
4041
  console.log(
3733
- pc9.dim("Available topics: ") + availableTopics.map((t) => pc9.cyan(t.topic)).join(", ")
4042
+ pc10.dim("Available topics: ") + availableTopics.map((t) => pc10.cyan(t.topic)).join(", ")
3734
4043
  );
3735
- console.log(pc9.dim(`Run ${pc9.cyan("one guide <topic>")} for a specific section.`));
4044
+ console.log(pc10.dim(`Run ${pc10.cyan("one guide <topic>")} for a specific section.`));
3736
4045
  }
3737
4046
 
3738
4047
  // src/lib/platform-meta.ts
@@ -4075,14 +4384,14 @@ async function fetchLatestVersionInfo() {
4075
4384
  return null;
4076
4385
  }
4077
4386
  }
4078
- function readCache() {
4387
+ function readCache3() {
4079
4388
  try {
4080
4389
  return JSON.parse(readFileSync(CACHE_PATH, "utf8"));
4081
4390
  } catch {
4082
4391
  return null;
4083
4392
  }
4084
4393
  }
4085
- function writeCache(latestVersion, publishedAt) {
4394
+ function writeCache2(latestVersion, publishedAt) {
4086
4395
  try {
4087
4396
  mkdirSync(join(homedir(), ".one"), { recursive: true });
4088
4397
  writeFileSync(CACHE_PATH, JSON.stringify({ lastCheck: Date.now(), latestVersion, publishedAt }));
@@ -4091,16 +4400,16 @@ function writeCache(latestVersion, publishedAt) {
4091
4400
  }
4092
4401
  async function checkLatestVersion() {
4093
4402
  const info = await fetchLatestVersionInfo();
4094
- if (info) writeCache(info.version, info.publishedAt);
4403
+ if (info) writeCache2(info.version, info.publishedAt);
4095
4404
  return info?.version ?? null;
4096
4405
  }
4097
4406
  async function checkLatestVersionCached() {
4098
- const cache = readCache();
4099
- if (cache && Date.now() - cache.lastCheck < CHECK_INTERVAL_MS) {
4100
- return { version: cache.latestVersion, publishedAt: cache.publishedAt ?? null };
4407
+ const cache2 = readCache3();
4408
+ if (cache2 && Date.now() - cache2.lastCheck < CHECK_INTERVAL_MS) {
4409
+ return { version: cache2.latestVersion, publishedAt: cache2.publishedAt ?? null };
4101
4410
  }
4102
4411
  const info = await fetchLatestVersionInfo();
4103
- if (info) writeCache(info.version, info.publishedAt);
4412
+ if (info) writeCache2(info.version, info.publishedAt);
4104
4413
  return info;
4105
4414
  }
4106
4415
  function getCurrentVersion() {
@@ -4182,6 +4491,11 @@ program.name("one").option("--agent", "Machine-readable JSON output (no colors,
4182
4491
  one flow execute <key> Execute a workflow
4183
4492
  one flow validate <key> Validate a flow
4184
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
+
4185
4499
  Webhook Relay:
4186
4500
  one relay create Create a relay endpoint for a connection
4187
4501
  one relay list List relay endpoints
@@ -4241,11 +4555,11 @@ program.command("platforms").alias("p").description("List available platforms").
4241
4555
  await platformsCommand(options);
4242
4556
  });
4243
4557
  var actions = program.command("actions").alias("a").description("Search, explore, and execute platform actions (workflow: search \u2192 knowledge \u2192 execute)");
4244
- 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) => {
4245
4559
  await actionsSearchCommand(platform, query, options);
4246
4560
  });
4247
- 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) => {
4248
- 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);
4249
4563
  });
4250
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) => {
4251
4565
  await actionsExecuteCommand(platform, actionId, connectionKey, {
@@ -4311,6 +4625,16 @@ relay.command("deliveries").description("List delivery attempts for an endpoint
4311
4625
  relay.command("event-types <platform>").description("List supported webhook event types for a platform").action(async (platform) => {
4312
4626
  await relayEventTypesCommand(platform);
4313
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
+ });
4314
4638
  program.command("guide [topic]").description("Full CLI usage guide for agents (topics: overview, actions, flows, relay, all)").action(async (topic) => {
4315
4639
  await guideCommand(topic);
4316
4640
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@withone/cli",
3
- "version": "1.17.1",
3
+ "version": "1.18.0",
4
4
  "description": "CLI for managing One",
5
5
  "type": "module",
6
6
  "files": [
@@ -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: