@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 = `${
|
|
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 = `${
|
|
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-
|
|
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,
|
package/dist/index.js
CHANGED
|
@@ -11,7 +11,7 @@ import {
|
|
|
11
11
|
loadFlow,
|
|
12
12
|
resolveFlowPath,
|
|
13
13
|
saveFlow
|
|
14
|
-
} from "./chunk-
|
|
14
|
+
} from "./chunk-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,
|
|
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,
|
|
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("
|
|
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
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
2566
|
+
const path5 = `${pathPrefix}[${i}]`;
|
|
2201
2567
|
if (!step || typeof step !== "object" || Array.isArray(step)) {
|
|
2202
|
-
errors.push({ path:
|
|
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: `${
|
|
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: `${
|
|
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: `${
|
|
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: `${
|
|
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: `${
|
|
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 = `${
|
|
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
|
|
2654
|
+
const path5 = `${pathPrefix}[${i}]`;
|
|
2289
2655
|
if (seen.has(step.id)) {
|
|
2290
|
-
errors.push({ path: `${
|
|
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], `${
|
|
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,
|
|
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:
|
|
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:
|
|
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
|
|
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 =
|
|
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 =
|
|
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/
|
|
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(
|
|
4165
|
+
intro2(pc10.bgCyan(pc10.black(" One Guide ")));
|
|
3601
4166
|
console.log();
|
|
3602
4167
|
console.log(content);
|
|
3603
|
-
console.log(
|
|
4168
|
+
console.log(pc10.dim("\u2500".repeat(60)));
|
|
3604
4169
|
console.log(
|
|
3605
|
-
|
|
4170
|
+
pc10.dim("Available topics: ") + availableTopics.map((t) => pc10.cyan(t.topic)).join(", ")
|
|
3606
4171
|
);
|
|
3607
|
-
console.log(
|
|
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
|
|
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
|
|
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)
|
|
4531
|
+
if (info) writeCache2(info.version, info.publishedAt);
|
|
3967
4532
|
return info?.version ?? null;
|
|
3968
4533
|
}
|
|
3969
4534
|
async function checkLatestVersionCached() {
|
|
3970
|
-
const
|
|
3971
|
-
if (
|
|
3972
|
-
return { version:
|
|
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)
|
|
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 (
|
|
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
package/skills/one/SKILL.md
CHANGED
|
@@ -100,6 +100,18 @@ All errors return JSON: `{"error": "message"}`. Parse output as JSON and check f
|
|
|
100
100
|
- JSON values passed to `-d`, `--path-vars`, `--query-params` must be valid JSON (use single quotes around JSON to avoid shell escaping)
|
|
101
101
|
- Do NOT pass path or query parameters inside the `-d` body flag
|
|
102
102
|
|
|
103
|
+
## Caching
|
|
104
|
+
|
|
105
|
+
Knowledge and search responses are cached locally (`~/.one/cache/`). Subsequent calls for the same action serve instantly from disk.
|
|
106
|
+
|
|
107
|
+
- Cache is automatic — no setup required
|
|
108
|
+
- Default TTL: 1 hour (configurable via `ONE_CACHE_TTL` env var)
|
|
109
|
+
- In `--agent` mode, responses include a `_cache` field: `{"hit": true, "age": 1423, "fresh": true}`
|
|
110
|
+
- Use `--no-cache` to force a fresh fetch: `one --agent actions knowledge <platform> <actionId> --no-cache`
|
|
111
|
+
- Use `--cache-status` to check cache state without fetching
|
|
112
|
+
- Manage cache: `one cache list`, `one cache clear`, `one cache update-all`
|
|
113
|
+
- `actions execute` is NEVER cached — always fresh
|
|
114
|
+
|
|
103
115
|
## Beyond Single Actions
|
|
104
116
|
|
|
105
117
|
One also supports more advanced patterns. Read the relevant reference file before using these:
|