@withone/cli 1.17.2 → 1.19.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
 
@@ -10,7 +10,6 @@ import { exec } from "child_process";
10
10
  import { promisify } from "util";
11
11
 
12
12
  // src/lib/api.ts
13
- var API_BASE = "https://api.withone.ai/v1";
14
13
  var ApiError = class extends Error {
15
14
  constructor(status, message) {
16
15
  super(message);
@@ -19,14 +18,16 @@ var ApiError = class extends Error {
19
18
  }
20
19
  };
21
20
  var OneApi = class {
22
- constructor(apiKey) {
21
+ constructor(apiKey, apiBase) {
23
22
  this.apiKey = apiKey;
23
+ this.apiBase = apiBase ?? "https://api.withone.ai/v1";
24
24
  }
25
+ apiBase;
25
26
  async request(path3) {
26
27
  return this.requestFull({ path: path3 });
27
28
  }
28
29
  async requestFull(opts) {
29
- let url = `${API_BASE}${opts.path}`;
30
+ let url = `${this.apiBase}${opts.path}`;
30
31
  if (opts.queryParams && Object.keys(opts.queryParams).length > 0) {
31
32
  const params = new URLSearchParams(opts.queryParams);
32
33
  url += `?${params.toString()}`;
@@ -128,6 +129,81 @@ var OneApi = class {
128
129
  method: action.method
129
130
  };
130
131
  }
132
+ async requestWithMeta(opts) {
133
+ let url = `${this.apiBase}${opts.path}`;
134
+ if (opts.queryParams && Object.keys(opts.queryParams).length > 0) {
135
+ const params = new URLSearchParams(opts.queryParams);
136
+ url += `?${params.toString()}`;
137
+ }
138
+ const headers = {
139
+ "x-one-secret": this.apiKey,
140
+ "Content-Type": "application/json",
141
+ ...opts.headers
142
+ };
143
+ if (opts.ifNoneMatch) {
144
+ headers["If-None-Match"] = opts.ifNoneMatch;
145
+ }
146
+ const fetchOpts = {
147
+ method: opts.method || "GET",
148
+ headers
149
+ };
150
+ if (opts.body !== void 0) {
151
+ fetchOpts.body = JSON.stringify(opts.body);
152
+ }
153
+ const response = await fetch(url, fetchOpts);
154
+ if (response.status === 304) {
155
+ return { data: null, etag: opts.ifNoneMatch ?? null, status: 304 };
156
+ }
157
+ if (!response.ok) {
158
+ const text2 = await response.text();
159
+ throw new ApiError(response.status, text2 || `HTTP ${response.status}`);
160
+ }
161
+ const etag = response.headers.get("etag") ?? null;
162
+ const text = await response.text();
163
+ const data = text ? JSON.parse(text) : {};
164
+ return { data, etag, status: response.status };
165
+ }
166
+ async getActionKnowledgeWithMeta(actionId, ifNoneMatch) {
167
+ const result = await this.requestWithMeta({
168
+ path: "/knowledge",
169
+ queryParams: { _id: actionId },
170
+ ifNoneMatch
171
+ });
172
+ if (result.status === 304) {
173
+ return { data: null, etag: result.etag, status: 304 };
174
+ }
175
+ const actions = result.data?.rows || [];
176
+ if (actions.length === 0) {
177
+ throw new ApiError(404, `Action with ID ${actionId} not found`);
178
+ }
179
+ const action = actions[0];
180
+ const knowledge = {
181
+ knowledge: action.knowledge || "No knowledge was found",
182
+ method: action.method || "No method was found"
183
+ };
184
+ return { data: knowledge, etag: result.etag, status: result.status };
185
+ }
186
+ async searchActionsWithMeta(platform, query, agentType, ifNoneMatch) {
187
+ const isKnowledgeAgent = !agentType || agentType === "knowledge";
188
+ const queryParams = {
189
+ query,
190
+ limit: "5"
191
+ };
192
+ if (isKnowledgeAgent) {
193
+ queryParams.knowledgeAgent = "true";
194
+ } else {
195
+ queryParams.executeAgent = "true";
196
+ }
197
+ const result = await this.requestWithMeta({
198
+ path: `/available-actions/search/${platform}`,
199
+ queryParams,
200
+ ifNoneMatch
201
+ });
202
+ if (result.status === 304) {
203
+ return { data: null, etag: result.etag, status: 304 };
204
+ }
205
+ return { data: result.data || [], etag: result.etag, status: result.status };
206
+ }
131
207
  async executePassthroughRequest(args, preloadedAction) {
132
208
  const action = preloadedAction ?? await this.getActionDetails(args.actionId);
133
209
  const method = action.method;
@@ -141,7 +217,7 @@ var OneApi = class {
141
217
  };
142
218
  const finalActionPath = args.pathVariables ? replacePathVariables(action.path, args.pathVariables) : action.path;
143
219
  const normalizedPath = finalActionPath.startsWith("/") ? finalActionPath : `/${finalActionPath}`;
144
- const url = `${API_BASE.replace("/v1", "")}/v1/passthrough${normalizedPath}`;
220
+ const url = `${this.apiBase.replace("/v1", "")}/v1/passthrough${normalizedPath}`;
145
221
  const isCustomAction = action.tags?.includes("custom");
146
222
  let requestData = args.data;
147
223
  if (isCustomAction && method?.toLowerCase() !== "get") {
@@ -703,7 +779,7 @@ async function executeSubflowStep(step, context, api, permissions, allowedAction
703
779
  if (flowStack.includes(resolvedKey)) {
704
780
  throw new Error(`Circular flow detected: ${[...flowStack, resolvedKey].join(" \u2192 ")}`);
705
781
  }
706
- const { loadFlow: loadFlow2 } = await import("./flow-runner-AJGIUT6E.js");
782
+ const { loadFlow: loadFlow2 } = await import("./flow-runner-SHV6JPE6.js");
707
783
  const subFlow = loadFlow2(resolvedKey);
708
784
  const subContext = await executeFlow(
709
785
  subFlow,
@@ -4,7 +4,7 @@ import {
4
4
  loadFlow,
5
5
  resolveFlowPath,
6
6
  saveFlow
7
- } from "./chunk-QW4MBV4F.js";
7
+ } from "./chunk-SIZK6EAM.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-SIZK6EAM.js";
15
15
 
16
16
  // src/index.ts
17
17
  import { createRequire as createRequire2 } from "module";
@@ -101,6 +101,31 @@ function getAccessControlFromAllSources() {
101
101
  function getAccessControl() {
102
102
  return readConfig()?.accessControl ?? {};
103
103
  }
104
+ var DEFAULT_API_BASE = "https://api.withone.ai/v1";
105
+ function getApiBase() {
106
+ const config = readConfig();
107
+ if (config?.apiBase) return `${config.apiBase}/v1`;
108
+ return DEFAULT_API_BASE;
109
+ }
110
+ function updateApiBase(url) {
111
+ const config = readConfig();
112
+ if (!config) return;
113
+ if (url) {
114
+ config.apiBase = url;
115
+ } else {
116
+ delete config.apiBase;
117
+ }
118
+ writeConfig(config);
119
+ }
120
+ function getCacheTtl() {
121
+ if (process.env.ONE_CACHE_TTL) {
122
+ const val = parseInt(process.env.ONE_CACHE_TTL, 10);
123
+ if (!isNaN(val) && val > 0) return val;
124
+ }
125
+ const config = readConfig();
126
+ if (config?.cacheTtl && config.cacheTtl > 0) return config.cacheTtl;
127
+ return 3600;
128
+ }
104
129
  function updateAccessControl(settings) {
105
130
  const config = readConfig();
106
131
  if (!config) return;
@@ -456,6 +481,113 @@ async function configCommand() {
456
481
  p2.outro("No changes made.");
457
482
  return;
458
483
  }
484
+ const currentBase = getApiBase();
485
+ const isCustomBase = !!readConfig()?.apiBase;
486
+ const baseUrlMode = await p2.select({
487
+ message: "API base URL",
488
+ options: [
489
+ { value: "default", label: "Default", hint: "https://api.withone.ai" },
490
+ { value: "custom", label: "Custom", hint: "Use a different API endpoint" }
491
+ ],
492
+ initialValue: isCustomBase ? "custom" : "default"
493
+ });
494
+ if (p2.isCancel(baseUrlMode)) {
495
+ p2.outro("No changes made.");
496
+ return;
497
+ }
498
+ let newApiKey = config.apiKey;
499
+ if (baseUrlMode === "custom") {
500
+ const customUrl = await p2.text({
501
+ message: "Enter API base URL:",
502
+ placeholder: "https://development-api.withone.ai",
503
+ initialValue: isCustomBase ? currentBase.replace(/\/v1$/, "") : "",
504
+ validate: (value) => {
505
+ if (!value) return "URL is required";
506
+ try {
507
+ new URL(value);
508
+ } catch {
509
+ return "Invalid URL";
510
+ }
511
+ return void 0;
512
+ }
513
+ });
514
+ if (p2.isCancel(customUrl)) {
515
+ p2.outro("No changes made.");
516
+ return;
517
+ }
518
+ const normalized = customUrl.replace(/\/+$/, "").replace(/\/v1$/, "");
519
+ const apiKey = await p2.text({
520
+ message: `Enter your API key for ${pc.cyan(normalized)}:`,
521
+ placeholder: "sk_live_...",
522
+ validate: (value) => {
523
+ if (!value) return "API key is required";
524
+ if (!value.startsWith("sk_live_") && !value.startsWith("sk_test_")) {
525
+ return "API key should start with sk_live_ or sk_test_";
526
+ }
527
+ return void 0;
528
+ }
529
+ });
530
+ if (p2.isCancel(apiKey)) {
531
+ p2.outro("No changes made.");
532
+ return;
533
+ }
534
+ const spinner5 = p2.spinner();
535
+ spinner5.start("Validating API key...");
536
+ let isValid = false;
537
+ try {
538
+ const api = new OneApi(apiKey, `${normalized}/v1`);
539
+ isValid = await api.validateApiKey();
540
+ } catch (err) {
541
+ spinner5.stop("Connection failed");
542
+ const msg = err instanceof Error ? err.message : String(err);
543
+ p2.log.error(`Could not reach ${pc.cyan(normalized)}: ${msg}`);
544
+ return;
545
+ }
546
+ if (!isValid) {
547
+ spinner5.stop("Invalid API key");
548
+ p2.log.error(`Invalid API key for ${pc.cyan(normalized)}.`);
549
+ return;
550
+ }
551
+ spinner5.stop("API key validated");
552
+ updateApiBase(normalized);
553
+ newApiKey = apiKey;
554
+ } else if (isCustomBase) {
555
+ const apiKey = await p2.text({
556
+ message: `Enter your API key for ${pc.cyan("https://api.withone.ai")}:`,
557
+ placeholder: "sk_live_...",
558
+ validate: (value) => {
559
+ if (!value) return "API key is required";
560
+ if (!value.startsWith("sk_live_") && !value.startsWith("sk_test_")) {
561
+ return "API key should start with sk_live_ or sk_test_";
562
+ }
563
+ return void 0;
564
+ }
565
+ });
566
+ if (p2.isCancel(apiKey)) {
567
+ p2.outro("No changes made.");
568
+ return;
569
+ }
570
+ const spinner5 = p2.spinner();
571
+ spinner5.start("Validating API key...");
572
+ let isValid = false;
573
+ try {
574
+ const api = new OneApi(apiKey, "https://api.withone.ai/v1");
575
+ isValid = await api.validateApiKey();
576
+ } catch (err) {
577
+ spinner5.stop("Connection failed");
578
+ const msg = err instanceof Error ? err.message : String(err);
579
+ p2.log.error(`Could not reach ${pc.cyan("https://api.withone.ai")}: ${msg}`);
580
+ return;
581
+ }
582
+ if (!isValid) {
583
+ spinner5.stop("Invalid API key");
584
+ p2.log.error(`Invalid API key. Get a valid key at ${getApiKeyUrl()}`);
585
+ return;
586
+ }
587
+ spinner5.stop("API key validated");
588
+ updateApiBase(null);
589
+ newApiKey = apiKey;
590
+ }
459
591
  const settings = {
460
592
  permissions,
461
593
  connectionKeys: connectionKeys ?? ["*"],
@@ -463,30 +595,35 @@ async function configCommand() {
463
595
  knowledgeAgent
464
596
  };
465
597
  updateAccessControl(settings);
598
+ const updatedConfig = readConfig();
599
+ if (updatedConfig && newApiKey !== config.apiKey) {
600
+ updatedConfig.apiKey = newApiKey;
601
+ writeConfig(updatedConfig);
602
+ }
466
603
  const ac = getAccessControl();
467
604
  const statuses = getAgentStatuses();
468
605
  const reinstalled = [];
469
606
  for (const s of statuses) {
470
607
  if (s.globalMcp) {
471
- installMcpConfig(s.agent, config.apiKey, "global", ac);
608
+ installMcpConfig(s.agent, newApiKey, "global", ac);
472
609
  reinstalled.push(`${s.agent.name} (global)`);
473
610
  }
474
611
  if (s.projectMcp) {
475
- installMcpConfig(s.agent, config.apiKey, "project", ac);
612
+ installMcpConfig(s.agent, newApiKey, "project", ac);
476
613
  reinstalled.push(`${s.agent.name} (project)`);
477
614
  }
478
615
  }
479
616
  if (reinstalled.length > 0) {
480
617
  p2.log.success(`Updated MCP configs: ${reinstalled.join(", ")}`);
481
618
  }
482
- p2.outro("Access control updated.");
619
+ p2.outro("Configuration updated.");
483
620
  }
484
621
  async function selectConnections(apiKey) {
485
622
  const spinner5 = p2.spinner();
486
623
  spinner5.start("Fetching connections...");
487
624
  let connections;
488
625
  try {
489
- const api = new OneApi(apiKey);
626
+ const api = new OneApi(apiKey, getApiBase());
490
627
  const rawConnections = await api.listConnections();
491
628
  connections = rawConnections.map((c) => ({ platform: c.platform, key: c.key }));
492
629
  spinner5.stop(`Found ${connections.length} connection(s)`);
@@ -656,7 +793,7 @@ ${pc2.cyan(getApiKeyUrl())}`, "API Key");
656
793
  }
657
794
  const spinner5 = p3.spinner();
658
795
  spinner5.start("Validating API key...");
659
- const api = new OneApi(newKey);
796
+ const api = new OneApi(newKey, getApiBase());
660
797
  const isValid = await api.validateApiKey();
661
798
  if (!isValid) {
662
799
  spinner5.stop("Invalid API key");
@@ -875,7 +1012,7 @@ ${pc2.cyan(getApiKeyUrl())}`, "API Key");
875
1012
  }
876
1013
  const spinner5 = p3.spinner();
877
1014
  spinner5.start("Validating API key...");
878
- const api = new OneApi(apiKey);
1015
+ const api = new OneApi(apiKey, getApiBase());
879
1016
  const isValid = await api.validateApiKey();
880
1017
  if (!isValid) {
881
1018
  spinner5.stop("Invalid API key");
@@ -919,7 +1056,7 @@ var TOP_INTEGRATIONS = [
919
1056
  { value: "notion", label: "Notion", hint: "Access pages, databases, and docs" }
920
1057
  ];
921
1058
  async function promptConnectIntegrations(apiKey) {
922
- const api = new OneApi(apiKey);
1059
+ const api = new OneApi(apiKey, getApiBase());
923
1060
  const connected = [];
924
1061
  try {
925
1062
  const existing = await api.listConnections();
@@ -1081,7 +1218,7 @@ async function connectionAddCommand(platformArg) {
1081
1218
  p4.cancel("Not configured. Run `one init` first.");
1082
1219
  process.exit(1);
1083
1220
  }
1084
- const api = new OneApi(apiKey);
1221
+ const api = new OneApi(apiKey, getApiBase());
1085
1222
  const spinner5 = p4.spinner();
1086
1223
  spinner5.start("Loading platforms...");
1087
1224
  let platforms;
@@ -1185,7 +1322,7 @@ async function connectionListCommand(options) {
1185
1322
  if (!apiKey) {
1186
1323
  error("Not configured. Run `one init` first.");
1187
1324
  }
1188
- const api = new OneApi(apiKey);
1325
+ const api = new OneApi(apiKey, getApiBase());
1189
1326
  const spinner5 = createSpinner();
1190
1327
  spinner5.start("Loading connections...");
1191
1328
  try {
@@ -1281,7 +1418,7 @@ async function platformsCommand(options) {
1281
1418
  if (isAgentMode()) {
1282
1419
  options.json = true;
1283
1420
  }
1284
- const api = new OneApi(apiKey);
1421
+ const api = new OneApi(apiKey, getApiBase());
1285
1422
  const spinner5 = createSpinner();
1286
1423
  spinner5.start("Loading platforms...");
1287
1424
  try {
@@ -1350,6 +1487,123 @@ async function platformsCommand(options) {
1350
1487
  // src/commands/actions.ts
1351
1488
  import * as p6 from "@clack/prompts";
1352
1489
  import pc6 from "picocolors";
1490
+
1491
+ // src/lib/cache.ts
1492
+ import fs4 from "fs";
1493
+ import path4 from "path";
1494
+ import os4 from "os";
1495
+ var CACHE_BASE = path4.join(os4.homedir(), ".one", "cache");
1496
+ var KNOWLEDGE_DIR = path4.join(CACHE_BASE, "knowledge");
1497
+ var SEARCH_DIR = path4.join(CACHE_BASE, "search");
1498
+ function sanitizeFilename(input) {
1499
+ return input.replace(/[^a-zA-Z0-9_\-\.]/g, "_");
1500
+ }
1501
+ function knowledgeCachePath(actionId) {
1502
+ return path4.join(KNOWLEDGE_DIR, `${sanitizeFilename(actionId)}.json`);
1503
+ }
1504
+ function searchCachePath(platform, query, type) {
1505
+ const key = `${platform}_${sanitizeFilename(query)}_${type || "knowledge"}`;
1506
+ return path4.join(SEARCH_DIR, `${key}.json`);
1507
+ }
1508
+ function readCache(filePath) {
1509
+ try {
1510
+ const content = fs4.readFileSync(filePath, "utf-8");
1511
+ return JSON.parse(content);
1512
+ } catch {
1513
+ return null;
1514
+ }
1515
+ }
1516
+ function writeCache(filePath, entry) {
1517
+ try {
1518
+ const dir = path4.dirname(filePath);
1519
+ fs4.mkdirSync(dir, { recursive: true });
1520
+ fs4.writeFileSync(filePath, JSON.stringify(entry, null, 2));
1521
+ } catch {
1522
+ }
1523
+ }
1524
+ function isFresh(entry) {
1525
+ const cachedTime = new Date(entry.cachedAt).getTime();
1526
+ const now = Date.now();
1527
+ return now - cachedTime < entry.ttl * 1e3;
1528
+ }
1529
+ function getAge(entry) {
1530
+ return Math.floor((Date.now() - new Date(entry.cachedAt).getTime()) / 1e3);
1531
+ }
1532
+ function buildCacheMeta(entry, hit) {
1533
+ if (!entry) {
1534
+ return { hit: false, age: 0, fresh: false };
1535
+ }
1536
+ return {
1537
+ hit,
1538
+ age: getAge(entry),
1539
+ fresh: isFresh(entry)
1540
+ };
1541
+ }
1542
+ function formatAge(seconds) {
1543
+ if (seconds < 60) return `${seconds}s`;
1544
+ if (seconds < 3600) return `${Math.floor(seconds / 60)}m`;
1545
+ if (seconds < 86400) {
1546
+ const h2 = Math.floor(seconds / 3600);
1547
+ const m = Math.floor(seconds % 3600 / 60);
1548
+ return m > 0 ? `${h2}h ${m}m` : `${h2}h`;
1549
+ }
1550
+ const d = Math.floor(seconds / 86400);
1551
+ const h = Math.floor(seconds % 86400 / 3600);
1552
+ return h > 0 ? `${d}d ${h}h` : `${d}d`;
1553
+ }
1554
+ function listCacheEntries() {
1555
+ const entries = [];
1556
+ for (const [dir, type] of [[KNOWLEDGE_DIR, "knowledge"], [SEARCH_DIR, "search"]]) {
1557
+ try {
1558
+ const files = fs4.readdirSync(dir);
1559
+ for (const file of files) {
1560
+ if (!file.endsWith(".json")) continue;
1561
+ const filePath = path4.join(dir, file);
1562
+ const entry = readCache(filePath);
1563
+ if (entry) {
1564
+ entries.push({ type, filePath, entry });
1565
+ }
1566
+ }
1567
+ } catch {
1568
+ }
1569
+ }
1570
+ return entries;
1571
+ }
1572
+ function clearAll() {
1573
+ let count = 0;
1574
+ for (const dir of [KNOWLEDGE_DIR, SEARCH_DIR]) {
1575
+ try {
1576
+ const files = fs4.readdirSync(dir);
1577
+ for (const file of files) {
1578
+ fs4.unlinkSync(path4.join(dir, file));
1579
+ count++;
1580
+ }
1581
+ fs4.rmdirSync(dir);
1582
+ } catch {
1583
+ }
1584
+ }
1585
+ return count;
1586
+ }
1587
+ function clearEntry(actionId) {
1588
+ const filePath = knowledgeCachePath(actionId);
1589
+ try {
1590
+ fs4.unlinkSync(filePath);
1591
+ return true;
1592
+ } catch {
1593
+ return false;
1594
+ }
1595
+ }
1596
+ function makeCacheEntry(key, data, etag) {
1597
+ return {
1598
+ key,
1599
+ etag,
1600
+ cachedAt: (/* @__PURE__ */ new Date()).toISOString(),
1601
+ ttl: getCacheTtl(),
1602
+ data
1603
+ };
1604
+ }
1605
+
1606
+ // src/commands/actions.ts
1353
1607
  function getConfig() {
1354
1608
  const apiKey = getApiKey();
1355
1609
  if (!apiKey) {
@@ -1372,22 +1626,70 @@ function parseJsonArg(value, argName) {
1372
1626
  async function actionsSearchCommand(platform, query, options) {
1373
1627
  intro2(pc6.bgCyan(pc6.black(" One ")));
1374
1628
  const { apiKey, permissions, actionIds, knowledgeAgent } = getConfig();
1375
- const api = new OneApi(apiKey);
1629
+ const api = new OneApi(apiKey, getApiBase());
1376
1630
  const spinner5 = createSpinner();
1377
1631
  spinner5.start(`Searching actions on ${pc6.cyan(platform)} for "${query}"...`);
1378
1632
  try {
1379
1633
  const agentType = knowledgeAgent ? "knowledge" : options.type;
1380
- let actions2 = await api.searchActions(platform, query, agentType);
1381
- actions2 = filterByPermissions(actions2, permissions);
1382
- actions2 = actions2.filter((a) => isActionAllowed(a.systemId, actionIds));
1383
- const cleanedActions = actions2.map((action) => ({
1384
- actionId: action.systemId,
1385
- title: action.title,
1386
- method: action.method,
1387
- path: action.path
1388
- }));
1634
+ const useCache = options.cache !== false;
1635
+ const cachePath = searchCachePath(platform, query, agentType || "knowledge");
1636
+ const cached = useCache ? readCache(cachePath) : null;
1637
+ let cleanedActions;
1638
+ let cacheHit = false;
1639
+ if (cached && isFresh(cached)) {
1640
+ cleanedActions = cached.data.actions;
1641
+ cacheHit = true;
1642
+ } else {
1643
+ try {
1644
+ const result = await api.searchActionsWithMeta(
1645
+ platform,
1646
+ query,
1647
+ agentType,
1648
+ cached?.etag ?? void 0
1649
+ );
1650
+ if (result.status === 304 && cached) {
1651
+ cached.cachedAt = (/* @__PURE__ */ new Date()).toISOString();
1652
+ writeCache(cachePath, cached);
1653
+ cleanedActions = cached.data.actions;
1654
+ cacheHit = true;
1655
+ } else {
1656
+ let actions2 = result.data;
1657
+ actions2 = filterByPermissions(actions2, permissions);
1658
+ actions2 = actions2.filter((a) => isActionAllowed(a.systemId, actionIds));
1659
+ cleanedActions = actions2.map((action) => ({
1660
+ actionId: action.systemId,
1661
+ title: action.title,
1662
+ method: action.method,
1663
+ path: action.path
1664
+ }));
1665
+ writeCache(cachePath, makeCacheEntry(
1666
+ `${platform}_${query}_${agentType || "knowledge"}`,
1667
+ { actions: cleanedActions },
1668
+ result.etag
1669
+ ));
1670
+ }
1671
+ } catch (fetchError) {
1672
+ if (cached) {
1673
+ process.stderr.write(
1674
+ `Warning: serving cached search results (network unavailable, cached ${formatAge(getAge(cached))} ago)
1675
+ `
1676
+ );
1677
+ cleanedActions = cached.data.actions;
1678
+ cacheHit = true;
1679
+ } else {
1680
+ throw fetchError;
1681
+ }
1682
+ }
1683
+ }
1389
1684
  if (isAgentMode()) {
1390
- json({ actions: cleanedActions });
1685
+ const response = { actions: cleanedActions };
1686
+ if (cacheHit && cached) {
1687
+ response._cache = buildCacheMeta(cached, true);
1688
+ } else {
1689
+ const freshEntry = readCache(cachePath);
1690
+ response._cache = buildCacheMeta(freshEntry, false);
1691
+ }
1692
+ json(response);
1391
1693
  return;
1392
1694
  }
1393
1695
  if (cleanedActions.length === 0) {
@@ -1441,10 +1743,32 @@ Execute: ${pc6.cyan(`one actions execute ${platform} <actionId> <connectionK
1441
1743
  );
1442
1744
  }
1443
1745
  }
1444
- async function actionsKnowledgeCommand(platform, actionId) {
1746
+ async function actionsKnowledgeCommand(platform, actionId, options) {
1747
+ const cachePath = knowledgeCachePath(actionId);
1748
+ if (options.cacheStatus) {
1749
+ const entry = readCache(cachePath);
1750
+ if (!entry) {
1751
+ json({
1752
+ cached: false,
1753
+ path: cachePath
1754
+ });
1755
+ } else {
1756
+ const age = getAge(entry);
1757
+ json({
1758
+ cached: true,
1759
+ cachedAt: entry.cachedAt,
1760
+ age: formatAge(age),
1761
+ ttl: entry.ttl,
1762
+ expired: !isFresh(entry),
1763
+ etag: entry.etag,
1764
+ path: cachePath
1765
+ });
1766
+ }
1767
+ return;
1768
+ }
1445
1769
  intro2(pc6.bgCyan(pc6.black(" One ")));
1446
1770
  const { apiKey, actionIds, connectionKeys } = getConfig();
1447
- const api = new OneApi(apiKey);
1771
+ const api = new OneApi(apiKey, getApiBase());
1448
1772
  if (!isActionAllowed(actionId, actionIds)) {
1449
1773
  error(`Action "${actionId}" is not in the allowed action list.`);
1450
1774
  }
@@ -1469,15 +1793,57 @@ async function actionsKnowledgeCommand(platform, actionId) {
1469
1793
  const spinner5 = createSpinner();
1470
1794
  spinner5.start(`Loading knowledge for action ${pc6.dim(actionId)}...`);
1471
1795
  try {
1472
- const { knowledge, method } = await api.getActionKnowledge(actionId);
1796
+ const useCache = options.cache !== false;
1797
+ const cached = useCache ? readCache(cachePath) : null;
1798
+ let knowledgeData;
1799
+ let cacheHit = false;
1800
+ let cacheEntry = cached;
1801
+ if (cached && isFresh(cached) && useCache) {
1802
+ knowledgeData = cached.data;
1803
+ cacheHit = true;
1804
+ } else {
1805
+ try {
1806
+ const result = await api.getActionKnowledgeWithMeta(
1807
+ actionId,
1808
+ cached?.etag ?? void 0
1809
+ );
1810
+ if (result.status === 304 && cached) {
1811
+ cached.cachedAt = (/* @__PURE__ */ new Date()).toISOString();
1812
+ writeCache(cachePath, cached);
1813
+ knowledgeData = cached.data;
1814
+ cacheHit = true;
1815
+ } else {
1816
+ knowledgeData = result.data;
1817
+ const newEntry = makeCacheEntry(actionId, knowledgeData, result.etag);
1818
+ writeCache(cachePath, newEntry);
1819
+ cacheEntry = newEntry;
1820
+ }
1821
+ } catch (fetchError) {
1822
+ if (cached) {
1823
+ process.stderr.write(
1824
+ `Warning: serving cached knowledge (network unavailable, cached ${formatAge(getAge(cached))} ago)
1825
+ `
1826
+ );
1827
+ knowledgeData = cached.data;
1828
+ cacheHit = true;
1829
+ } else {
1830
+ throw fetchError;
1831
+ }
1832
+ }
1833
+ }
1473
1834
  const knowledgeWithGuidance = buildActionKnowledgeWithGuidance(
1474
- knowledge,
1475
- method,
1835
+ knowledgeData.knowledge,
1836
+ knowledgeData.method,
1476
1837
  platform,
1477
1838
  actionId
1478
1839
  );
1479
1840
  if (isAgentMode()) {
1480
- json({ knowledge: knowledgeWithGuidance, method });
1841
+ const response = {
1842
+ knowledge: knowledgeWithGuidance,
1843
+ method: knowledgeData.method,
1844
+ _cache: buildCacheMeta(cacheEntry, cacheHit)
1845
+ };
1846
+ json(response);
1481
1847
  return;
1482
1848
  }
1483
1849
  spinner5.stop("Knowledge loaded");
@@ -1509,7 +1875,7 @@ async function actionsExecuteCommand(platform, actionId, connectionKey, options)
1509
1875
  if (!connectionKeys.includes("*") && !connectionKeys.includes(connectionKey)) {
1510
1876
  error(`Connection key "${connectionKey}" is not allowed.`);
1511
1877
  }
1512
- const api = new OneApi(apiKey);
1878
+ const api = new OneApi(apiKey, getApiBase());
1513
1879
  const spinner5 = createSpinner();
1514
1880
  spinner5.start("Loading action details...");
1515
1881
  try {
@@ -2197,26 +2563,26 @@ function validateStepsArray(steps, pathPrefix, errors) {
2197
2563
  const validTypes = FLOW_SCHEMA.stepTypes.map((st) => st.type);
2198
2564
  for (let i = 0; i < steps.length; i++) {
2199
2565
  const step = steps[i];
2200
- const path4 = `${pathPrefix}[${i}]`;
2566
+ const path5 = `${pathPrefix}[${i}]`;
2201
2567
  if (!step || typeof step !== "object" || Array.isArray(step)) {
2202
- errors.push({ path: path4, message: "Step must be an object" });
2568
+ errors.push({ path: path5, message: "Step must be an object" });
2203
2569
  continue;
2204
2570
  }
2205
2571
  const s = step;
2206
2572
  if (!s.id || typeof s.id !== "string") {
2207
- errors.push({ path: `${path4}.id`, message: 'Step must have a string "id"' });
2573
+ errors.push({ path: `${path5}.id`, message: 'Step must have a string "id"' });
2208
2574
  }
2209
2575
  if (!s.name || typeof s.name !== "string") {
2210
- errors.push({ path: `${path4}.name`, message: 'Step must have a string "name"' });
2576
+ errors.push({ path: `${path5}.name`, message: 'Step must have a string "name"' });
2211
2577
  }
2212
2578
  if (!s.type || !validTypes.includes(s.type)) {
2213
- errors.push({ path: `${path4}.type`, message: `Step type must be one of: ${validTypes.join(", ")}` });
2579
+ errors.push({ path: `${path5}.type`, message: `Step type must be one of: ${validTypes.join(", ")}` });
2214
2580
  continue;
2215
2581
  }
2216
2582
  if (s.onError && typeof s.onError === "object") {
2217
2583
  const oe = s.onError;
2218
2584
  if (!FLOW_SCHEMA.errorStrategies.includes(oe.strategy)) {
2219
- errors.push({ path: `${path4}.onError.strategy`, message: `Error strategy must be one of: ${FLOW_SCHEMA.errorStrategies.join(", ")}` });
2585
+ errors.push({ path: `${path5}.onError.strategy`, message: `Error strategy must be one of: ${FLOW_SCHEMA.errorStrategies.join(", ")}` });
2220
2586
  }
2221
2587
  }
2222
2588
  const descriptor = getStepTypeDescriptor(s.type);
@@ -2226,14 +2592,14 @@ function validateStepsArray(steps, pathPrefix, errors) {
2226
2592
  if (!configObj || typeof configObj !== "object") {
2227
2593
  const hint = detectFlatConfigHint(s, descriptor);
2228
2594
  errors.push({
2229
- path: `${path4}.${configKey}`,
2595
+ path: `${path5}.${configKey}`,
2230
2596
  message: `${capitalize(descriptor.type)} step must have a "${configKey}" config object${hint}`
2231
2597
  });
2232
2598
  continue;
2233
2599
  }
2234
2600
  const config = configObj;
2235
2601
  for (const [fieldName, fd] of Object.entries(descriptor.fields)) {
2236
- const fieldPath = `${path4}.${configKey}.${fieldName}`;
2602
+ const fieldPath = `${path5}.${configKey}.${fieldName}`;
2237
2603
  const value = config[fieldName];
2238
2604
  if (fd.required && (value === void 0 || value === null || value === "")) {
2239
2605
  errors.push({ path: fieldPath, message: `${capitalize(descriptor.type)} must have ${fd.type === "string" ? "a string" : fd.type === "array" ? "a" : "a"} "${fieldName}"` });
@@ -2285,16 +2651,16 @@ function validateStepIds(flow2) {
2285
2651
  function collectIds(steps, pathPrefix) {
2286
2652
  for (let i = 0; i < steps.length; i++) {
2287
2653
  const step = steps[i];
2288
- const path4 = `${pathPrefix}[${i}]`;
2654
+ const path5 = `${pathPrefix}[${i}]`;
2289
2655
  if (seen.has(step.id)) {
2290
- errors.push({ path: `${path4}.id`, message: `Duplicate step ID: "${step.id}"` });
2656
+ errors.push({ path: `${path5}.id`, message: `Duplicate step ID: "${step.id}"` });
2291
2657
  } else {
2292
2658
  seen.add(step.id);
2293
2659
  }
2294
2660
  for (const { configKey, fieldName } of nestedKeys) {
2295
2661
  const config = step[configKey];
2296
2662
  if (config && Array.isArray(config[fieldName])) {
2297
- collectIds(config[fieldName], `${path4}.${configKey}.${fieldName}`);
2663
+ collectIds(config[fieldName], `${path5}.${configKey}.${fieldName}`);
2298
2664
  }
2299
2665
  }
2300
2666
  }
@@ -2341,7 +2707,7 @@ function validateSelectorReferences(flow2) {
2341
2707
  }
2342
2708
  return selectors;
2343
2709
  }
2344
- function checkSelectors(selectors, path4) {
2710
+ function checkSelectors(selectors, path5) {
2345
2711
  for (const selector of selectors) {
2346
2712
  const parts = selector.split(".");
2347
2713
  if (parts.length < 3) continue;
@@ -2349,12 +2715,12 @@ function validateSelectorReferences(flow2) {
2349
2715
  if (root === "input") {
2350
2716
  const inputName = parts[2];
2351
2717
  if (!inputNames.has(inputName)) {
2352
- errors.push({ path: path4, message: `Selector "${selector}" references undefined input "${inputName}"` });
2718
+ errors.push({ path: path5, message: `Selector "${selector}" references undefined input "${inputName}"` });
2353
2719
  }
2354
2720
  } else if (root === "steps") {
2355
2721
  const stepId = parts[2];
2356
2722
  if (!allStepIds.has(stepId)) {
2357
- errors.push({ path: path4, message: `Selector "${selector}" references undefined step "${stepId}"` });
2723
+ errors.push({ path: path5, message: `Selector "${selector}" references undefined step "${stepId}"` });
2358
2724
  }
2359
2725
  }
2360
2726
  }
@@ -2402,7 +2768,7 @@ function validateFlow(flow2) {
2402
2768
  }
2403
2769
 
2404
2770
  // src/commands/flow.ts
2405
- import fs4 from "fs";
2771
+ import fs5 from "fs";
2406
2772
  function getConfig2() {
2407
2773
  const apiKey = getApiKey();
2408
2774
  if (!apiKey) {
@@ -2460,7 +2826,7 @@ async function flowCreateCommand(key, options) {
2460
2826
  if (raw.startsWith("@")) {
2461
2827
  const filePath = raw.slice(1);
2462
2828
  try {
2463
- raw = fs4.readFileSync(filePath, "utf-8");
2829
+ raw = fs5.readFileSync(filePath, "utf-8");
2464
2830
  } catch (err) {
2465
2831
  error(`Cannot read file "${filePath}": ${err.message}`);
2466
2832
  }
@@ -2508,7 +2874,7 @@ Execute: ${pc7.cyan(`one flow execute ${flow2.key}`)}`);
2508
2874
  async function flowExecuteCommand(keyOrPath, options) {
2509
2875
  intro2(pc7.bgCyan(pc7.black(" One Workflow ")));
2510
2876
  const { apiKey, permissions, actionIds } = getConfig2();
2511
- const api = new OneApi(apiKey);
2877
+ const api = new OneApi(apiKey, getApiBase());
2512
2878
  const spinner5 = createSpinner();
2513
2879
  spinner5.start(`Loading workflow "${keyOrPath}"...`);
2514
2880
  let flow2;
@@ -2643,7 +3009,7 @@ async function flowValidateCommand(keyOrPath) {
2643
3009
  let flowData;
2644
3010
  try {
2645
3011
  const flowPath = resolveFlowPath(keyOrPath);
2646
- const content = fs4.readFileSync(flowPath, "utf-8");
3012
+ const content = fs5.readFileSync(flowPath, "utf-8");
2647
3013
  flowData = JSON.parse(content);
2648
3014
  } catch (err) {
2649
3015
  spinner5.stop("Validation failed");
@@ -2680,7 +3046,7 @@ async function flowResumeCommand(runId) {
2680
3046
  error(`Run "${runId}" is ${state.status} \u2014 can only resume paused or failed runs`);
2681
3047
  }
2682
3048
  const { apiKey, permissions, actionIds } = getConfig2();
2683
- const api = new OneApi(apiKey);
3049
+ const api = new OneApi(apiKey, getApiBase());
2684
3050
  let flow2;
2685
3051
  try {
2686
3052
  flow2 = loadFlow(state.flowKey);
@@ -2998,7 +3364,7 @@ async function relayCreateCommand(options) {
2998
3364
  if (!connectionKeys.includes("*") && !connectionKeys.includes(options.connectionKey)) {
2999
3365
  error(`Connection key "${options.connectionKey}" is not allowed.`);
3000
3366
  }
3001
- const api = new OneApi(apiKey);
3367
+ const api = new OneApi(apiKey, getApiBase());
3002
3368
  const spinner5 = createSpinner();
3003
3369
  spinner5.start("Creating relay endpoint...");
3004
3370
  try {
@@ -3030,7 +3396,7 @@ async function relayCreateCommand(options) {
3030
3396
  }
3031
3397
  async function relayListCommand(options) {
3032
3398
  const { apiKey } = getConfig3();
3033
- const api = new OneApi(apiKey);
3399
+ const api = new OneApi(apiKey, getApiBase());
3034
3400
  const spinner5 = createSpinner();
3035
3401
  spinner5.start("Loading relay endpoints...");
3036
3402
  try {
@@ -3077,7 +3443,7 @@ async function relayListCommand(options) {
3077
3443
  }
3078
3444
  async function relayGetCommand(id) {
3079
3445
  const { apiKey } = getConfig3();
3080
- const api = new OneApi(apiKey);
3446
+ const api = new OneApi(apiKey, getApiBase());
3081
3447
  const spinner5 = createSpinner();
3082
3448
  spinner5.start("Loading relay endpoint...");
3083
3449
  try {
@@ -3108,7 +3474,7 @@ async function relayGetCommand(id) {
3108
3474
  }
3109
3475
  async function relayUpdateCommand(id, options) {
3110
3476
  const { apiKey } = getConfig3();
3111
- const api = new OneApi(apiKey);
3477
+ const api = new OneApi(apiKey, getApiBase());
3112
3478
  const spinner5 = createSpinner();
3113
3479
  spinner5.start("Updating relay endpoint...");
3114
3480
  try {
@@ -3135,7 +3501,7 @@ async function relayUpdateCommand(id, options) {
3135
3501
  }
3136
3502
  async function relayDeleteCommand(id) {
3137
3503
  const { apiKey } = getConfig3();
3138
- const api = new OneApi(apiKey);
3504
+ const api = new OneApi(apiKey, getApiBase());
3139
3505
  const spinner5 = createSpinner();
3140
3506
  spinner5.start("Deleting relay endpoint...");
3141
3507
  try {
@@ -3154,7 +3520,7 @@ async function relayDeleteCommand(id) {
3154
3520
  }
3155
3521
  async function relayActivateCommand(id, options) {
3156
3522
  const { apiKey } = getConfig3();
3157
- const api = new OneApi(apiKey);
3523
+ const api = new OneApi(apiKey, getApiBase());
3158
3524
  const spinner5 = createSpinner();
3159
3525
  spinner5.start("Activating relay endpoint...");
3160
3526
  try {
@@ -3178,7 +3544,7 @@ async function relayActivateCommand(id, options) {
3178
3544
  }
3179
3545
  async function relayEventsCommand(options) {
3180
3546
  const { apiKey } = getConfig3();
3181
- const api = new OneApi(apiKey);
3547
+ const api = new OneApi(apiKey, getApiBase());
3182
3548
  const spinner5 = createSpinner();
3183
3549
  spinner5.start("Loading relay events...");
3184
3550
  try {
@@ -3225,7 +3591,7 @@ async function relayEventsCommand(options) {
3225
3591
  }
3226
3592
  async function relayEventGetCommand(id) {
3227
3593
  const { apiKey } = getConfig3();
3228
- const api = new OneApi(apiKey);
3594
+ const api = new OneApi(apiKey, getApiBase());
3229
3595
  const spinner5 = createSpinner();
3230
3596
  spinner5.start("Loading relay event...");
3231
3597
  try {
@@ -3253,7 +3619,7 @@ async function relayDeliveriesCommand(options) {
3253
3619
  error("Provide either --endpoint-id or --event-id");
3254
3620
  }
3255
3621
  const { apiKey } = getConfig3();
3256
- const api = new OneApi(apiKey);
3622
+ const api = new OneApi(apiKey, getApiBase());
3257
3623
  const spinner5 = createSpinner();
3258
3624
  spinner5.start("Loading deliveries...");
3259
3625
  try {
@@ -3285,7 +3651,7 @@ async function relayDeliveriesCommand(options) {
3285
3651
  }
3286
3652
  async function relayEventTypesCommand(platform) {
3287
3653
  const { apiKey } = getConfig3();
3288
- const api = new OneApi(apiKey);
3654
+ const api = new OneApi(apiKey, getApiBase());
3289
3655
  const spinner5 = createSpinner();
3290
3656
  spinner5.start(`Loading event types for ${pc8.cyan(platform)}...`);
3291
3657
  try {
@@ -3312,8 +3678,122 @@ async function relayEventTypesCommand(platform) {
3312
3678
  }
3313
3679
  }
3314
3680
 
3315
- // src/commands/guide.ts
3681
+ // src/commands/cache.ts
3316
3682
  import pc9 from "picocolors";
3683
+ async function cacheClearCommand(actionId) {
3684
+ if (actionId) {
3685
+ const deleted = clearEntry(actionId);
3686
+ if (isAgentMode()) {
3687
+ json({ cleared: deleted, actionId });
3688
+ return;
3689
+ }
3690
+ if (deleted) {
3691
+ console.log(`Cleared cache for ${pc9.cyan(actionId)}`);
3692
+ } else {
3693
+ console.log(`No cache entry found for ${pc9.dim(actionId)}`);
3694
+ }
3695
+ } else {
3696
+ const count = clearAll();
3697
+ if (isAgentMode()) {
3698
+ json({ cleared: true, count });
3699
+ return;
3700
+ }
3701
+ console.log(`Cleared ${count} cached ${count === 1 ? "entry" : "entries"}`);
3702
+ }
3703
+ }
3704
+ async function cacheListCommand(options) {
3705
+ const entries = listCacheEntries();
3706
+ const filtered = options.expired ? entries.filter((e) => !isFresh(e.entry)) : entries;
3707
+ if (isAgentMode()) {
3708
+ json({
3709
+ entries: filtered.map((e) => ({
3710
+ type: e.type,
3711
+ key: e.entry.key,
3712
+ cachedAt: e.entry.cachedAt,
3713
+ age: formatAge(getAge(e.entry)),
3714
+ ttl: e.entry.ttl,
3715
+ fresh: isFresh(e.entry),
3716
+ etag: e.entry.etag,
3717
+ path: e.filePath
3718
+ }))
3719
+ });
3720
+ return;
3721
+ }
3722
+ if (filtered.length === 0) {
3723
+ console.log(options.expired ? "No expired cache entries" : "No cached entries");
3724
+ return;
3725
+ }
3726
+ const rows = filtered.map((e) => ({
3727
+ type: e.type,
3728
+ key: e.entry.key,
3729
+ age: formatAge(getAge(e.entry)),
3730
+ status: isFresh(e.entry) ? pc9.green("fresh") : pc9.yellow("expired")
3731
+ }));
3732
+ printTable(
3733
+ [
3734
+ { key: "type", label: "Type" },
3735
+ { key: "key", label: "Key" },
3736
+ { key: "age", label: "Age" },
3737
+ { key: "status", label: "Status" }
3738
+ ],
3739
+ rows
3740
+ );
3741
+ }
3742
+ async function cacheUpdateAllCommand() {
3743
+ const apiKey = getApiKey();
3744
+ if (!apiKey) {
3745
+ error("Not configured. Run `one init` first.");
3746
+ }
3747
+ const api = new OneApi(apiKey, getApiBase());
3748
+ const entries = listCacheEntries();
3749
+ if (entries.length === 0) {
3750
+ if (isAgentMode()) {
3751
+ json({ updated: 0, failed: 0, entries: [] });
3752
+ return;
3753
+ }
3754
+ console.log("No cached entries to update");
3755
+ return;
3756
+ }
3757
+ const spinner5 = createSpinner();
3758
+ spinner5.start(`Updating ${entries.length} cached ${entries.length === 1 ? "entry" : "entries"}...`);
3759
+ let updated = 0;
3760
+ let failed = 0;
3761
+ const errors = [];
3762
+ for (const e of entries) {
3763
+ try {
3764
+ if (e.type === "knowledge") {
3765
+ const result = await api.getActionKnowledgeWithMeta(e.entry.key);
3766
+ const newEntry = makeCacheEntry(e.entry.key, result.data, result.etag);
3767
+ writeCache(e.filePath, newEntry);
3768
+ updated++;
3769
+ } else {
3770
+ const refreshed = { ...e.entry, cachedAt: (/* @__PURE__ */ new Date()).toISOString() };
3771
+ writeCache(e.filePath, refreshed);
3772
+ updated++;
3773
+ }
3774
+ } catch (err) {
3775
+ failed++;
3776
+ errors.push({
3777
+ key: e.entry.key,
3778
+ error: err instanceof Error ? err.message : "Unknown error"
3779
+ });
3780
+ }
3781
+ }
3782
+ spinner5.stop(`Updated ${updated} ${updated === 1 ? "entry" : "entries"}${failed > 0 ? `, ${failed} failed` : ""}`);
3783
+ if (isAgentMode()) {
3784
+ json({ updated, failed, errors: errors.length > 0 ? errors : void 0 });
3785
+ return;
3786
+ }
3787
+ if (errors.length > 0) {
3788
+ console.log();
3789
+ for (const e of errors) {
3790
+ console.log(` ${pc9.red("\u2717")} ${e.key}: ${pc9.dim(e.error)}`);
3791
+ }
3792
+ }
3793
+ }
3794
+
3795
+ // src/commands/guide.ts
3796
+ import pc10 from "picocolors";
3317
3797
 
3318
3798
  // src/lib/guide-content.ts
3319
3799
  var GUIDE_OVERVIEW = `# One CLI \u2014 Agent Guide
@@ -3404,6 +3884,7 @@ Request specific sections:
3404
3884
  - \`one guide actions\` \u2014 Actions reference (search, knowledge, execute)
3405
3885
  - \`one guide flows\` \u2014 Workflow engine reference (step types, selectors, examples)
3406
3886
  - \`one guide relay\` \u2014 Webhook relay reference (templates, passthrough actions)
3887
+ - \`one guide cache\` \u2014 Cache management (TTL, flags, commands)
3407
3888
  - \`one guide all\` \u2014 Everything
3408
3889
 
3409
3890
  ## Important Notes
@@ -3555,11 +4036,93 @@ Any connected platform can be a destination via passthrough actions.
3555
4036
  3. \`relay deliveries --event-id <id>\` \u2014 check delivery status and errors
3556
4037
  4. \`relay event <id>\` \u2014 inspect full payload to verify template paths
3557
4038
  `;
4039
+ var GUIDE_CACHE = `# One Cache \u2014 Reference
4040
+
4041
+ ## Overview
4042
+
4043
+ 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.
4044
+
4045
+ Cache location: \`~/.one/cache/knowledge/\` and \`~/.one/cache/search/\`
4046
+
4047
+ ## How It Works
4048
+
4049
+ - **First call**: fetches from the API, writes to cache, serves the response
4050
+ - **Subsequent calls (within TTL)**: serves from cache instantly, no API call
4051
+ - **After TTL expires**: makes a conditional request (ETag). If content unchanged, refreshes the cache timestamp. If changed, writes fresh data.
4052
+ - **Network failure with stale cache**: serves the stale cache with a warning \u2014 never fails hard when a cache exists
4053
+
4054
+ Default TTL: 3600 seconds (1 hour). Configure via \`ONE_CACHE_TTL\` env var or \`cacheTtl\` in \`~/.one/config.json\`.
4055
+
4056
+ ## What Gets Cached
4057
+
4058
+ | Cached | Not Cached |
4059
+ |--------|-----------|
4060
+ | \`actions knowledge\` (API docs, change infrequently) | \`actions execute\` (live data, always fresh) |
4061
+ | \`actions search\` results | \`connection list\` (changes with add/remove) |
4062
+
4063
+ ## Agent Mode \`_cache\` Metadata
4064
+
4065
+ In \`--agent\` mode, knowledge and search responses include a \`_cache\` field:
4066
+
4067
+ \`\`\`json
4068
+ {
4069
+ "knowledge": "...",
4070
+ "method": "POST",
4071
+ "_cache": {
4072
+ "hit": true,
4073
+ "age": 1423,
4074
+ "fresh": true
4075
+ }
4076
+ }
4077
+ \`\`\`
4078
+
4079
+ Use this to programmatically decide whether to force-refresh.
4080
+
4081
+ ## Cache Flags
4082
+
4083
+ \`\`\`bash
4084
+ # Skip cache, fetch fresh (result still gets cached for next time)
4085
+ one --agent actions knowledge <platform> <actionId> --no-cache
4086
+
4087
+ # Check cache status without fetching
4088
+ one --agent actions knowledge <platform> <actionId> --cache-status
4089
+
4090
+ # Same for search
4091
+ one --agent actions search <platform> "<query>" --no-cache
4092
+ \`\`\`
4093
+
4094
+ ## Cache Management Commands
4095
+
4096
+ \`\`\`bash
4097
+ one cache list # List all cached entries with age and status
4098
+ one cache list --expired # List only expired entries
4099
+ one cache clear # Delete all cached knowledge and search data
4100
+ one cache clear <actionId> # Delete one specific entry
4101
+ one cache update-all # Re-fetch fresh data for all cached entries
4102
+ \`\`\`
4103
+
4104
+ All cache commands respect \`--agent\` for JSON output.
4105
+
4106
+ ## When to Force-Refresh
4107
+
4108
+ - After a platform updates its API docs (rare)
4109
+ - If you suspect stale data is causing issues
4110
+ - Use \`one cache update-all\` to proactively warm the entire cache
4111
+
4112
+ ## Configuration
4113
+
4114
+ | Setting | Source | Example |
4115
+ |---------|--------|---------|
4116
+ | TTL (seconds) | \`ONE_CACHE_TTL\` env var | \`ONE_CACHE_TTL=7200\` |
4117
+ | TTL (seconds) | \`cacheTtl\` in \`~/.one/config.json\` | \`"cacheTtl": 7200\` |
4118
+ | Default | \u2014 | 3600 (1 hour) |
4119
+ `;
3558
4120
  var TOPICS = [
3559
4121
  { topic: "overview", description: "Setup, features, and quick start for each" },
3560
4122
  { topic: "actions", description: "Search, read docs, and execute platform actions" },
3561
4123
  { topic: "flows", description: "Build and execute multi-step workflows" },
3562
4124
  { topic: "relay", description: "Receive webhooks and forward to other platforms" },
4125
+ { topic: "cache", description: "Local caching for knowledge and search responses" },
3563
4126
  { topic: "all", description: "Complete guide (all topics combined)" }
3564
4127
  ];
3565
4128
  function getGuideContent(topic) {
@@ -3572,10 +4135,12 @@ function getGuideContent(topic) {
3572
4135
  return { title: "One CLI \u2014 Agent Guide: Workflows", content: GUIDE_FLOWS };
3573
4136
  case "relay":
3574
4137
  return { title: "One CLI \u2014 Agent Guide: Relay", content: GUIDE_RELAY };
4138
+ case "cache":
4139
+ return { title: "One CLI \u2014 Agent Guide: Cache", content: GUIDE_CACHE };
3575
4140
  case "all":
3576
4141
  return {
3577
4142
  title: "One CLI \u2014 Agent Guide: Complete",
3578
- content: [GUIDE_OVERVIEW, GUIDE_ACTIONS, GUIDE_FLOWS, GUIDE_RELAY].join("\n---\n\n")
4143
+ content: [GUIDE_OVERVIEW, GUIDE_ACTIONS, GUIDE_FLOWS, GUIDE_RELAY, GUIDE_CACHE].join("\n---\n\n")
3579
4144
  };
3580
4145
  }
3581
4146
  }
@@ -3584,7 +4149,7 @@ function getAvailableTopics() {
3584
4149
  }
3585
4150
 
3586
4151
  // src/commands/guide.ts
3587
- var VALID_TOPICS = ["overview", "actions", "flows", "relay", "all"];
4152
+ var VALID_TOPICS = ["overview", "actions", "flows", "relay", "cache", "all"];
3588
4153
  async function guideCommand(topic = "all") {
3589
4154
  if (!VALID_TOPICS.includes(topic)) {
3590
4155
  error(
@@ -3597,14 +4162,14 @@ async function guideCommand(topic = "all") {
3597
4162
  json({ topic, title, content, availableTopics });
3598
4163
  return;
3599
4164
  }
3600
- intro2(pc9.bgCyan(pc9.black(" One Guide ")));
4165
+ intro2(pc10.bgCyan(pc10.black(" One Guide ")));
3601
4166
  console.log();
3602
4167
  console.log(content);
3603
- console.log(pc9.dim("\u2500".repeat(60)));
4168
+ console.log(pc10.dim("\u2500".repeat(60)));
3604
4169
  console.log(
3605
- pc9.dim("Available topics: ") + availableTopics.map((t) => pc9.cyan(t.topic)).join(", ")
4170
+ pc10.dim("Available topics: ") + availableTopics.map((t) => pc10.cyan(t.topic)).join(", ")
3606
4171
  );
3607
- console.log(pc9.dim(`Run ${pc9.cyan("one guide <topic>")} for a specific section.`));
4172
+ console.log(pc10.dim(`Run ${pc10.cyan("one guide <topic>")} for a specific section.`));
3608
4173
  }
3609
4174
 
3610
4175
  // src/lib/platform-meta.ts
@@ -3682,7 +4247,7 @@ async function onboardCommand(step) {
3682
4247
  let connections = [];
3683
4248
  if (currentStep >= 2) {
3684
4249
  try {
3685
- const api = new OneApi(apiKey);
4250
+ const api = new OneApi(apiKey, getApiBase());
3686
4251
  connections = await api.listConnections();
3687
4252
  } catch {
3688
4253
  }
@@ -3947,14 +4512,14 @@ async function fetchLatestVersionInfo() {
3947
4512
  return null;
3948
4513
  }
3949
4514
  }
3950
- function readCache() {
4515
+ function readCache3() {
3951
4516
  try {
3952
4517
  return JSON.parse(readFileSync(CACHE_PATH, "utf8"));
3953
4518
  } catch {
3954
4519
  return null;
3955
4520
  }
3956
4521
  }
3957
- function writeCache(latestVersion, publishedAt) {
4522
+ function writeCache2(latestVersion, publishedAt) {
3958
4523
  try {
3959
4524
  mkdirSync(join(homedir(), ".one"), { recursive: true });
3960
4525
  writeFileSync(CACHE_PATH, JSON.stringify({ lastCheck: Date.now(), latestVersion, publishedAt }));
@@ -3963,16 +4528,16 @@ function writeCache(latestVersion, publishedAt) {
3963
4528
  }
3964
4529
  async function checkLatestVersion() {
3965
4530
  const info = await fetchLatestVersionInfo();
3966
- if (info) writeCache(info.version, info.publishedAt);
4531
+ if (info) writeCache2(info.version, info.publishedAt);
3967
4532
  return info?.version ?? null;
3968
4533
  }
3969
4534
  async function checkLatestVersionCached() {
3970
- const cache = readCache();
3971
- if (cache && Date.now() - cache.lastCheck < CHECK_INTERVAL_MS) {
3972
- return { version: cache.latestVersion, publishedAt: cache.publishedAt ?? null };
4535
+ const cache2 = readCache3();
4536
+ if (cache2 && Date.now() - cache2.lastCheck < CHECK_INTERVAL_MS) {
4537
+ return { version: cache2.latestVersion, publishedAt: cache2.publishedAt ?? null };
3973
4538
  }
3974
4539
  const info = await fetchLatestVersionInfo();
3975
- if (info) writeCache(info.version, info.publishedAt);
4540
+ if (info) writeCache2(info.version, info.publishedAt);
3976
4541
  return info;
3977
4542
  }
3978
4543
  function getCurrentVersion() {
@@ -4015,6 +4580,14 @@ async function updateCommand() {
4015
4580
  error("Update failed \u2014 try running: npm install -g @withone/cli@latest");
4016
4581
  }
4017
4582
  }
4583
+ function isNewerVersion(latest, current) {
4584
+ const parse = (v) => v.split(".").map(Number);
4585
+ const [lMaj, lMin, lPat] = parse(latest);
4586
+ const [cMaj, cMin, cPat] = parse(current);
4587
+ if (lMaj !== cMaj) return lMaj > cMaj;
4588
+ if (lMin !== cMin) return lMin > cMin;
4589
+ return lPat > cPat;
4590
+ }
4018
4591
  function autoUpdate(targetVersion, publishedAt) {
4019
4592
  if (publishedAt) {
4020
4593
  const age = Date.now() - new Date(publishedAt).getTime();
@@ -4054,6 +4627,11 @@ program.name("one").option("--agent", "Machine-readable JSON output (no colors,
4054
4627
  one flow execute <key> Execute a workflow
4055
4628
  one flow validate <key> Validate a flow
4056
4629
 
4630
+ Cache:
4631
+ one cache list List cached entries with age and status
4632
+ one cache clear Clear all cached knowledge and search data
4633
+ one cache update-all Re-fetch fresh data for all cached entries
4634
+
4057
4635
  Webhook Relay:
4058
4636
  one relay create Create a relay endpoint for a connection
4059
4637
  one relay list List relay endpoints
@@ -4093,7 +4671,7 @@ program.hook("postAction", async () => {
4093
4671
  const info = await updateCheckPromise;
4094
4672
  if (!info) return;
4095
4673
  const current = getCurrentVersion();
4096
- if (current === info.version) return;
4674
+ if (!isNewerVersion(info.version, current)) return;
4097
4675
  autoUpdate(info.version, info.publishedAt);
4098
4676
  });
4099
4677
  program.command("init").description("Set up One and install MCP to your AI agents").option("-y, --yes", "Skip confirmations").option("-g, --global", "Install MCP globally (available in all projects)").option("-p, --project", "Install MCP for this project only (creates .mcp.json)").action(async (options) => {
@@ -4113,11 +4691,11 @@ program.command("platforms").alias("p").description("List available platforms").
4113
4691
  await platformsCommand(options);
4114
4692
  });
4115
4693
  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) => {
4694
+ 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
4695
  await actionsSearchCommand(platform, query, options);
4118
4696
  });
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);
4697
+ 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) => {
4698
+ await actionsKnowledgeCommand(platform, actionId, options);
4121
4699
  });
4122
4700
  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
4701
  await actionsExecuteCommand(platform, actionId, connectionKey, {
@@ -4183,6 +4761,16 @@ relay.command("deliveries").description("List delivery attempts for an endpoint
4183
4761
  relay.command("event-types <platform>").description("List supported webhook event types for a platform").action(async (platform) => {
4184
4762
  await relayEventTypesCommand(platform);
4185
4763
  });
4764
+ var cache = program.command("cache").description("Manage the local knowledge and search cache");
4765
+ cache.command("clear [actionId]").description("Clear all cached data, or a specific action by ID").action(async (actionId) => {
4766
+ await cacheClearCommand(actionId);
4767
+ });
4768
+ cache.command("list").alias("ls").description("List all cached entries with age and status").option("--expired", "Show only expired entries").action(async (options) => {
4769
+ await cacheListCommand(options);
4770
+ });
4771
+ cache.command("update-all").description("Re-fetch fresh data for all cached entries").action(async () => {
4772
+ await cacheUpdateAllCommand();
4773
+ });
4186
4774
  program.command("guide [topic]").description("Full CLI usage guide for agents (topics: overview, actions, flows, relay, all)").action(async (topic) => {
4187
4775
  await guideCommand(topic);
4188
4776
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@withone/cli",
3
- "version": "1.17.2",
3
+ "version": "1.19.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: