@withone/cli 1.17.1 → 1.18.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md
CHANGED
|
@@ -225,6 +225,30 @@ one actions execute stripe <actionId> <connectionKey> \
|
|
|
225
225
|
| `--form-url-encoded` | Send as application/x-www-form-urlencoded |
|
|
226
226
|
| `--dry-run` | Show the request without executing it |
|
|
227
227
|
|
|
228
|
+
### `one cache`
|
|
229
|
+
|
|
230
|
+
Manage the local cache for knowledge and search responses. The CLI automatically caches `actions knowledge` and `actions search` results so repeated calls serve instantly from disk.
|
|
231
|
+
|
|
232
|
+
```bash
|
|
233
|
+
one cache list # List all cached entries with age and status
|
|
234
|
+
one cache list --expired # Show only expired entries
|
|
235
|
+
one cache clear # Clear all cached data
|
|
236
|
+
one cache clear <actionId> # Clear a specific entry
|
|
237
|
+
one cache update-all # Re-fetch fresh data for all cached entries
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
Knowledge and search commands also support cache flags:
|
|
241
|
+
|
|
242
|
+
```bash
|
|
243
|
+
one actions knowledge gmail <actionId> --no-cache # Skip cache, fetch fresh
|
|
244
|
+
one actions knowledge gmail <actionId> --cache-status # Check cache status
|
|
245
|
+
one actions search gmail "send email" --no-cache # Skip cache for search
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
Default TTL is 1 hour. Configure via `ONE_CACHE_TTL` environment variable or `cacheTtl` in `~/.one/config.json`.
|
|
249
|
+
|
|
250
|
+
Note: `actions execute` is never cached — it always hits the API fresh.
|
|
251
|
+
|
|
228
252
|
### `one guide [topic]`
|
|
229
253
|
|
|
230
254
|
Get the full CLI usage guide, designed for AI agents that only have the binary (no MCP, no IDE skills).
|
|
@@ -239,7 +263,7 @@ one --agent guide # full guide as structured JSON
|
|
|
239
263
|
one --agent guide flows # single topic as JSON
|
|
240
264
|
```
|
|
241
265
|
|
|
242
|
-
Topics: `overview`, `actions`, `flows`, `all` (default).
|
|
266
|
+
Topics: `overview`, `actions`, `flows`, `relay`, `cache`, `all` (default).
|
|
243
267
|
|
|
244
268
|
In agent mode (`--agent`), the JSON response includes the guide content and an `availableTopics` array so agents can discover what sections exist.
|
|
245
269
|
|
|
@@ -128,6 +128,81 @@ var OneApi = class {
|
|
|
128
128
|
method: action.method
|
|
129
129
|
};
|
|
130
130
|
}
|
|
131
|
+
async requestWithMeta(opts) {
|
|
132
|
+
let url = `${API_BASE}${opts.path}`;
|
|
133
|
+
if (opts.queryParams && Object.keys(opts.queryParams).length > 0) {
|
|
134
|
+
const params = new URLSearchParams(opts.queryParams);
|
|
135
|
+
url += `?${params.toString()}`;
|
|
136
|
+
}
|
|
137
|
+
const headers = {
|
|
138
|
+
"x-one-secret": this.apiKey,
|
|
139
|
+
"Content-Type": "application/json",
|
|
140
|
+
...opts.headers
|
|
141
|
+
};
|
|
142
|
+
if (opts.ifNoneMatch) {
|
|
143
|
+
headers["If-None-Match"] = opts.ifNoneMatch;
|
|
144
|
+
}
|
|
145
|
+
const fetchOpts = {
|
|
146
|
+
method: opts.method || "GET",
|
|
147
|
+
headers
|
|
148
|
+
};
|
|
149
|
+
if (opts.body !== void 0) {
|
|
150
|
+
fetchOpts.body = JSON.stringify(opts.body);
|
|
151
|
+
}
|
|
152
|
+
const response = await fetch(url, fetchOpts);
|
|
153
|
+
if (response.status === 304) {
|
|
154
|
+
return { data: null, etag: opts.ifNoneMatch ?? null, status: 304 };
|
|
155
|
+
}
|
|
156
|
+
if (!response.ok) {
|
|
157
|
+
const text2 = await response.text();
|
|
158
|
+
throw new ApiError(response.status, text2 || `HTTP ${response.status}`);
|
|
159
|
+
}
|
|
160
|
+
const etag = response.headers.get("etag") ?? null;
|
|
161
|
+
const text = await response.text();
|
|
162
|
+
const data = text ? JSON.parse(text) : {};
|
|
163
|
+
return { data, etag, status: response.status };
|
|
164
|
+
}
|
|
165
|
+
async getActionKnowledgeWithMeta(actionId, ifNoneMatch) {
|
|
166
|
+
const result = await this.requestWithMeta({
|
|
167
|
+
path: "/knowledge",
|
|
168
|
+
queryParams: { _id: actionId },
|
|
169
|
+
ifNoneMatch
|
|
170
|
+
});
|
|
171
|
+
if (result.status === 304) {
|
|
172
|
+
return { data: null, etag: result.etag, status: 304 };
|
|
173
|
+
}
|
|
174
|
+
const actions = result.data?.rows || [];
|
|
175
|
+
if (actions.length === 0) {
|
|
176
|
+
throw new ApiError(404, `Action with ID ${actionId} not found`);
|
|
177
|
+
}
|
|
178
|
+
const action = actions[0];
|
|
179
|
+
const knowledge = {
|
|
180
|
+
knowledge: action.knowledge || "No knowledge was found",
|
|
181
|
+
method: action.method || "No method was found"
|
|
182
|
+
};
|
|
183
|
+
return { data: knowledge, etag: result.etag, status: result.status };
|
|
184
|
+
}
|
|
185
|
+
async searchActionsWithMeta(platform, query, agentType, ifNoneMatch) {
|
|
186
|
+
const isKnowledgeAgent = !agentType || agentType === "knowledge";
|
|
187
|
+
const queryParams = {
|
|
188
|
+
query,
|
|
189
|
+
limit: "5"
|
|
190
|
+
};
|
|
191
|
+
if (isKnowledgeAgent) {
|
|
192
|
+
queryParams.knowledgeAgent = "true";
|
|
193
|
+
} else {
|
|
194
|
+
queryParams.executeAgent = "true";
|
|
195
|
+
}
|
|
196
|
+
const result = await this.requestWithMeta({
|
|
197
|
+
path: `/available-actions/search/${platform}`,
|
|
198
|
+
queryParams,
|
|
199
|
+
ifNoneMatch
|
|
200
|
+
});
|
|
201
|
+
if (result.status === 304) {
|
|
202
|
+
return { data: null, etag: result.etag, status: 304 };
|
|
203
|
+
}
|
|
204
|
+
return { data: result.data || [], etag: result.etag, status: result.status };
|
|
205
|
+
}
|
|
131
206
|
async executePassthroughRequest(args, preloadedAction) {
|
|
132
207
|
const action = preloadedAction ?? await this.getActionDetails(args.actionId);
|
|
133
208
|
const method = action.method;
|
|
@@ -703,7 +778,7 @@ async function executeSubflowStep(step, context, api, permissions, allowedAction
|
|
|
703
778
|
if (flowStack.includes(resolvedKey)) {
|
|
704
779
|
throw new Error(`Circular flow detected: ${[...flowStack, resolvedKey].join(" \u2192 ")}`);
|
|
705
780
|
}
|
|
706
|
-
const { loadFlow: loadFlow2 } = await import("./flow-runner-
|
|
781
|
+
const { loadFlow: loadFlow2 } = await import("./flow-runner-SU4JHSZW.js");
|
|
707
782
|
const subFlow = loadFlow2(resolvedKey);
|
|
708
783
|
const subContext = await executeFlow(
|
|
709
784
|
subFlow,
|
package/dist/index.js
CHANGED
|
@@ -11,7 +11,7 @@ import {
|
|
|
11
11
|
loadFlow,
|
|
12
12
|
resolveFlowPath,
|
|
13
13
|
saveFlow
|
|
14
|
-
} from "./chunk-
|
|
14
|
+
} from "./chunk-CTL2YHUH.js";
|
|
15
15
|
|
|
16
16
|
// src/index.ts
|
|
17
17
|
import { createRequire as createRequire2 } from "module";
|
|
@@ -101,6 +101,15 @@ function getAccessControlFromAllSources() {
|
|
|
101
101
|
function getAccessControl() {
|
|
102
102
|
return readConfig()?.accessControl ?? {};
|
|
103
103
|
}
|
|
104
|
+
function getCacheTtl() {
|
|
105
|
+
if (process.env.ONE_CACHE_TTL) {
|
|
106
|
+
const val = parseInt(process.env.ONE_CACHE_TTL, 10);
|
|
107
|
+
if (!isNaN(val) && val > 0) return val;
|
|
108
|
+
}
|
|
109
|
+
const config = readConfig();
|
|
110
|
+
if (config?.cacheTtl && config.cacheTtl > 0) return config.cacheTtl;
|
|
111
|
+
return 3600;
|
|
112
|
+
}
|
|
104
113
|
function updateAccessControl(settings) {
|
|
105
114
|
const config = readConfig();
|
|
106
115
|
if (!config) return;
|
|
@@ -223,12 +232,6 @@ var AGENTS = [
|
|
|
223
232
|
projectConfigPath: ".kiro/settings/mcp.json"
|
|
224
233
|
}
|
|
225
234
|
];
|
|
226
|
-
function getAllAgents() {
|
|
227
|
-
return AGENTS;
|
|
228
|
-
}
|
|
229
|
-
function supportsProjectScope(agent) {
|
|
230
|
-
return agent.projectConfigPath !== void 0;
|
|
231
|
-
}
|
|
232
235
|
function getAgentConfigPath(agent, scope = "global") {
|
|
233
236
|
if (scope === "project" && agent.projectConfigPath) {
|
|
234
237
|
return path2.join(process.cwd(), agent.projectConfigPath);
|
|
@@ -589,11 +592,6 @@ async function handleExistingConfig(apiKey, options) {
|
|
|
589
592
|
label: "Configure access control",
|
|
590
593
|
hint: "permissions, connections, actions"
|
|
591
594
|
});
|
|
592
|
-
actionOptions.push({
|
|
593
|
-
value: "install-mcp",
|
|
594
|
-
label: "Install MCP server",
|
|
595
|
-
hint: "not recommended \u2014 use skills instead"
|
|
596
|
-
});
|
|
597
595
|
actionOptions.push({
|
|
598
596
|
value: "start-fresh",
|
|
599
597
|
label: "Start fresh (reconfigure everything)"
|
|
@@ -631,10 +629,6 @@ async function handleExistingConfig(apiKey, options) {
|
|
|
631
629
|
case "access-control":
|
|
632
630
|
await configCommand();
|
|
633
631
|
break;
|
|
634
|
-
case "install-mcp":
|
|
635
|
-
await promptAndInstallMcp(apiKey, options);
|
|
636
|
-
p3.outro("Done.");
|
|
637
|
-
break;
|
|
638
632
|
case "start-fresh":
|
|
639
633
|
await freshSetup({ yes: true });
|
|
640
634
|
break;
|
|
@@ -859,102 +853,6 @@ function printOnboardingPrompt() {
|
|
|
859
853
|
console.log(pc2.cyan(" \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
|
|
860
854
|
console.log();
|
|
861
855
|
}
|
|
862
|
-
async function promptAndInstallMcp(apiKey, options) {
|
|
863
|
-
const allAgents = getAllAgents();
|
|
864
|
-
const agentChoice = await p3.select({
|
|
865
|
-
message: "Where do you want to install the MCP?",
|
|
866
|
-
options: [
|
|
867
|
-
{
|
|
868
|
-
value: "all",
|
|
869
|
-
label: "All agents",
|
|
870
|
-
hint: allAgents.map((a) => a.name).join(", ")
|
|
871
|
-
},
|
|
872
|
-
...allAgents.map((agent) => ({
|
|
873
|
-
value: agent.id,
|
|
874
|
-
label: agent.name
|
|
875
|
-
}))
|
|
876
|
-
]
|
|
877
|
-
});
|
|
878
|
-
if (p3.isCancel(agentChoice)) {
|
|
879
|
-
p3.log.info("Skipped MCP installation.");
|
|
880
|
-
return;
|
|
881
|
-
}
|
|
882
|
-
const selectedAgents = agentChoice === "all" ? allAgents : allAgents.filter((a) => a.id === agentChoice);
|
|
883
|
-
let scope = "global";
|
|
884
|
-
const hasProjectScopeAgent = selectedAgents.some((a) => supportsProjectScope(a));
|
|
885
|
-
if (options.global) {
|
|
886
|
-
scope = "global";
|
|
887
|
-
} else if (options.project) {
|
|
888
|
-
scope = "project";
|
|
889
|
-
} else if (hasProjectScopeAgent) {
|
|
890
|
-
const scopeChoice = await p3.select({
|
|
891
|
-
message: "How do you want to install it?",
|
|
892
|
-
options: [
|
|
893
|
-
{
|
|
894
|
-
value: "global",
|
|
895
|
-
label: "Global (Recommended)",
|
|
896
|
-
hint: "Available in all your projects"
|
|
897
|
-
},
|
|
898
|
-
{
|
|
899
|
-
value: "project",
|
|
900
|
-
label: "Project only",
|
|
901
|
-
hint: "Creates config files in current directory"
|
|
902
|
-
}
|
|
903
|
-
]
|
|
904
|
-
});
|
|
905
|
-
if (p3.isCancel(scopeChoice)) {
|
|
906
|
-
p3.log.info("Skipped MCP installation.");
|
|
907
|
-
return;
|
|
908
|
-
}
|
|
909
|
-
scope = scopeChoice;
|
|
910
|
-
}
|
|
911
|
-
if (scope === "project") {
|
|
912
|
-
const projectAgents = selectedAgents.filter((a) => supportsProjectScope(a));
|
|
913
|
-
const nonProjectAgents = selectedAgents.filter((a) => !supportsProjectScope(a));
|
|
914
|
-
if (projectAgents.length === 0) {
|
|
915
|
-
const supported = allAgents.filter((a) => supportsProjectScope(a)).map((a) => a.name).join(", ");
|
|
916
|
-
p3.note(
|
|
917
|
-
`${selectedAgents.map((a) => a.name).join(", ")} does not support project-level MCP.
|
|
918
|
-
Project scope is supported by: ${supported}`,
|
|
919
|
-
"Not Supported"
|
|
920
|
-
);
|
|
921
|
-
p3.log.warn("Run again and choose global scope or a different agent.");
|
|
922
|
-
return;
|
|
923
|
-
}
|
|
924
|
-
for (const agent of projectAgents) {
|
|
925
|
-
const wasInstalled = isMcpInstalled(agent, "project");
|
|
926
|
-
installMcpConfig(agent, apiKey, "project");
|
|
927
|
-
const configPath = getAgentConfigPath(agent, "project");
|
|
928
|
-
const status = wasInstalled ? "updated" : "created";
|
|
929
|
-
p3.log.success(`${agent.name}: ${configPath} ${status}`);
|
|
930
|
-
}
|
|
931
|
-
if (nonProjectAgents.length > 0) {
|
|
932
|
-
p3.log.info(`Installing globally for agents without project scope support:`);
|
|
933
|
-
for (const agent of nonProjectAgents) {
|
|
934
|
-
const wasInstalled = isMcpInstalled(agent, "global");
|
|
935
|
-
installMcpConfig(agent, apiKey, "global");
|
|
936
|
-
const status = wasInstalled ? "updated" : "installed";
|
|
937
|
-
p3.log.success(`${agent.name}: MCP ${status} (global)`);
|
|
938
|
-
}
|
|
939
|
-
}
|
|
940
|
-
const allInstalled = [...projectAgents, ...nonProjectAgents];
|
|
941
|
-
updateConfigAgentsList(allInstalled.map((a) => a.id));
|
|
942
|
-
p3.note(
|
|
943
|
-
pc2.yellow("Project config files can be committed to share with your team.\n") + pc2.yellow("Team members will need their own API key."),
|
|
944
|
-
"Tip"
|
|
945
|
-
);
|
|
946
|
-
return;
|
|
947
|
-
}
|
|
948
|
-
const installedAgentIds = [];
|
|
949
|
-
for (const agent of selectedAgents) {
|
|
950
|
-
const wasInstalled = isMcpInstalled(agent, "global");
|
|
951
|
-
installMcpConfig(agent, apiKey, "global");
|
|
952
|
-
installedAgentIds.push(agent.id);
|
|
953
|
-
const status = wasInstalled ? "updated" : "installed";
|
|
954
|
-
p3.log.success(`${agent.name}: MCP ${status}`);
|
|
955
|
-
}
|
|
956
|
-
updateConfigAgentsList(installedAgentIds);
|
|
957
|
-
}
|
|
958
856
|
async function freshSetup(options) {
|
|
959
857
|
p3.note(`Get your API key at:
|
|
960
858
|
${pc2.cyan(getApiKeyUrl())}`, "API Key");
|
|
@@ -1001,13 +899,6 @@ ${pc2.cyan(getApiKeyUrl())}`, "API Key");
|
|
|
1001
899
|
});
|
|
1002
900
|
await promptSkillInstall();
|
|
1003
901
|
await promptConnectIntegrations(apiKey);
|
|
1004
|
-
const installMcp = await p3.confirm({
|
|
1005
|
-
message: "Install One MCP server to your AI agents? (not recommended)",
|
|
1006
|
-
initialValue: false
|
|
1007
|
-
});
|
|
1008
|
-
if (!p3.isCancel(installMcp) && installMcp) {
|
|
1009
|
-
await promptAndInstallMcp(apiKey, options);
|
|
1010
|
-
}
|
|
1011
902
|
p3.note(
|
|
1012
903
|
`Config saved to: ${pc2.dim(getConfigPath())}`,
|
|
1013
904
|
"Setup Complete"
|
|
@@ -1112,16 +1003,6 @@ function maskApiKey(key) {
|
|
|
1112
1003
|
if (key.length <= 12) return key.slice(0, 8) + "...";
|
|
1113
1004
|
return key.slice(0, 8) + "..." + key.slice(-4);
|
|
1114
1005
|
}
|
|
1115
|
-
function updateConfigAgentsList(agentIds) {
|
|
1116
|
-
const config = readConfig();
|
|
1117
|
-
if (!config) return;
|
|
1118
|
-
for (const id of agentIds) {
|
|
1119
|
-
if (!config.installedAgents.includes(id)) {
|
|
1120
|
-
config.installedAgents.push(id);
|
|
1121
|
-
}
|
|
1122
|
-
}
|
|
1123
|
-
writeConfig(config);
|
|
1124
|
-
}
|
|
1125
1006
|
|
|
1126
1007
|
// src/commands/connection.ts
|
|
1127
1008
|
import * as p4 from "@clack/prompts";
|
|
@@ -1478,6 +1359,123 @@ async function platformsCommand(options) {
|
|
|
1478
1359
|
// src/commands/actions.ts
|
|
1479
1360
|
import * as p6 from "@clack/prompts";
|
|
1480
1361
|
import pc6 from "picocolors";
|
|
1362
|
+
|
|
1363
|
+
// src/lib/cache.ts
|
|
1364
|
+
import fs4 from "fs";
|
|
1365
|
+
import path4 from "path";
|
|
1366
|
+
import os4 from "os";
|
|
1367
|
+
var CACHE_BASE = path4.join(os4.homedir(), ".one", "cache");
|
|
1368
|
+
var KNOWLEDGE_DIR = path4.join(CACHE_BASE, "knowledge");
|
|
1369
|
+
var SEARCH_DIR = path4.join(CACHE_BASE, "search");
|
|
1370
|
+
function sanitizeFilename(input) {
|
|
1371
|
+
return input.replace(/[^a-zA-Z0-9_\-\.]/g, "_");
|
|
1372
|
+
}
|
|
1373
|
+
function knowledgeCachePath(actionId) {
|
|
1374
|
+
return path4.join(KNOWLEDGE_DIR, `${sanitizeFilename(actionId)}.json`);
|
|
1375
|
+
}
|
|
1376
|
+
function searchCachePath(platform, query, type) {
|
|
1377
|
+
const key = `${platform}_${sanitizeFilename(query)}_${type || "knowledge"}`;
|
|
1378
|
+
return path4.join(SEARCH_DIR, `${key}.json`);
|
|
1379
|
+
}
|
|
1380
|
+
function readCache(filePath) {
|
|
1381
|
+
try {
|
|
1382
|
+
const content = fs4.readFileSync(filePath, "utf-8");
|
|
1383
|
+
return JSON.parse(content);
|
|
1384
|
+
} catch {
|
|
1385
|
+
return null;
|
|
1386
|
+
}
|
|
1387
|
+
}
|
|
1388
|
+
function writeCache(filePath, entry) {
|
|
1389
|
+
try {
|
|
1390
|
+
const dir = path4.dirname(filePath);
|
|
1391
|
+
fs4.mkdirSync(dir, { recursive: true });
|
|
1392
|
+
fs4.writeFileSync(filePath, JSON.stringify(entry, null, 2));
|
|
1393
|
+
} catch {
|
|
1394
|
+
}
|
|
1395
|
+
}
|
|
1396
|
+
function isFresh(entry) {
|
|
1397
|
+
const cachedTime = new Date(entry.cachedAt).getTime();
|
|
1398
|
+
const now = Date.now();
|
|
1399
|
+
return now - cachedTime < entry.ttl * 1e3;
|
|
1400
|
+
}
|
|
1401
|
+
function getAge(entry) {
|
|
1402
|
+
return Math.floor((Date.now() - new Date(entry.cachedAt).getTime()) / 1e3);
|
|
1403
|
+
}
|
|
1404
|
+
function buildCacheMeta(entry, hit) {
|
|
1405
|
+
if (!entry) {
|
|
1406
|
+
return { hit: false, age: 0, fresh: false };
|
|
1407
|
+
}
|
|
1408
|
+
return {
|
|
1409
|
+
hit,
|
|
1410
|
+
age: getAge(entry),
|
|
1411
|
+
fresh: isFresh(entry)
|
|
1412
|
+
};
|
|
1413
|
+
}
|
|
1414
|
+
function formatAge(seconds) {
|
|
1415
|
+
if (seconds < 60) return `${seconds}s`;
|
|
1416
|
+
if (seconds < 3600) return `${Math.floor(seconds / 60)}m`;
|
|
1417
|
+
if (seconds < 86400) {
|
|
1418
|
+
const h2 = Math.floor(seconds / 3600);
|
|
1419
|
+
const m = Math.floor(seconds % 3600 / 60);
|
|
1420
|
+
return m > 0 ? `${h2}h ${m}m` : `${h2}h`;
|
|
1421
|
+
}
|
|
1422
|
+
const d = Math.floor(seconds / 86400);
|
|
1423
|
+
const h = Math.floor(seconds % 86400 / 3600);
|
|
1424
|
+
return h > 0 ? `${d}d ${h}h` : `${d}d`;
|
|
1425
|
+
}
|
|
1426
|
+
function listCacheEntries() {
|
|
1427
|
+
const entries = [];
|
|
1428
|
+
for (const [dir, type] of [[KNOWLEDGE_DIR, "knowledge"], [SEARCH_DIR, "search"]]) {
|
|
1429
|
+
try {
|
|
1430
|
+
const files = fs4.readdirSync(dir);
|
|
1431
|
+
for (const file of files) {
|
|
1432
|
+
if (!file.endsWith(".json")) continue;
|
|
1433
|
+
const filePath = path4.join(dir, file);
|
|
1434
|
+
const entry = readCache(filePath);
|
|
1435
|
+
if (entry) {
|
|
1436
|
+
entries.push({ type, filePath, entry });
|
|
1437
|
+
}
|
|
1438
|
+
}
|
|
1439
|
+
} catch {
|
|
1440
|
+
}
|
|
1441
|
+
}
|
|
1442
|
+
return entries;
|
|
1443
|
+
}
|
|
1444
|
+
function clearAll() {
|
|
1445
|
+
let count = 0;
|
|
1446
|
+
for (const dir of [KNOWLEDGE_DIR, SEARCH_DIR]) {
|
|
1447
|
+
try {
|
|
1448
|
+
const files = fs4.readdirSync(dir);
|
|
1449
|
+
for (const file of files) {
|
|
1450
|
+
fs4.unlinkSync(path4.join(dir, file));
|
|
1451
|
+
count++;
|
|
1452
|
+
}
|
|
1453
|
+
fs4.rmdirSync(dir);
|
|
1454
|
+
} catch {
|
|
1455
|
+
}
|
|
1456
|
+
}
|
|
1457
|
+
return count;
|
|
1458
|
+
}
|
|
1459
|
+
function clearEntry(actionId) {
|
|
1460
|
+
const filePath = knowledgeCachePath(actionId);
|
|
1461
|
+
try {
|
|
1462
|
+
fs4.unlinkSync(filePath);
|
|
1463
|
+
return true;
|
|
1464
|
+
} catch {
|
|
1465
|
+
return false;
|
|
1466
|
+
}
|
|
1467
|
+
}
|
|
1468
|
+
function makeCacheEntry(key, data, etag) {
|
|
1469
|
+
return {
|
|
1470
|
+
key,
|
|
1471
|
+
etag,
|
|
1472
|
+
cachedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1473
|
+
ttl: getCacheTtl(),
|
|
1474
|
+
data
|
|
1475
|
+
};
|
|
1476
|
+
}
|
|
1477
|
+
|
|
1478
|
+
// src/commands/actions.ts
|
|
1481
1479
|
function getConfig() {
|
|
1482
1480
|
const apiKey = getApiKey();
|
|
1483
1481
|
if (!apiKey) {
|
|
@@ -1505,17 +1503,65 @@ async function actionsSearchCommand(platform, query, options) {
|
|
|
1505
1503
|
spinner5.start(`Searching actions on ${pc6.cyan(platform)} for "${query}"...`);
|
|
1506
1504
|
try {
|
|
1507
1505
|
const agentType = knowledgeAgent ? "knowledge" : options.type;
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
}
|
|
1506
|
+
const useCache = options.cache !== false;
|
|
1507
|
+
const cachePath = searchCachePath(platform, query, agentType || "knowledge");
|
|
1508
|
+
const cached = useCache ? readCache(cachePath) : null;
|
|
1509
|
+
let cleanedActions;
|
|
1510
|
+
let cacheHit = false;
|
|
1511
|
+
if (cached && isFresh(cached)) {
|
|
1512
|
+
cleanedActions = cached.data.actions;
|
|
1513
|
+
cacheHit = true;
|
|
1514
|
+
} else {
|
|
1515
|
+
try {
|
|
1516
|
+
const result = await api.searchActionsWithMeta(
|
|
1517
|
+
platform,
|
|
1518
|
+
query,
|
|
1519
|
+
agentType,
|
|
1520
|
+
cached?.etag ?? void 0
|
|
1521
|
+
);
|
|
1522
|
+
if (result.status === 304 && cached) {
|
|
1523
|
+
cached.cachedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
1524
|
+
writeCache(cachePath, cached);
|
|
1525
|
+
cleanedActions = cached.data.actions;
|
|
1526
|
+
cacheHit = true;
|
|
1527
|
+
} else {
|
|
1528
|
+
let actions2 = result.data;
|
|
1529
|
+
actions2 = filterByPermissions(actions2, permissions);
|
|
1530
|
+
actions2 = actions2.filter((a) => isActionAllowed(a.systemId, actionIds));
|
|
1531
|
+
cleanedActions = actions2.map((action) => ({
|
|
1532
|
+
actionId: action.systemId,
|
|
1533
|
+
title: action.title,
|
|
1534
|
+
method: action.method,
|
|
1535
|
+
path: action.path
|
|
1536
|
+
}));
|
|
1537
|
+
writeCache(cachePath, makeCacheEntry(
|
|
1538
|
+
`${platform}_${query}_${agentType || "knowledge"}`,
|
|
1539
|
+
{ actions: cleanedActions },
|
|
1540
|
+
result.etag
|
|
1541
|
+
));
|
|
1542
|
+
}
|
|
1543
|
+
} catch (fetchError) {
|
|
1544
|
+
if (cached) {
|
|
1545
|
+
process.stderr.write(
|
|
1546
|
+
`Warning: serving cached search results (network unavailable, cached ${formatAge(getAge(cached))} ago)
|
|
1547
|
+
`
|
|
1548
|
+
);
|
|
1549
|
+
cleanedActions = cached.data.actions;
|
|
1550
|
+
cacheHit = true;
|
|
1551
|
+
} else {
|
|
1552
|
+
throw fetchError;
|
|
1553
|
+
}
|
|
1554
|
+
}
|
|
1555
|
+
}
|
|
1517
1556
|
if (isAgentMode()) {
|
|
1518
|
-
|
|
1557
|
+
const response = { actions: cleanedActions };
|
|
1558
|
+
if (cacheHit && cached) {
|
|
1559
|
+
response._cache = buildCacheMeta(cached, true);
|
|
1560
|
+
} else {
|
|
1561
|
+
const freshEntry = readCache(cachePath);
|
|
1562
|
+
response._cache = buildCacheMeta(freshEntry, false);
|
|
1563
|
+
}
|
|
1564
|
+
json(response);
|
|
1519
1565
|
return;
|
|
1520
1566
|
}
|
|
1521
1567
|
if (cleanedActions.length === 0) {
|
|
@@ -1569,7 +1615,29 @@ Execute: ${pc6.cyan(`one actions execute ${platform} <actionId> <connectionK
|
|
|
1569
1615
|
);
|
|
1570
1616
|
}
|
|
1571
1617
|
}
|
|
1572
|
-
async function actionsKnowledgeCommand(platform, actionId) {
|
|
1618
|
+
async function actionsKnowledgeCommand(platform, actionId, options) {
|
|
1619
|
+
const cachePath = knowledgeCachePath(actionId);
|
|
1620
|
+
if (options.cacheStatus) {
|
|
1621
|
+
const entry = readCache(cachePath);
|
|
1622
|
+
if (!entry) {
|
|
1623
|
+
json({
|
|
1624
|
+
cached: false,
|
|
1625
|
+
path: cachePath
|
|
1626
|
+
});
|
|
1627
|
+
} else {
|
|
1628
|
+
const age = getAge(entry);
|
|
1629
|
+
json({
|
|
1630
|
+
cached: true,
|
|
1631
|
+
cachedAt: entry.cachedAt,
|
|
1632
|
+
age: formatAge(age),
|
|
1633
|
+
ttl: entry.ttl,
|
|
1634
|
+
expired: !isFresh(entry),
|
|
1635
|
+
etag: entry.etag,
|
|
1636
|
+
path: cachePath
|
|
1637
|
+
});
|
|
1638
|
+
}
|
|
1639
|
+
return;
|
|
1640
|
+
}
|
|
1573
1641
|
intro2(pc6.bgCyan(pc6.black(" One ")));
|
|
1574
1642
|
const { apiKey, actionIds, connectionKeys } = getConfig();
|
|
1575
1643
|
const api = new OneApi(apiKey);
|
|
@@ -1597,15 +1665,57 @@ async function actionsKnowledgeCommand(platform, actionId) {
|
|
|
1597
1665
|
const spinner5 = createSpinner();
|
|
1598
1666
|
spinner5.start(`Loading knowledge for action ${pc6.dim(actionId)}...`);
|
|
1599
1667
|
try {
|
|
1600
|
-
const
|
|
1668
|
+
const useCache = options.cache !== false;
|
|
1669
|
+
const cached = useCache ? readCache(cachePath) : null;
|
|
1670
|
+
let knowledgeData;
|
|
1671
|
+
let cacheHit = false;
|
|
1672
|
+
let cacheEntry = cached;
|
|
1673
|
+
if (cached && isFresh(cached) && useCache) {
|
|
1674
|
+
knowledgeData = cached.data;
|
|
1675
|
+
cacheHit = true;
|
|
1676
|
+
} else {
|
|
1677
|
+
try {
|
|
1678
|
+
const result = await api.getActionKnowledgeWithMeta(
|
|
1679
|
+
actionId,
|
|
1680
|
+
cached?.etag ?? void 0
|
|
1681
|
+
);
|
|
1682
|
+
if (result.status === 304 && cached) {
|
|
1683
|
+
cached.cachedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
1684
|
+
writeCache(cachePath, cached);
|
|
1685
|
+
knowledgeData = cached.data;
|
|
1686
|
+
cacheHit = true;
|
|
1687
|
+
} else {
|
|
1688
|
+
knowledgeData = result.data;
|
|
1689
|
+
const newEntry = makeCacheEntry(actionId, knowledgeData, result.etag);
|
|
1690
|
+
writeCache(cachePath, newEntry);
|
|
1691
|
+
cacheEntry = newEntry;
|
|
1692
|
+
}
|
|
1693
|
+
} catch (fetchError) {
|
|
1694
|
+
if (cached) {
|
|
1695
|
+
process.stderr.write(
|
|
1696
|
+
`Warning: serving cached knowledge (network unavailable, cached ${formatAge(getAge(cached))} ago)
|
|
1697
|
+
`
|
|
1698
|
+
);
|
|
1699
|
+
knowledgeData = cached.data;
|
|
1700
|
+
cacheHit = true;
|
|
1701
|
+
} else {
|
|
1702
|
+
throw fetchError;
|
|
1703
|
+
}
|
|
1704
|
+
}
|
|
1705
|
+
}
|
|
1601
1706
|
const knowledgeWithGuidance = buildActionKnowledgeWithGuidance(
|
|
1602
|
-
knowledge,
|
|
1603
|
-
method,
|
|
1707
|
+
knowledgeData.knowledge,
|
|
1708
|
+
knowledgeData.method,
|
|
1604
1709
|
platform,
|
|
1605
1710
|
actionId
|
|
1606
1711
|
);
|
|
1607
1712
|
if (isAgentMode()) {
|
|
1608
|
-
|
|
1713
|
+
const response = {
|
|
1714
|
+
knowledge: knowledgeWithGuidance,
|
|
1715
|
+
method: knowledgeData.method,
|
|
1716
|
+
_cache: buildCacheMeta(cacheEntry, cacheHit)
|
|
1717
|
+
};
|
|
1718
|
+
json(response);
|
|
1609
1719
|
return;
|
|
1610
1720
|
}
|
|
1611
1721
|
spinner5.stop("Knowledge loaded");
|
|
@@ -2325,26 +2435,26 @@ function validateStepsArray(steps, pathPrefix, errors) {
|
|
|
2325
2435
|
const validTypes = FLOW_SCHEMA.stepTypes.map((st) => st.type);
|
|
2326
2436
|
for (let i = 0; i < steps.length; i++) {
|
|
2327
2437
|
const step = steps[i];
|
|
2328
|
-
const
|
|
2438
|
+
const path5 = `${pathPrefix}[${i}]`;
|
|
2329
2439
|
if (!step || typeof step !== "object" || Array.isArray(step)) {
|
|
2330
|
-
errors.push({ path:
|
|
2440
|
+
errors.push({ path: path5, message: "Step must be an object" });
|
|
2331
2441
|
continue;
|
|
2332
2442
|
}
|
|
2333
2443
|
const s = step;
|
|
2334
2444
|
if (!s.id || typeof s.id !== "string") {
|
|
2335
|
-
errors.push({ path: `${
|
|
2445
|
+
errors.push({ path: `${path5}.id`, message: 'Step must have a string "id"' });
|
|
2336
2446
|
}
|
|
2337
2447
|
if (!s.name || typeof s.name !== "string") {
|
|
2338
|
-
errors.push({ path: `${
|
|
2448
|
+
errors.push({ path: `${path5}.name`, message: 'Step must have a string "name"' });
|
|
2339
2449
|
}
|
|
2340
2450
|
if (!s.type || !validTypes.includes(s.type)) {
|
|
2341
|
-
errors.push({ path: `${
|
|
2451
|
+
errors.push({ path: `${path5}.type`, message: `Step type must be one of: ${validTypes.join(", ")}` });
|
|
2342
2452
|
continue;
|
|
2343
2453
|
}
|
|
2344
2454
|
if (s.onError && typeof s.onError === "object") {
|
|
2345
2455
|
const oe = s.onError;
|
|
2346
2456
|
if (!FLOW_SCHEMA.errorStrategies.includes(oe.strategy)) {
|
|
2347
|
-
errors.push({ path: `${
|
|
2457
|
+
errors.push({ path: `${path5}.onError.strategy`, message: `Error strategy must be one of: ${FLOW_SCHEMA.errorStrategies.join(", ")}` });
|
|
2348
2458
|
}
|
|
2349
2459
|
}
|
|
2350
2460
|
const descriptor = getStepTypeDescriptor(s.type);
|
|
@@ -2354,14 +2464,14 @@ function validateStepsArray(steps, pathPrefix, errors) {
|
|
|
2354
2464
|
if (!configObj || typeof configObj !== "object") {
|
|
2355
2465
|
const hint = detectFlatConfigHint(s, descriptor);
|
|
2356
2466
|
errors.push({
|
|
2357
|
-
path: `${
|
|
2467
|
+
path: `${path5}.${configKey}`,
|
|
2358
2468
|
message: `${capitalize(descriptor.type)} step must have a "${configKey}" config object${hint}`
|
|
2359
2469
|
});
|
|
2360
2470
|
continue;
|
|
2361
2471
|
}
|
|
2362
2472
|
const config = configObj;
|
|
2363
2473
|
for (const [fieldName, fd] of Object.entries(descriptor.fields)) {
|
|
2364
|
-
const fieldPath = `${
|
|
2474
|
+
const fieldPath = `${path5}.${configKey}.${fieldName}`;
|
|
2365
2475
|
const value = config[fieldName];
|
|
2366
2476
|
if (fd.required && (value === void 0 || value === null || value === "")) {
|
|
2367
2477
|
errors.push({ path: fieldPath, message: `${capitalize(descriptor.type)} must have ${fd.type === "string" ? "a string" : fd.type === "array" ? "a" : "a"} "${fieldName}"` });
|
|
@@ -2413,16 +2523,16 @@ function validateStepIds(flow2) {
|
|
|
2413
2523
|
function collectIds(steps, pathPrefix) {
|
|
2414
2524
|
for (let i = 0; i < steps.length; i++) {
|
|
2415
2525
|
const step = steps[i];
|
|
2416
|
-
const
|
|
2526
|
+
const path5 = `${pathPrefix}[${i}]`;
|
|
2417
2527
|
if (seen.has(step.id)) {
|
|
2418
|
-
errors.push({ path: `${
|
|
2528
|
+
errors.push({ path: `${path5}.id`, message: `Duplicate step ID: "${step.id}"` });
|
|
2419
2529
|
} else {
|
|
2420
2530
|
seen.add(step.id);
|
|
2421
2531
|
}
|
|
2422
2532
|
for (const { configKey, fieldName } of nestedKeys) {
|
|
2423
2533
|
const config = step[configKey];
|
|
2424
2534
|
if (config && Array.isArray(config[fieldName])) {
|
|
2425
|
-
collectIds(config[fieldName], `${
|
|
2535
|
+
collectIds(config[fieldName], `${path5}.${configKey}.${fieldName}`);
|
|
2426
2536
|
}
|
|
2427
2537
|
}
|
|
2428
2538
|
}
|
|
@@ -2469,7 +2579,7 @@ function validateSelectorReferences(flow2) {
|
|
|
2469
2579
|
}
|
|
2470
2580
|
return selectors;
|
|
2471
2581
|
}
|
|
2472
|
-
function checkSelectors(selectors,
|
|
2582
|
+
function checkSelectors(selectors, path5) {
|
|
2473
2583
|
for (const selector of selectors) {
|
|
2474
2584
|
const parts = selector.split(".");
|
|
2475
2585
|
if (parts.length < 3) continue;
|
|
@@ -2477,12 +2587,12 @@ function validateSelectorReferences(flow2) {
|
|
|
2477
2587
|
if (root === "input") {
|
|
2478
2588
|
const inputName = parts[2];
|
|
2479
2589
|
if (!inputNames.has(inputName)) {
|
|
2480
|
-
errors.push({ path:
|
|
2590
|
+
errors.push({ path: path5, message: `Selector "${selector}" references undefined input "${inputName}"` });
|
|
2481
2591
|
}
|
|
2482
2592
|
} else if (root === "steps") {
|
|
2483
2593
|
const stepId = parts[2];
|
|
2484
2594
|
if (!allStepIds.has(stepId)) {
|
|
2485
|
-
errors.push({ path:
|
|
2595
|
+
errors.push({ path: path5, message: `Selector "${selector}" references undefined step "${stepId}"` });
|
|
2486
2596
|
}
|
|
2487
2597
|
}
|
|
2488
2598
|
}
|
|
@@ -2530,7 +2640,7 @@ function validateFlow(flow2) {
|
|
|
2530
2640
|
}
|
|
2531
2641
|
|
|
2532
2642
|
// src/commands/flow.ts
|
|
2533
|
-
import
|
|
2643
|
+
import fs5 from "fs";
|
|
2534
2644
|
function getConfig2() {
|
|
2535
2645
|
const apiKey = getApiKey();
|
|
2536
2646
|
if (!apiKey) {
|
|
@@ -2588,7 +2698,7 @@ async function flowCreateCommand(key, options) {
|
|
|
2588
2698
|
if (raw.startsWith("@")) {
|
|
2589
2699
|
const filePath = raw.slice(1);
|
|
2590
2700
|
try {
|
|
2591
|
-
raw =
|
|
2701
|
+
raw = fs5.readFileSync(filePath, "utf-8");
|
|
2592
2702
|
} catch (err) {
|
|
2593
2703
|
error(`Cannot read file "${filePath}": ${err.message}`);
|
|
2594
2704
|
}
|
|
@@ -2771,7 +2881,7 @@ async function flowValidateCommand(keyOrPath) {
|
|
|
2771
2881
|
let flowData;
|
|
2772
2882
|
try {
|
|
2773
2883
|
const flowPath = resolveFlowPath(keyOrPath);
|
|
2774
|
-
const content =
|
|
2884
|
+
const content = fs5.readFileSync(flowPath, "utf-8");
|
|
2775
2885
|
flowData = JSON.parse(content);
|
|
2776
2886
|
} catch (err) {
|
|
2777
2887
|
spinner5.stop("Validation failed");
|
|
@@ -3440,8 +3550,122 @@ async function relayEventTypesCommand(platform) {
|
|
|
3440
3550
|
}
|
|
3441
3551
|
}
|
|
3442
3552
|
|
|
3443
|
-
// src/commands/
|
|
3553
|
+
// src/commands/cache.ts
|
|
3444
3554
|
import pc9 from "picocolors";
|
|
3555
|
+
async function cacheClearCommand(actionId) {
|
|
3556
|
+
if (actionId) {
|
|
3557
|
+
const deleted = clearEntry(actionId);
|
|
3558
|
+
if (isAgentMode()) {
|
|
3559
|
+
json({ cleared: deleted, actionId });
|
|
3560
|
+
return;
|
|
3561
|
+
}
|
|
3562
|
+
if (deleted) {
|
|
3563
|
+
console.log(`Cleared cache for ${pc9.cyan(actionId)}`);
|
|
3564
|
+
} else {
|
|
3565
|
+
console.log(`No cache entry found for ${pc9.dim(actionId)}`);
|
|
3566
|
+
}
|
|
3567
|
+
} else {
|
|
3568
|
+
const count = clearAll();
|
|
3569
|
+
if (isAgentMode()) {
|
|
3570
|
+
json({ cleared: true, count });
|
|
3571
|
+
return;
|
|
3572
|
+
}
|
|
3573
|
+
console.log(`Cleared ${count} cached ${count === 1 ? "entry" : "entries"}`);
|
|
3574
|
+
}
|
|
3575
|
+
}
|
|
3576
|
+
async function cacheListCommand(options) {
|
|
3577
|
+
const entries = listCacheEntries();
|
|
3578
|
+
const filtered = options.expired ? entries.filter((e) => !isFresh(e.entry)) : entries;
|
|
3579
|
+
if (isAgentMode()) {
|
|
3580
|
+
json({
|
|
3581
|
+
entries: filtered.map((e) => ({
|
|
3582
|
+
type: e.type,
|
|
3583
|
+
key: e.entry.key,
|
|
3584
|
+
cachedAt: e.entry.cachedAt,
|
|
3585
|
+
age: formatAge(getAge(e.entry)),
|
|
3586
|
+
ttl: e.entry.ttl,
|
|
3587
|
+
fresh: isFresh(e.entry),
|
|
3588
|
+
etag: e.entry.etag,
|
|
3589
|
+
path: e.filePath
|
|
3590
|
+
}))
|
|
3591
|
+
});
|
|
3592
|
+
return;
|
|
3593
|
+
}
|
|
3594
|
+
if (filtered.length === 0) {
|
|
3595
|
+
console.log(options.expired ? "No expired cache entries" : "No cached entries");
|
|
3596
|
+
return;
|
|
3597
|
+
}
|
|
3598
|
+
const rows = filtered.map((e) => ({
|
|
3599
|
+
type: e.type,
|
|
3600
|
+
key: e.entry.key,
|
|
3601
|
+
age: formatAge(getAge(e.entry)),
|
|
3602
|
+
status: isFresh(e.entry) ? pc9.green("fresh") : pc9.yellow("expired")
|
|
3603
|
+
}));
|
|
3604
|
+
printTable(
|
|
3605
|
+
[
|
|
3606
|
+
{ key: "type", label: "Type" },
|
|
3607
|
+
{ key: "key", label: "Key" },
|
|
3608
|
+
{ key: "age", label: "Age" },
|
|
3609
|
+
{ key: "status", label: "Status" }
|
|
3610
|
+
],
|
|
3611
|
+
rows
|
|
3612
|
+
);
|
|
3613
|
+
}
|
|
3614
|
+
async function cacheUpdateAllCommand() {
|
|
3615
|
+
const apiKey = getApiKey();
|
|
3616
|
+
if (!apiKey) {
|
|
3617
|
+
error("Not configured. Run `one init` first.");
|
|
3618
|
+
}
|
|
3619
|
+
const api = new OneApi(apiKey);
|
|
3620
|
+
const entries = listCacheEntries();
|
|
3621
|
+
if (entries.length === 0) {
|
|
3622
|
+
if (isAgentMode()) {
|
|
3623
|
+
json({ updated: 0, failed: 0, entries: [] });
|
|
3624
|
+
return;
|
|
3625
|
+
}
|
|
3626
|
+
console.log("No cached entries to update");
|
|
3627
|
+
return;
|
|
3628
|
+
}
|
|
3629
|
+
const spinner5 = createSpinner();
|
|
3630
|
+
spinner5.start(`Updating ${entries.length} cached ${entries.length === 1 ? "entry" : "entries"}...`);
|
|
3631
|
+
let updated = 0;
|
|
3632
|
+
let failed = 0;
|
|
3633
|
+
const errors = [];
|
|
3634
|
+
for (const e of entries) {
|
|
3635
|
+
try {
|
|
3636
|
+
if (e.type === "knowledge") {
|
|
3637
|
+
const result = await api.getActionKnowledgeWithMeta(e.entry.key);
|
|
3638
|
+
const newEntry = makeCacheEntry(e.entry.key, result.data, result.etag);
|
|
3639
|
+
writeCache(e.filePath, newEntry);
|
|
3640
|
+
updated++;
|
|
3641
|
+
} else {
|
|
3642
|
+
const refreshed = { ...e.entry, cachedAt: (/* @__PURE__ */ new Date()).toISOString() };
|
|
3643
|
+
writeCache(e.filePath, refreshed);
|
|
3644
|
+
updated++;
|
|
3645
|
+
}
|
|
3646
|
+
} catch (err) {
|
|
3647
|
+
failed++;
|
|
3648
|
+
errors.push({
|
|
3649
|
+
key: e.entry.key,
|
|
3650
|
+
error: err instanceof Error ? err.message : "Unknown error"
|
|
3651
|
+
});
|
|
3652
|
+
}
|
|
3653
|
+
}
|
|
3654
|
+
spinner5.stop(`Updated ${updated} ${updated === 1 ? "entry" : "entries"}${failed > 0 ? `, ${failed} failed` : ""}`);
|
|
3655
|
+
if (isAgentMode()) {
|
|
3656
|
+
json({ updated, failed, errors: errors.length > 0 ? errors : void 0 });
|
|
3657
|
+
return;
|
|
3658
|
+
}
|
|
3659
|
+
if (errors.length > 0) {
|
|
3660
|
+
console.log();
|
|
3661
|
+
for (const e of errors) {
|
|
3662
|
+
console.log(` ${pc9.red("\u2717")} ${e.key}: ${pc9.dim(e.error)}`);
|
|
3663
|
+
}
|
|
3664
|
+
}
|
|
3665
|
+
}
|
|
3666
|
+
|
|
3667
|
+
// src/commands/guide.ts
|
|
3668
|
+
import pc10 from "picocolors";
|
|
3445
3669
|
|
|
3446
3670
|
// src/lib/guide-content.ts
|
|
3447
3671
|
var GUIDE_OVERVIEW = `# One CLI \u2014 Agent Guide
|
|
@@ -3532,6 +3756,7 @@ Request specific sections:
|
|
|
3532
3756
|
- \`one guide actions\` \u2014 Actions reference (search, knowledge, execute)
|
|
3533
3757
|
- \`one guide flows\` \u2014 Workflow engine reference (step types, selectors, examples)
|
|
3534
3758
|
- \`one guide relay\` \u2014 Webhook relay reference (templates, passthrough actions)
|
|
3759
|
+
- \`one guide cache\` \u2014 Cache management (TTL, flags, commands)
|
|
3535
3760
|
- \`one guide all\` \u2014 Everything
|
|
3536
3761
|
|
|
3537
3762
|
## Important Notes
|
|
@@ -3683,11 +3908,93 @@ Any connected platform can be a destination via passthrough actions.
|
|
|
3683
3908
|
3. \`relay deliveries --event-id <id>\` \u2014 check delivery status and errors
|
|
3684
3909
|
4. \`relay event <id>\` \u2014 inspect full payload to verify template paths
|
|
3685
3910
|
`;
|
|
3911
|
+
var GUIDE_CACHE = `# One Cache \u2014 Reference
|
|
3912
|
+
|
|
3913
|
+
## Overview
|
|
3914
|
+
|
|
3915
|
+
The One CLI caches \`actions knowledge\` and \`actions search\` responses locally so repeated calls serve instantly from disk instead of hitting the API. This is the single biggest latency win for agents who call knowledge for the same actions repeatedly.
|
|
3916
|
+
|
|
3917
|
+
Cache location: \`~/.one/cache/knowledge/\` and \`~/.one/cache/search/\`
|
|
3918
|
+
|
|
3919
|
+
## How It Works
|
|
3920
|
+
|
|
3921
|
+
- **First call**: fetches from the API, writes to cache, serves the response
|
|
3922
|
+
- **Subsequent calls (within TTL)**: serves from cache instantly, no API call
|
|
3923
|
+
- **After TTL expires**: makes a conditional request (ETag). If content unchanged, refreshes the cache timestamp. If changed, writes fresh data.
|
|
3924
|
+
- **Network failure with stale cache**: serves the stale cache with a warning \u2014 never fails hard when a cache exists
|
|
3925
|
+
|
|
3926
|
+
Default TTL: 3600 seconds (1 hour). Configure via \`ONE_CACHE_TTL\` env var or \`cacheTtl\` in \`~/.one/config.json\`.
|
|
3927
|
+
|
|
3928
|
+
## What Gets Cached
|
|
3929
|
+
|
|
3930
|
+
| Cached | Not Cached |
|
|
3931
|
+
|--------|-----------|
|
|
3932
|
+
| \`actions knowledge\` (API docs, change infrequently) | \`actions execute\` (live data, always fresh) |
|
|
3933
|
+
| \`actions search\` results | \`connection list\` (changes with add/remove) |
|
|
3934
|
+
|
|
3935
|
+
## Agent Mode \`_cache\` Metadata
|
|
3936
|
+
|
|
3937
|
+
In \`--agent\` mode, knowledge and search responses include a \`_cache\` field:
|
|
3938
|
+
|
|
3939
|
+
\`\`\`json
|
|
3940
|
+
{
|
|
3941
|
+
"knowledge": "...",
|
|
3942
|
+
"method": "POST",
|
|
3943
|
+
"_cache": {
|
|
3944
|
+
"hit": true,
|
|
3945
|
+
"age": 1423,
|
|
3946
|
+
"fresh": true
|
|
3947
|
+
}
|
|
3948
|
+
}
|
|
3949
|
+
\`\`\`
|
|
3950
|
+
|
|
3951
|
+
Use this to programmatically decide whether to force-refresh.
|
|
3952
|
+
|
|
3953
|
+
## Cache Flags
|
|
3954
|
+
|
|
3955
|
+
\`\`\`bash
|
|
3956
|
+
# Skip cache, fetch fresh (result still gets cached for next time)
|
|
3957
|
+
one --agent actions knowledge <platform> <actionId> --no-cache
|
|
3958
|
+
|
|
3959
|
+
# Check cache status without fetching
|
|
3960
|
+
one --agent actions knowledge <platform> <actionId> --cache-status
|
|
3961
|
+
|
|
3962
|
+
# Same for search
|
|
3963
|
+
one --agent actions search <platform> "<query>" --no-cache
|
|
3964
|
+
\`\`\`
|
|
3965
|
+
|
|
3966
|
+
## Cache Management Commands
|
|
3967
|
+
|
|
3968
|
+
\`\`\`bash
|
|
3969
|
+
one cache list # List all cached entries with age and status
|
|
3970
|
+
one cache list --expired # List only expired entries
|
|
3971
|
+
one cache clear # Delete all cached knowledge and search data
|
|
3972
|
+
one cache clear <actionId> # Delete one specific entry
|
|
3973
|
+
one cache update-all # Re-fetch fresh data for all cached entries
|
|
3974
|
+
\`\`\`
|
|
3975
|
+
|
|
3976
|
+
All cache commands respect \`--agent\` for JSON output.
|
|
3977
|
+
|
|
3978
|
+
## When to Force-Refresh
|
|
3979
|
+
|
|
3980
|
+
- After a platform updates its API docs (rare)
|
|
3981
|
+
- If you suspect stale data is causing issues
|
|
3982
|
+
- Use \`one cache update-all\` to proactively warm the entire cache
|
|
3983
|
+
|
|
3984
|
+
## Configuration
|
|
3985
|
+
|
|
3986
|
+
| Setting | Source | Example |
|
|
3987
|
+
|---------|--------|---------|
|
|
3988
|
+
| TTL (seconds) | \`ONE_CACHE_TTL\` env var | \`ONE_CACHE_TTL=7200\` |
|
|
3989
|
+
| TTL (seconds) | \`cacheTtl\` in \`~/.one/config.json\` | \`"cacheTtl": 7200\` |
|
|
3990
|
+
| Default | \u2014 | 3600 (1 hour) |
|
|
3991
|
+
`;
|
|
3686
3992
|
var TOPICS = [
|
|
3687
3993
|
{ topic: "overview", description: "Setup, features, and quick start for each" },
|
|
3688
3994
|
{ topic: "actions", description: "Search, read docs, and execute platform actions" },
|
|
3689
3995
|
{ topic: "flows", description: "Build and execute multi-step workflows" },
|
|
3690
3996
|
{ topic: "relay", description: "Receive webhooks and forward to other platforms" },
|
|
3997
|
+
{ topic: "cache", description: "Local caching for knowledge and search responses" },
|
|
3691
3998
|
{ topic: "all", description: "Complete guide (all topics combined)" }
|
|
3692
3999
|
];
|
|
3693
4000
|
function getGuideContent(topic) {
|
|
@@ -3700,10 +4007,12 @@ function getGuideContent(topic) {
|
|
|
3700
4007
|
return { title: "One CLI \u2014 Agent Guide: Workflows", content: GUIDE_FLOWS };
|
|
3701
4008
|
case "relay":
|
|
3702
4009
|
return { title: "One CLI \u2014 Agent Guide: Relay", content: GUIDE_RELAY };
|
|
4010
|
+
case "cache":
|
|
4011
|
+
return { title: "One CLI \u2014 Agent Guide: Cache", content: GUIDE_CACHE };
|
|
3703
4012
|
case "all":
|
|
3704
4013
|
return {
|
|
3705
4014
|
title: "One CLI \u2014 Agent Guide: Complete",
|
|
3706
|
-
content: [GUIDE_OVERVIEW, GUIDE_ACTIONS, GUIDE_FLOWS, GUIDE_RELAY].join("\n---\n\n")
|
|
4015
|
+
content: [GUIDE_OVERVIEW, GUIDE_ACTIONS, GUIDE_FLOWS, GUIDE_RELAY, GUIDE_CACHE].join("\n---\n\n")
|
|
3707
4016
|
};
|
|
3708
4017
|
}
|
|
3709
4018
|
}
|
|
@@ -3712,7 +4021,7 @@ function getAvailableTopics() {
|
|
|
3712
4021
|
}
|
|
3713
4022
|
|
|
3714
4023
|
// src/commands/guide.ts
|
|
3715
|
-
var VALID_TOPICS = ["overview", "actions", "flows", "relay", "all"];
|
|
4024
|
+
var VALID_TOPICS = ["overview", "actions", "flows", "relay", "cache", "all"];
|
|
3716
4025
|
async function guideCommand(topic = "all") {
|
|
3717
4026
|
if (!VALID_TOPICS.includes(topic)) {
|
|
3718
4027
|
error(
|
|
@@ -3725,14 +4034,14 @@ async function guideCommand(topic = "all") {
|
|
|
3725
4034
|
json({ topic, title, content, availableTopics });
|
|
3726
4035
|
return;
|
|
3727
4036
|
}
|
|
3728
|
-
intro2(
|
|
4037
|
+
intro2(pc10.bgCyan(pc10.black(" One Guide ")));
|
|
3729
4038
|
console.log();
|
|
3730
4039
|
console.log(content);
|
|
3731
|
-
console.log(
|
|
4040
|
+
console.log(pc10.dim("\u2500".repeat(60)));
|
|
3732
4041
|
console.log(
|
|
3733
|
-
|
|
4042
|
+
pc10.dim("Available topics: ") + availableTopics.map((t) => pc10.cyan(t.topic)).join(", ")
|
|
3734
4043
|
);
|
|
3735
|
-
console.log(
|
|
4044
|
+
console.log(pc10.dim(`Run ${pc10.cyan("one guide <topic>")} for a specific section.`));
|
|
3736
4045
|
}
|
|
3737
4046
|
|
|
3738
4047
|
// src/lib/platform-meta.ts
|
|
@@ -4075,14 +4384,14 @@ async function fetchLatestVersionInfo() {
|
|
|
4075
4384
|
return null;
|
|
4076
4385
|
}
|
|
4077
4386
|
}
|
|
4078
|
-
function
|
|
4387
|
+
function readCache3() {
|
|
4079
4388
|
try {
|
|
4080
4389
|
return JSON.parse(readFileSync(CACHE_PATH, "utf8"));
|
|
4081
4390
|
} catch {
|
|
4082
4391
|
return null;
|
|
4083
4392
|
}
|
|
4084
4393
|
}
|
|
4085
|
-
function
|
|
4394
|
+
function writeCache2(latestVersion, publishedAt) {
|
|
4086
4395
|
try {
|
|
4087
4396
|
mkdirSync(join(homedir(), ".one"), { recursive: true });
|
|
4088
4397
|
writeFileSync(CACHE_PATH, JSON.stringify({ lastCheck: Date.now(), latestVersion, publishedAt }));
|
|
@@ -4091,16 +4400,16 @@ function writeCache(latestVersion, publishedAt) {
|
|
|
4091
4400
|
}
|
|
4092
4401
|
async function checkLatestVersion() {
|
|
4093
4402
|
const info = await fetchLatestVersionInfo();
|
|
4094
|
-
if (info)
|
|
4403
|
+
if (info) writeCache2(info.version, info.publishedAt);
|
|
4095
4404
|
return info?.version ?? null;
|
|
4096
4405
|
}
|
|
4097
4406
|
async function checkLatestVersionCached() {
|
|
4098
|
-
const
|
|
4099
|
-
if (
|
|
4100
|
-
return { version:
|
|
4407
|
+
const cache2 = readCache3();
|
|
4408
|
+
if (cache2 && Date.now() - cache2.lastCheck < CHECK_INTERVAL_MS) {
|
|
4409
|
+
return { version: cache2.latestVersion, publishedAt: cache2.publishedAt ?? null };
|
|
4101
4410
|
}
|
|
4102
4411
|
const info = await fetchLatestVersionInfo();
|
|
4103
|
-
if (info)
|
|
4412
|
+
if (info) writeCache2(info.version, info.publishedAt);
|
|
4104
4413
|
return info;
|
|
4105
4414
|
}
|
|
4106
4415
|
function getCurrentVersion() {
|
|
@@ -4182,6 +4491,11 @@ program.name("one").option("--agent", "Machine-readable JSON output (no colors,
|
|
|
4182
4491
|
one flow execute <key> Execute a workflow
|
|
4183
4492
|
one flow validate <key> Validate a flow
|
|
4184
4493
|
|
|
4494
|
+
Cache:
|
|
4495
|
+
one cache list List cached entries with age and status
|
|
4496
|
+
one cache clear Clear all cached knowledge and search data
|
|
4497
|
+
one cache update-all Re-fetch fresh data for all cached entries
|
|
4498
|
+
|
|
4185
4499
|
Webhook Relay:
|
|
4186
4500
|
one relay create Create a relay endpoint for a connection
|
|
4187
4501
|
one relay list List relay endpoints
|
|
@@ -4241,11 +4555,11 @@ program.command("platforms").alias("p").description("List available platforms").
|
|
|
4241
4555
|
await platformsCommand(options);
|
|
4242
4556
|
});
|
|
4243
4557
|
var actions = program.command("actions").alias("a").description("Search, explore, and execute platform actions (workflow: search \u2192 knowledge \u2192 execute)");
|
|
4244
|
-
actions.command("search <platform> <query>").description('Search for actions on a platform (e.g. one actions search gmail "send email")').option("-t, --type <type>", "execute (to run it) or knowledge (to learn about it). Default: knowledge").action(async (platform, query, options) => {
|
|
4558
|
+
actions.command("search <platform> <query>").description('Search for actions on a platform (e.g. one actions search gmail "send email")').option("-t, --type <type>", "execute (to run it) or knowledge (to learn about it). Default: knowledge").option("--no-cache", "Skip cache, fetch fresh from API").action(async (platform, query, options) => {
|
|
4245
4559
|
await actionsSearchCommand(platform, query, options);
|
|
4246
4560
|
});
|
|
4247
|
-
actions.command("knowledge <platform> <actionId>").alias("k").description("Get full docs for an action \u2014 MUST call before execute to know required params").action(async (platform, actionId) => {
|
|
4248
|
-
await actionsKnowledgeCommand(platform, actionId);
|
|
4561
|
+
actions.command("knowledge <platform> <actionId>").alias("k").description("Get full docs for an action \u2014 MUST call before execute to know required params").option("--no-cache", "Skip cache, fetch fresh from API").option("--cache-status", "Print cache metadata without fetching").action(async (platform, actionId, options) => {
|
|
4562
|
+
await actionsKnowledgeCommand(platform, actionId, options);
|
|
4249
4563
|
});
|
|
4250
4564
|
actions.command("execute <platform> <actionId> <connectionKey>").alias("x").description('Execute an action \u2014 pass connectionKey from "one list", actionId from "actions search"').option("-d, --data <json>", "Request body as JSON").option("--path-vars <json>", "Path variables as JSON").option("--query-params <json>", "Query parameters as JSON").option("--headers <json>", "Additional headers as JSON").option("--form-data", "Send as multipart/form-data").option("--form-url-encoded", "Send as application/x-www-form-urlencoded").option("--dry-run", "Show request that would be sent without executing").action(async (platform, actionId, connectionKey, options) => {
|
|
4251
4565
|
await actionsExecuteCommand(platform, actionId, connectionKey, {
|
|
@@ -4311,6 +4625,16 @@ relay.command("deliveries").description("List delivery attempts for an endpoint
|
|
|
4311
4625
|
relay.command("event-types <platform>").description("List supported webhook event types for a platform").action(async (platform) => {
|
|
4312
4626
|
await relayEventTypesCommand(platform);
|
|
4313
4627
|
});
|
|
4628
|
+
var cache = program.command("cache").description("Manage the local knowledge and search cache");
|
|
4629
|
+
cache.command("clear [actionId]").description("Clear all cached data, or a specific action by ID").action(async (actionId) => {
|
|
4630
|
+
await cacheClearCommand(actionId);
|
|
4631
|
+
});
|
|
4632
|
+
cache.command("list").alias("ls").description("List all cached entries with age and status").option("--expired", "Show only expired entries").action(async (options) => {
|
|
4633
|
+
await cacheListCommand(options);
|
|
4634
|
+
});
|
|
4635
|
+
cache.command("update-all").description("Re-fetch fresh data for all cached entries").action(async () => {
|
|
4636
|
+
await cacheUpdateAllCommand();
|
|
4637
|
+
});
|
|
4314
4638
|
program.command("guide [topic]").description("Full CLI usage guide for agents (topics: overview, actions, flows, relay, all)").action(async (topic) => {
|
|
4315
4639
|
await guideCommand(topic);
|
|
4316
4640
|
});
|
package/package.json
CHANGED
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:
|