@withone/cli 1.20.3 → 1.22.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 +14 -3
- package/dist/{chunk-DPOG6BQ5.js → chunk-KZOFPEHD.js} +806 -19
- package/dist/flow-runner-CXZ6AWXT.js +28 -0
- package/dist/index.js +448 -798
- package/package.json +1 -1
- package/skills/one/references/flows.md +87 -2
- package/dist/flow-runner-UWZL2FPJ.js +0 -14
package/dist/index.js
CHANGED
|
@@ -1,17 +1,22 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
|
+
FLOW_SCHEMA,
|
|
3
4
|
FlowRunner,
|
|
4
5
|
OneApi,
|
|
5
6
|
TimeoutError,
|
|
6
7
|
buildActionKnowledgeWithGuidance,
|
|
7
8
|
filterByPermissions,
|
|
9
|
+
flowRequiresBash,
|
|
10
|
+
generateFlowGuide,
|
|
11
|
+
getNestedStepsKeys,
|
|
12
|
+
getStepTypeDescriptor,
|
|
8
13
|
isActionAllowed,
|
|
9
14
|
isMethodAllowed,
|
|
10
15
|
listFlows,
|
|
11
|
-
|
|
16
|
+
loadFlowWithMeta,
|
|
12
17
|
resolveFlowPath,
|
|
13
18
|
saveFlow
|
|
14
|
-
} from "./chunk-
|
|
19
|
+
} from "./chunk-KZOFPEHD.js";
|
|
15
20
|
|
|
16
21
|
// src/index.ts
|
|
17
22
|
import { createRequire as createRequire2 } from "module";
|
|
@@ -20,10 +25,10 @@ import { Command } from "commander";
|
|
|
20
25
|
// src/commands/init.ts
|
|
21
26
|
import * as p3 from "@clack/prompts";
|
|
22
27
|
import pc2 from "picocolors";
|
|
23
|
-
import
|
|
24
|
-
import
|
|
25
|
-
import
|
|
26
|
-
import { fileURLToPath } from "url";
|
|
28
|
+
import fs4 from "fs";
|
|
29
|
+
import path4 from "path";
|
|
30
|
+
import os4 from "os";
|
|
31
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
27
32
|
|
|
28
33
|
// src/lib/config.ts
|
|
29
34
|
import fs from "fs";
|
|
@@ -48,11 +53,11 @@ function readConfig() {
|
|
|
48
53
|
return null;
|
|
49
54
|
}
|
|
50
55
|
}
|
|
51
|
-
function writeConfig(
|
|
56
|
+
function writeConfig(config2) {
|
|
52
57
|
if (!fs.existsSync(CONFIG_DIR)) {
|
|
53
58
|
fs.mkdirSync(CONFIG_DIR, { mode: 448 });
|
|
54
59
|
}
|
|
55
|
-
fs.writeFileSync(CONFIG_FILE, JSON.stringify(
|
|
60
|
+
fs.writeFileSync(CONFIG_FILE, JSON.stringify(config2, null, 2), { mode: 384 });
|
|
56
61
|
}
|
|
57
62
|
function readOneRc() {
|
|
58
63
|
const rcPath = path.join(process.cwd(), ".onerc");
|
|
@@ -103,32 +108,32 @@ function getAccessControl() {
|
|
|
103
108
|
}
|
|
104
109
|
var DEFAULT_API_BASE = "https://api.withone.ai/v1";
|
|
105
110
|
function getApiBase() {
|
|
106
|
-
const
|
|
107
|
-
if (
|
|
111
|
+
const config2 = readConfig();
|
|
112
|
+
if (config2?.apiBase) return `${config2.apiBase}/v1`;
|
|
108
113
|
return DEFAULT_API_BASE;
|
|
109
114
|
}
|
|
110
115
|
function updateApiBase(url) {
|
|
111
|
-
const
|
|
112
|
-
if (!
|
|
116
|
+
const config2 = readConfig();
|
|
117
|
+
if (!config2) return;
|
|
113
118
|
if (url) {
|
|
114
|
-
|
|
119
|
+
config2.apiBase = url;
|
|
115
120
|
} else {
|
|
116
|
-
delete
|
|
121
|
+
delete config2.apiBase;
|
|
117
122
|
}
|
|
118
|
-
writeConfig(
|
|
123
|
+
writeConfig(config2);
|
|
119
124
|
}
|
|
120
125
|
function getCacheTtl() {
|
|
121
126
|
if (process.env.ONE_CACHE_TTL) {
|
|
122
127
|
const val = parseInt(process.env.ONE_CACHE_TTL, 10);
|
|
123
128
|
if (!isNaN(val) && val > 0) return val;
|
|
124
129
|
}
|
|
125
|
-
const
|
|
126
|
-
if (
|
|
130
|
+
const config2 = readConfig();
|
|
131
|
+
if (config2?.cacheTtl && config2.cacheTtl > 0) return config2.cacheTtl;
|
|
127
132
|
return 3600;
|
|
128
133
|
}
|
|
129
134
|
function updateAccessControl(settings) {
|
|
130
|
-
const
|
|
131
|
-
if (!
|
|
135
|
+
const config2 = readConfig();
|
|
136
|
+
if (!config2) return;
|
|
132
137
|
const cleaned = {};
|
|
133
138
|
if (settings.permissions && settings.permissions !== "admin") {
|
|
134
139
|
cleaned.permissions = settings.permissions;
|
|
@@ -143,11 +148,11 @@ function updateAccessControl(settings) {
|
|
|
143
148
|
cleaned.knowledgeAgent = true;
|
|
144
149
|
}
|
|
145
150
|
if (Object.keys(cleaned).length === 0) {
|
|
146
|
-
delete
|
|
151
|
+
delete config2.accessControl;
|
|
147
152
|
} else {
|
|
148
|
-
|
|
153
|
+
config2.accessControl = cleaned;
|
|
149
154
|
}
|
|
150
|
-
writeConfig(
|
|
155
|
+
writeConfig(config2);
|
|
151
156
|
}
|
|
152
157
|
|
|
153
158
|
// src/lib/agents.ts
|
|
@@ -269,16 +274,16 @@ function readAgentConfig(agent, scope = "global") {
|
|
|
269
274
|
return {};
|
|
270
275
|
}
|
|
271
276
|
}
|
|
272
|
-
function writeAgentConfig(agent,
|
|
277
|
+
function writeAgentConfig(agent, config2, scope = "global") {
|
|
273
278
|
const configPath = getAgentConfigPath(agent, scope);
|
|
274
279
|
const configDir = path2.dirname(configPath);
|
|
275
280
|
if (!fs2.existsSync(configDir)) {
|
|
276
281
|
fs2.mkdirSync(configDir, { recursive: true });
|
|
277
282
|
}
|
|
278
283
|
if (agent.configFormat === "toml") {
|
|
279
|
-
fs2.writeFileSync(configPath, stringifyToml(
|
|
284
|
+
fs2.writeFileSync(configPath, stringifyToml(config2));
|
|
280
285
|
} else {
|
|
281
|
-
fs2.writeFileSync(configPath, JSON.stringify(
|
|
286
|
+
fs2.writeFileSync(configPath, JSON.stringify(config2, null, 2));
|
|
282
287
|
}
|
|
283
288
|
}
|
|
284
289
|
function getMcpServerConfig(apiKey, accessControl) {
|
|
@@ -306,17 +311,17 @@ function getMcpServerConfig(apiKey, accessControl) {
|
|
|
306
311
|
};
|
|
307
312
|
}
|
|
308
313
|
function installMcpConfig(agent, apiKey, scope = "global", accessControl) {
|
|
309
|
-
const
|
|
314
|
+
const config2 = readAgentConfig(agent, scope);
|
|
310
315
|
const configKey = agent.configKey;
|
|
311
|
-
const mcpServers =
|
|
316
|
+
const mcpServers = config2[configKey] || {};
|
|
312
317
|
mcpServers["one"] = getMcpServerConfig(apiKey, accessControl);
|
|
313
|
-
|
|
314
|
-
writeAgentConfig(agent,
|
|
318
|
+
config2[configKey] = mcpServers;
|
|
319
|
+
writeAgentConfig(agent, config2, scope);
|
|
315
320
|
}
|
|
316
321
|
function isMcpInstalled(agent, scope = "global") {
|
|
317
|
-
const
|
|
322
|
+
const config2 = readAgentConfig(agent, scope);
|
|
318
323
|
const configKey = agent.configKey;
|
|
319
|
-
const mcpServers =
|
|
324
|
+
const mcpServers = config2[configKey];
|
|
320
325
|
return mcpServers?.["one"] !== void 0;
|
|
321
326
|
}
|
|
322
327
|
function getAgentStatuses() {
|
|
@@ -392,8 +397,8 @@ async function configCommand() {
|
|
|
392
397
|
if (isAgentMode()) {
|
|
393
398
|
error("This command requires interactive input. Run without --agent.");
|
|
394
399
|
}
|
|
395
|
-
const
|
|
396
|
-
if (!
|
|
400
|
+
const config2 = readConfig();
|
|
401
|
+
if (!config2) {
|
|
397
402
|
p2.log.error(`No One config found. Run ${pc.cyan("one init")} first.`);
|
|
398
403
|
return;
|
|
399
404
|
}
|
|
@@ -434,7 +439,7 @@ async function configCommand() {
|
|
|
434
439
|
}
|
|
435
440
|
let connectionKeys;
|
|
436
441
|
if (connectionMode === "specific") {
|
|
437
|
-
connectionKeys = await selectConnections(
|
|
442
|
+
connectionKeys = await selectConnections(config2.apiKey);
|
|
438
443
|
if (connectionKeys === void 0) {
|
|
439
444
|
p2.outro("No changes made.");
|
|
440
445
|
return;
|
|
@@ -495,7 +500,7 @@ async function configCommand() {
|
|
|
495
500
|
p2.outro("No changes made.");
|
|
496
501
|
return;
|
|
497
502
|
}
|
|
498
|
-
let newApiKey =
|
|
503
|
+
let newApiKey = config2.apiKey;
|
|
499
504
|
if (baseUrlMode === "custom") {
|
|
500
505
|
const customUrl = await p2.text({
|
|
501
506
|
message: "Enter API base URL:",
|
|
@@ -596,7 +601,7 @@ async function configCommand() {
|
|
|
596
601
|
};
|
|
597
602
|
updateAccessControl(settings);
|
|
598
603
|
const updatedConfig = readConfig();
|
|
599
|
-
if (updatedConfig && newApiKey !==
|
|
604
|
+
if (updatedConfig && newApiKey !== config2.apiKey) {
|
|
600
605
|
updatedConfig.apiKey = newApiKey;
|
|
601
606
|
writeConfig(updatedConfig);
|
|
602
607
|
}
|
|
@@ -662,6 +667,214 @@ function formatList(list) {
|
|
|
662
667
|
|
|
663
668
|
// src/commands/init.ts
|
|
664
669
|
import open2 from "open";
|
|
670
|
+
|
|
671
|
+
// src/lib/skill-sync.ts
|
|
672
|
+
import fs3 from "fs";
|
|
673
|
+
import os3 from "os";
|
|
674
|
+
import path3 from "path";
|
|
675
|
+
import { fileURLToPath } from "url";
|
|
676
|
+
|
|
677
|
+
// src/commands/update.ts
|
|
678
|
+
import { createRequire } from "module";
|
|
679
|
+
import { spawn } from "child_process";
|
|
680
|
+
import { readFileSync, writeFileSync, mkdirSync } from "fs";
|
|
681
|
+
import { homedir } from "os";
|
|
682
|
+
import { join } from "path";
|
|
683
|
+
var require2 = createRequire(import.meta.url);
|
|
684
|
+
var { version: currentVersion } = require2("../package.json");
|
|
685
|
+
var CACHE_PATH = join(homedir(), ".one", "update-check.json");
|
|
686
|
+
var CHECK_INTERVAL_MS = 4 * 60 * 60 * 1e3;
|
|
687
|
+
var AGE_GATE_MS = 30 * 60 * 1e3;
|
|
688
|
+
async function fetchLatestVersionInfo() {
|
|
689
|
+
try {
|
|
690
|
+
const res = await fetch("https://registry.npmjs.org/@withone/cli");
|
|
691
|
+
if (!res.ok) return null;
|
|
692
|
+
const data = await res.json();
|
|
693
|
+
const latest = data["dist-tags"]?.latest;
|
|
694
|
+
if (!latest) return null;
|
|
695
|
+
return { version: latest, publishedAt: data.time?.[latest] ?? null };
|
|
696
|
+
} catch {
|
|
697
|
+
return null;
|
|
698
|
+
}
|
|
699
|
+
}
|
|
700
|
+
function readCache() {
|
|
701
|
+
try {
|
|
702
|
+
return JSON.parse(readFileSync(CACHE_PATH, "utf8"));
|
|
703
|
+
} catch {
|
|
704
|
+
return null;
|
|
705
|
+
}
|
|
706
|
+
}
|
|
707
|
+
function writeCache(latestVersion, publishedAt) {
|
|
708
|
+
try {
|
|
709
|
+
mkdirSync(join(homedir(), ".one"), { recursive: true });
|
|
710
|
+
writeFileSync(CACHE_PATH, JSON.stringify({ lastCheck: Date.now(), latestVersion, publishedAt }));
|
|
711
|
+
} catch {
|
|
712
|
+
}
|
|
713
|
+
}
|
|
714
|
+
async function checkLatestVersion() {
|
|
715
|
+
const info = await fetchLatestVersionInfo();
|
|
716
|
+
if (info) writeCache(info.version, info.publishedAt);
|
|
717
|
+
return info?.version ?? null;
|
|
718
|
+
}
|
|
719
|
+
async function checkLatestVersionCached() {
|
|
720
|
+
const cache2 = readCache();
|
|
721
|
+
if (cache2 && Date.now() - cache2.lastCheck < CHECK_INTERVAL_MS) {
|
|
722
|
+
return { version: cache2.latestVersion, publishedAt: cache2.publishedAt ?? null };
|
|
723
|
+
}
|
|
724
|
+
const info = await fetchLatestVersionInfo();
|
|
725
|
+
if (info) writeCache(info.version, info.publishedAt);
|
|
726
|
+
return info;
|
|
727
|
+
}
|
|
728
|
+
function getCurrentVersion() {
|
|
729
|
+
return currentVersion;
|
|
730
|
+
}
|
|
731
|
+
async function updateCommand() {
|
|
732
|
+
const s = createSpinner();
|
|
733
|
+
s.start("Checking for updates...");
|
|
734
|
+
const latestVersion = await checkLatestVersion();
|
|
735
|
+
if (!latestVersion) {
|
|
736
|
+
s.stop("");
|
|
737
|
+
error("Failed to check for updates \u2014 could not reach npm registry");
|
|
738
|
+
}
|
|
739
|
+
if (currentVersion === latestVersion) {
|
|
740
|
+
s.stop("Already up to date");
|
|
741
|
+
if (isAgentMode()) {
|
|
742
|
+
json({ current: currentVersion, latest: latestVersion, updated: false, message: "Already up to date" });
|
|
743
|
+
} else {
|
|
744
|
+
console.log(`Already up to date (v${currentVersion})`);
|
|
745
|
+
}
|
|
746
|
+
return;
|
|
747
|
+
}
|
|
748
|
+
s.stop(`Update available: v${currentVersion} \u2192 v${latestVersion}`);
|
|
749
|
+
console.log(`Updating @withone/cli: v${currentVersion} \u2192 v${latestVersion}...`);
|
|
750
|
+
const code = await new Promise((resolve) => {
|
|
751
|
+
const child = spawn("npm", ["install", "-g", "@withone/cli@latest", "--force"], {
|
|
752
|
+
stdio: isAgentMode() ? "pipe" : "inherit",
|
|
753
|
+
shell: true
|
|
754
|
+
});
|
|
755
|
+
child.on("close", resolve);
|
|
756
|
+
child.on("error", () => resolve(1));
|
|
757
|
+
});
|
|
758
|
+
if (code === 0) {
|
|
759
|
+
if (isAgentMode()) {
|
|
760
|
+
json({ current: currentVersion, latest: latestVersion, updated: true, message: "Updated successfully" });
|
|
761
|
+
} else {
|
|
762
|
+
console.log(`Successfully updated to v${latestVersion}`);
|
|
763
|
+
}
|
|
764
|
+
} else {
|
|
765
|
+
error("Update failed \u2014 try running: npm install -g @withone/cli@latest");
|
|
766
|
+
}
|
|
767
|
+
}
|
|
768
|
+
function isNewerVersion(latest, current) {
|
|
769
|
+
const parse = (v) => v.split(".").map(Number);
|
|
770
|
+
const [lMaj, lMin, lPat] = parse(latest);
|
|
771
|
+
const [cMaj, cMin, cPat] = parse(current);
|
|
772
|
+
if (lMaj !== cMaj) return lMaj > cMaj;
|
|
773
|
+
if (lMin !== cMin) return lMin > cMin;
|
|
774
|
+
return lPat > cPat;
|
|
775
|
+
}
|
|
776
|
+
function autoUpdate(targetVersion, publishedAt) {
|
|
777
|
+
if (publishedAt) {
|
|
778
|
+
const age = Date.now() - new Date(publishedAt).getTime();
|
|
779
|
+
if (age < AGE_GATE_MS) return;
|
|
780
|
+
}
|
|
781
|
+
const child = spawn("npm", ["install", "-g", `@withone/cli@${targetVersion}`], {
|
|
782
|
+
detached: true,
|
|
783
|
+
stdio: "ignore",
|
|
784
|
+
shell: true
|
|
785
|
+
});
|
|
786
|
+
child.unref();
|
|
787
|
+
}
|
|
788
|
+
|
|
789
|
+
// src/lib/skill-sync.ts
|
|
790
|
+
var CANONICAL_SKILL_DIR = ".agents/skills";
|
|
791
|
+
var VERSION_MARKER = ".one-cli-version";
|
|
792
|
+
function getPackagedSkillDir() {
|
|
793
|
+
const here = path3.dirname(fileURLToPath(import.meta.url));
|
|
794
|
+
return path3.resolve(here, "..", "skills", "one");
|
|
795
|
+
}
|
|
796
|
+
function getCanonicalSkillPath() {
|
|
797
|
+
return path3.join(os3.homedir(), CANONICAL_SKILL_DIR, "one");
|
|
798
|
+
}
|
|
799
|
+
function getVersionMarkerPath() {
|
|
800
|
+
return path3.join(getCanonicalSkillPath(), VERSION_MARKER);
|
|
801
|
+
}
|
|
802
|
+
function isSkillInstalled() {
|
|
803
|
+
return fs3.existsSync(path3.join(getCanonicalSkillPath(), "SKILL.md"));
|
|
804
|
+
}
|
|
805
|
+
function readInstalledSkillVersion() {
|
|
806
|
+
try {
|
|
807
|
+
return fs3.readFileSync(getVersionMarkerPath(), "utf-8").trim() || null;
|
|
808
|
+
} catch {
|
|
809
|
+
return null;
|
|
810
|
+
}
|
|
811
|
+
}
|
|
812
|
+
function writeInstalledSkillVersion(version2) {
|
|
813
|
+
try {
|
|
814
|
+
fs3.mkdirSync(getCanonicalSkillPath(), { recursive: true });
|
|
815
|
+
fs3.writeFileSync(getVersionMarkerPath(), `${version2}
|
|
816
|
+
`);
|
|
817
|
+
} catch {
|
|
818
|
+
}
|
|
819
|
+
}
|
|
820
|
+
function copyDirSync(src, dest) {
|
|
821
|
+
fs3.mkdirSync(dest, { recursive: true });
|
|
822
|
+
for (const entry of fs3.readdirSync(src, { withFileTypes: true })) {
|
|
823
|
+
const srcPath = path3.join(src, entry.name);
|
|
824
|
+
const destPath = path3.join(dest, entry.name);
|
|
825
|
+
if (entry.isDirectory()) {
|
|
826
|
+
copyDirSync(srcPath, destPath);
|
|
827
|
+
} else {
|
|
828
|
+
fs3.copyFileSync(srcPath, destPath);
|
|
829
|
+
}
|
|
830
|
+
}
|
|
831
|
+
}
|
|
832
|
+
function syncSkillsIfStale() {
|
|
833
|
+
if (!isSkillInstalled()) {
|
|
834
|
+
return { synced: false, reason: "not-installed" };
|
|
835
|
+
}
|
|
836
|
+
const current = getCurrentVersion();
|
|
837
|
+
const installed = readInstalledSkillVersion();
|
|
838
|
+
if (installed === current) {
|
|
839
|
+
return { synced: false, reason: "up-to-date", from: installed, to: current };
|
|
840
|
+
}
|
|
841
|
+
return performSync(current, installed === null ? "missing-marker" : "stale");
|
|
842
|
+
}
|
|
843
|
+
function forceSyncSkills() {
|
|
844
|
+
if (!isSkillInstalled()) {
|
|
845
|
+
return { synced: false, reason: "not-installed" };
|
|
846
|
+
}
|
|
847
|
+
return performSync(getCurrentVersion(), "forced");
|
|
848
|
+
}
|
|
849
|
+
function performSync(current, reason) {
|
|
850
|
+
const source = getPackagedSkillDir();
|
|
851
|
+
if (!fs3.existsSync(path3.join(source, "SKILL.md"))) {
|
|
852
|
+
return { synced: false, reason: "source-missing" };
|
|
853
|
+
}
|
|
854
|
+
const canonical = getCanonicalSkillPath();
|
|
855
|
+
try {
|
|
856
|
+
copyDirSync(source, canonical);
|
|
857
|
+
writeInstalledSkillVersion(current);
|
|
858
|
+
return { synced: true, reason, from: readInstalledSkillVersion() ?? null, to: current };
|
|
859
|
+
} catch (err) {
|
|
860
|
+
return { synced: false, reason: "error", error: err instanceof Error ? err.message : String(err) };
|
|
861
|
+
}
|
|
862
|
+
}
|
|
863
|
+
function getSkillStatus() {
|
|
864
|
+
const installed = isSkillInstalled();
|
|
865
|
+
const installedVersion = readInstalledSkillVersion();
|
|
866
|
+
const currentVersion2 = getCurrentVersion();
|
|
867
|
+
return {
|
|
868
|
+
installed,
|
|
869
|
+
canonicalPath: getCanonicalSkillPath(),
|
|
870
|
+
installedVersion,
|
|
871
|
+
currentVersion: currentVersion2,
|
|
872
|
+
upToDate: installed && installedVersion === currentVersion2,
|
|
873
|
+
markerExists: installedVersion !== null
|
|
874
|
+
};
|
|
875
|
+
}
|
|
876
|
+
|
|
877
|
+
// src/commands/init.ts
|
|
665
878
|
async function initCommand(options) {
|
|
666
879
|
if (isAgentMode()) {
|
|
667
880
|
error("This command requires interactive input. Run without --agent.");
|
|
@@ -677,7 +890,7 @@ async function initCommand(options) {
|
|
|
677
890
|
async function handleExistingConfig(apiKey, options) {
|
|
678
891
|
const statuses = getAgentStatuses();
|
|
679
892
|
const masked = maskApiKey(apiKey);
|
|
680
|
-
const skillInstalled =
|
|
893
|
+
const skillInstalled = isSkillInstalled2();
|
|
681
894
|
console.log();
|
|
682
895
|
console.log(` ${pc2.bold("Current Setup")}`);
|
|
683
896
|
console.log(` ${pc2.dim("\u2500".repeat(42))}`);
|
|
@@ -813,12 +1026,12 @@ ${pc2.cyan(getApiKeyUrl())}`, "API Key");
|
|
|
813
1026
|
reinstalled.push(`${s.agent.name} (project)`);
|
|
814
1027
|
}
|
|
815
1028
|
}
|
|
816
|
-
const
|
|
1029
|
+
const config2 = readConfig();
|
|
817
1030
|
writeConfig({
|
|
818
1031
|
apiKey: newKey,
|
|
819
|
-
installedAgents:
|
|
820
|
-
createdAt:
|
|
821
|
-
accessControl:
|
|
1032
|
+
installedAgents: config2?.installedAgents ?? [],
|
|
1033
|
+
createdAt: config2?.createdAt ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
1034
|
+
accessControl: config2?.accessControl
|
|
822
1035
|
});
|
|
823
1036
|
if (reinstalled.length > 0) {
|
|
824
1037
|
p3.log.success(`Updated MCP configs: ${reinstalled.join(", ")}`);
|
|
@@ -837,48 +1050,49 @@ var SKILL_AGENTS = [
|
|
|
837
1050
|
{ id: "opencode", name: "OpenCode", skillDir: ".opencode/skills" },
|
|
838
1051
|
{ id: "roo", name: "Roo", skillDir: ".roo/skills" }
|
|
839
1052
|
];
|
|
840
|
-
var
|
|
1053
|
+
var CANONICAL_SKILL_DIR2 = ".agents/skills";
|
|
841
1054
|
function getSkillSourceDir() {
|
|
842
|
-
const __dirname2 =
|
|
843
|
-
return
|
|
1055
|
+
const __dirname2 = path4.dirname(fileURLToPath2(import.meta.url));
|
|
1056
|
+
return path4.resolve(__dirname2, "..", "skills", "one");
|
|
844
1057
|
}
|
|
845
|
-
function
|
|
846
|
-
return
|
|
1058
|
+
function getCanonicalSkillPath2() {
|
|
1059
|
+
return path4.join(os4.homedir(), CANONICAL_SKILL_DIR2, "one");
|
|
847
1060
|
}
|
|
848
1061
|
function getAgentSkillPath(agent) {
|
|
849
|
-
return
|
|
1062
|
+
return path4.join(os4.homedir(), agent.skillDir, "one");
|
|
850
1063
|
}
|
|
851
|
-
function
|
|
852
|
-
return
|
|
1064
|
+
function isSkillInstalled2() {
|
|
1065
|
+
return fs4.existsSync(path4.join(getCanonicalSkillPath2(), "SKILL.md"));
|
|
853
1066
|
}
|
|
854
1067
|
function isSkillInstalledForAgent(agent) {
|
|
855
|
-
return
|
|
1068
|
+
return fs4.existsSync(path4.join(getAgentSkillPath(agent), "SKILL.md"));
|
|
856
1069
|
}
|
|
857
|
-
function
|
|
858
|
-
|
|
859
|
-
for (const entry of
|
|
860
|
-
const srcPath =
|
|
861
|
-
const destPath =
|
|
1070
|
+
function copyDirSync2(src, dest) {
|
|
1071
|
+
fs4.mkdirSync(dest, { recursive: true });
|
|
1072
|
+
for (const entry of fs4.readdirSync(src, { withFileTypes: true })) {
|
|
1073
|
+
const srcPath = path4.join(src, entry.name);
|
|
1074
|
+
const destPath = path4.join(dest, entry.name);
|
|
862
1075
|
if (entry.isDirectory()) {
|
|
863
|
-
|
|
1076
|
+
copyDirSync2(srcPath, destPath);
|
|
864
1077
|
} else {
|
|
865
|
-
|
|
1078
|
+
fs4.copyFileSync(srcPath, destPath);
|
|
866
1079
|
}
|
|
867
1080
|
}
|
|
868
1081
|
}
|
|
869
1082
|
function installSkillForAgents(agentIds) {
|
|
870
1083
|
const source = getSkillSourceDir();
|
|
871
|
-
const canonical =
|
|
1084
|
+
const canonical = getCanonicalSkillPath2();
|
|
872
1085
|
const installed = [];
|
|
873
1086
|
const failed = [];
|
|
874
|
-
if (!
|
|
1087
|
+
if (!fs4.existsSync(path4.join(source, "SKILL.md"))) {
|
|
875
1088
|
return { installed: [], failed: ["skill source not found"] };
|
|
876
1089
|
}
|
|
877
1090
|
try {
|
|
878
|
-
if (
|
|
879
|
-
|
|
1091
|
+
if (fs4.existsSync(canonical)) {
|
|
1092
|
+
fs4.rmSync(canonical, { recursive: true });
|
|
880
1093
|
}
|
|
881
|
-
|
|
1094
|
+
copyDirSync2(source, canonical);
|
|
1095
|
+
writeInstalledSkillVersion(getCurrentVersion());
|
|
882
1096
|
} catch {
|
|
883
1097
|
return { installed: [], failed: ["canonical copy"] };
|
|
884
1098
|
}
|
|
@@ -892,15 +1106,15 @@ function installSkillForAgents(agentIds) {
|
|
|
892
1106
|
continue;
|
|
893
1107
|
}
|
|
894
1108
|
try {
|
|
895
|
-
const agentSkillsDir =
|
|
896
|
-
|
|
1109
|
+
const agentSkillsDir = path4.dirname(agentPath);
|
|
1110
|
+
fs4.mkdirSync(agentSkillsDir, { recursive: true });
|
|
897
1111
|
try {
|
|
898
|
-
|
|
899
|
-
|
|
1112
|
+
fs4.lstatSync(agentPath);
|
|
1113
|
+
fs4.rmSync(agentPath, { recursive: true });
|
|
900
1114
|
} catch {
|
|
901
1115
|
}
|
|
902
|
-
const relative =
|
|
903
|
-
|
|
1116
|
+
const relative = path4.relative(agentSkillsDir, canonical);
|
|
1117
|
+
fs4.symlinkSync(relative, agentPath);
|
|
904
1118
|
installed.push(agent.name);
|
|
905
1119
|
seen.set(agentPath, true);
|
|
906
1120
|
} catch {
|
|
@@ -1548,35 +1762,35 @@ import * as p6 from "@clack/prompts";
|
|
|
1548
1762
|
import pc6 from "picocolors";
|
|
1549
1763
|
|
|
1550
1764
|
// src/lib/cache.ts
|
|
1551
|
-
import
|
|
1552
|
-
import
|
|
1553
|
-
import
|
|
1554
|
-
var CACHE_BASE =
|
|
1555
|
-
var KNOWLEDGE_DIR =
|
|
1556
|
-
var SEARCH_DIR =
|
|
1765
|
+
import fs5 from "fs";
|
|
1766
|
+
import path5 from "path";
|
|
1767
|
+
import os5 from "os";
|
|
1768
|
+
var CACHE_BASE = path5.join(os5.homedir(), ".one", "cache");
|
|
1769
|
+
var KNOWLEDGE_DIR = path5.join(CACHE_BASE, "knowledge");
|
|
1770
|
+
var SEARCH_DIR = path5.join(CACHE_BASE, "search");
|
|
1557
1771
|
function sanitizeFilename(input) {
|
|
1558
1772
|
return input.replace(/[^a-zA-Z0-9_\-\.]/g, "_");
|
|
1559
1773
|
}
|
|
1560
1774
|
function knowledgeCachePath(actionId) {
|
|
1561
|
-
return
|
|
1775
|
+
return path5.join(KNOWLEDGE_DIR, `${sanitizeFilename(actionId)}.json`);
|
|
1562
1776
|
}
|
|
1563
1777
|
function searchCachePath(platform, query, type) {
|
|
1564
1778
|
const key = `${platform}_${sanitizeFilename(query)}_${type || "knowledge"}`;
|
|
1565
|
-
return
|
|
1779
|
+
return path5.join(SEARCH_DIR, `${key}.json`);
|
|
1566
1780
|
}
|
|
1567
|
-
function
|
|
1781
|
+
function readCache2(filePath) {
|
|
1568
1782
|
try {
|
|
1569
|
-
const content =
|
|
1783
|
+
const content = fs5.readFileSync(filePath, "utf-8");
|
|
1570
1784
|
return JSON.parse(content);
|
|
1571
1785
|
} catch {
|
|
1572
1786
|
return null;
|
|
1573
1787
|
}
|
|
1574
1788
|
}
|
|
1575
|
-
function
|
|
1789
|
+
function writeCache2(filePath, entry) {
|
|
1576
1790
|
try {
|
|
1577
|
-
const dir =
|
|
1578
|
-
|
|
1579
|
-
|
|
1791
|
+
const dir = path5.dirname(filePath);
|
|
1792
|
+
fs5.mkdirSync(dir, { recursive: true });
|
|
1793
|
+
fs5.writeFileSync(filePath, JSON.stringify(entry, null, 2));
|
|
1580
1794
|
} catch {
|
|
1581
1795
|
}
|
|
1582
1796
|
}
|
|
@@ -1614,11 +1828,11 @@ function listCacheEntries() {
|
|
|
1614
1828
|
const entries = [];
|
|
1615
1829
|
for (const [dir, type] of [[KNOWLEDGE_DIR, "knowledge"], [SEARCH_DIR, "search"]]) {
|
|
1616
1830
|
try {
|
|
1617
|
-
const files =
|
|
1831
|
+
const files = fs5.readdirSync(dir);
|
|
1618
1832
|
for (const file of files) {
|
|
1619
1833
|
if (!file.endsWith(".json")) continue;
|
|
1620
|
-
const filePath =
|
|
1621
|
-
const entry =
|
|
1834
|
+
const filePath = path5.join(dir, file);
|
|
1835
|
+
const entry = readCache2(filePath);
|
|
1622
1836
|
if (entry) {
|
|
1623
1837
|
entries.push({ type, filePath, entry });
|
|
1624
1838
|
}
|
|
@@ -1632,12 +1846,12 @@ function clearAll() {
|
|
|
1632
1846
|
let count = 0;
|
|
1633
1847
|
for (const dir of [KNOWLEDGE_DIR, SEARCH_DIR]) {
|
|
1634
1848
|
try {
|
|
1635
|
-
const files =
|
|
1849
|
+
const files = fs5.readdirSync(dir);
|
|
1636
1850
|
for (const file of files) {
|
|
1637
|
-
|
|
1851
|
+
fs5.unlinkSync(path5.join(dir, file));
|
|
1638
1852
|
count++;
|
|
1639
1853
|
}
|
|
1640
|
-
|
|
1854
|
+
fs5.rmdirSync(dir);
|
|
1641
1855
|
} catch {
|
|
1642
1856
|
}
|
|
1643
1857
|
}
|
|
@@ -1646,7 +1860,7 @@ function clearAll() {
|
|
|
1646
1860
|
function clearEntry(actionId) {
|
|
1647
1861
|
const filePath = knowledgeCachePath(actionId);
|
|
1648
1862
|
try {
|
|
1649
|
-
|
|
1863
|
+
fs5.unlinkSync(filePath);
|
|
1650
1864
|
return true;
|
|
1651
1865
|
} catch {
|
|
1652
1866
|
return false;
|
|
@@ -1692,7 +1906,7 @@ async function actionsSearchCommand(platform, query, options) {
|
|
|
1692
1906
|
const agentType = knowledgeAgent ? "knowledge" : options.type || "execute";
|
|
1693
1907
|
const useCache = options.cache !== false;
|
|
1694
1908
|
const cachePath = searchCachePath(platform, query, agentType || "knowledge");
|
|
1695
|
-
const cached = useCache ?
|
|
1909
|
+
const cached = useCache ? readCache2(cachePath) : null;
|
|
1696
1910
|
let cleanedActions;
|
|
1697
1911
|
let cacheHit = false;
|
|
1698
1912
|
if (cached && isFresh(cached)) {
|
|
@@ -1708,7 +1922,7 @@ async function actionsSearchCommand(platform, query, options) {
|
|
|
1708
1922
|
);
|
|
1709
1923
|
if (result.status === 304 && cached) {
|
|
1710
1924
|
cached.cachedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
1711
|
-
|
|
1925
|
+
writeCache2(cachePath, cached);
|
|
1712
1926
|
cleanedActions = cached.data.actions;
|
|
1713
1927
|
cacheHit = true;
|
|
1714
1928
|
} else {
|
|
@@ -1721,7 +1935,7 @@ async function actionsSearchCommand(platform, query, options) {
|
|
|
1721
1935
|
method: action.method,
|
|
1722
1936
|
path: action.path
|
|
1723
1937
|
}));
|
|
1724
|
-
|
|
1938
|
+
writeCache2(cachePath, makeCacheEntry(
|
|
1725
1939
|
`${platform}_${query}_${agentType || "knowledge"}`,
|
|
1726
1940
|
{ actions: cleanedActions },
|
|
1727
1941
|
result.etag
|
|
@@ -1745,7 +1959,7 @@ async function actionsSearchCommand(platform, query, options) {
|
|
|
1745
1959
|
if (cacheHit && cached) {
|
|
1746
1960
|
response._cache = buildCacheMeta(cached, true);
|
|
1747
1961
|
} else {
|
|
1748
|
-
const freshEntry =
|
|
1962
|
+
const freshEntry = readCache2(cachePath);
|
|
1749
1963
|
response._cache = buildCacheMeta(freshEntry, false);
|
|
1750
1964
|
}
|
|
1751
1965
|
json(response);
|
|
@@ -1805,7 +2019,7 @@ Execute: ${pc6.cyan(`one actions execute ${platform} <actionId> <connectionK
|
|
|
1805
2019
|
async function actionsKnowledgeCommand(platform, actionId, options) {
|
|
1806
2020
|
const cachePath = knowledgeCachePath(actionId);
|
|
1807
2021
|
if (options.cacheStatus) {
|
|
1808
|
-
const entry =
|
|
2022
|
+
const entry = readCache2(cachePath);
|
|
1809
2023
|
if (!entry) {
|
|
1810
2024
|
json({
|
|
1811
2025
|
cached: false,
|
|
@@ -1853,7 +2067,7 @@ async function actionsKnowledgeCommand(platform, actionId, options) {
|
|
|
1853
2067
|
spinner5.start(`Loading knowledge for action ${pc6.dim(actionId)}...`);
|
|
1854
2068
|
try {
|
|
1855
2069
|
const useCache = options.cache !== false;
|
|
1856
|
-
const cached = useCache ?
|
|
2070
|
+
const cached = useCache ? readCache2(cachePath) : null;
|
|
1857
2071
|
let knowledgeData;
|
|
1858
2072
|
let cacheHit = false;
|
|
1859
2073
|
let cacheEntry = cached;
|
|
@@ -1868,13 +2082,13 @@ async function actionsKnowledgeCommand(platform, actionId, options) {
|
|
|
1868
2082
|
);
|
|
1869
2083
|
if (result.status === 304 && cached) {
|
|
1870
2084
|
cached.cachedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
1871
|
-
|
|
2085
|
+
writeCache2(cachePath, cached);
|
|
1872
2086
|
knowledgeData = cached.data;
|
|
1873
2087
|
cacheHit = true;
|
|
1874
2088
|
} else {
|
|
1875
2089
|
knowledgeData = result.data;
|
|
1876
2090
|
const newEntry = makeCacheEntry(actionId, knowledgeData, result.etag);
|
|
1877
|
-
|
|
2091
|
+
writeCache2(cachePath, newEntry);
|
|
1878
2092
|
cacheEntry = newEntry;
|
|
1879
2093
|
}
|
|
1880
2094
|
} catch (fetchError) {
|
|
@@ -2028,551 +2242,6 @@ function colorMethod(method) {
|
|
|
2028
2242
|
// src/commands/flow.ts
|
|
2029
2243
|
import pc7 from "picocolors";
|
|
2030
2244
|
|
|
2031
|
-
// src/lib/flow-schema.ts
|
|
2032
|
-
var FLOW_SCHEMA = {
|
|
2033
|
-
errorStrategies: ["fail", "continue", "retry", "fallback"],
|
|
2034
|
-
validInputTypes: ["string", "number", "boolean", "object", "array"],
|
|
2035
|
-
flowFields: {
|
|
2036
|
-
key: { type: "string", required: true, description: "Unique kebab-case identifier", pattern: /^[a-z0-9][a-z0-9-]*[a-z0-9]$/ },
|
|
2037
|
-
name: { type: "string", required: true, description: "Human-readable flow name" },
|
|
2038
|
-
description: { type: "string", required: false, description: "What this flow does" },
|
|
2039
|
-
version: { type: "string", required: false, description: "Semver or arbitrary version string" },
|
|
2040
|
-
inputs: { type: "object", required: true, description: "Input declarations (Record<string, InputDeclaration>)" },
|
|
2041
|
-
steps: { type: "array", required: true, description: "Ordered array of steps", stepsArray: true }
|
|
2042
|
-
},
|
|
2043
|
-
inputFields: {
|
|
2044
|
-
type: { type: "string", required: true, description: "Data type: string, number, boolean, object, array", enum: ["string", "number", "boolean", "object", "array"] },
|
|
2045
|
-
required: { type: "boolean", required: false, description: "Whether this input must be provided" },
|
|
2046
|
-
default: { type: "unknown", required: false, description: "Default value if not provided" },
|
|
2047
|
-
description: { type: "string", required: false, description: "Human-readable description" },
|
|
2048
|
-
connection: { type: "object", required: false, description: 'Connection metadata: { platform: "gmail" } \u2014 enables auto-resolution' }
|
|
2049
|
-
},
|
|
2050
|
-
stepCommonFields: {
|
|
2051
|
-
id: { type: "string", required: true, description: "Unique step identifier (used in selectors)" },
|
|
2052
|
-
name: { type: "string", required: true, description: "Human-readable step label" },
|
|
2053
|
-
type: { type: "string", required: true, description: "Step type (determines which config object is required)" },
|
|
2054
|
-
if: { type: "string", required: false, description: "JS expression \u2014 skip step if falsy" },
|
|
2055
|
-
unless: { type: "string", required: false, description: "JS expression \u2014 skip step if truthy" }
|
|
2056
|
-
},
|
|
2057
|
-
stepTypes: [
|
|
2058
|
-
{
|
|
2059
|
-
type: "action",
|
|
2060
|
-
configKey: "action",
|
|
2061
|
-
description: "Execute a platform API action",
|
|
2062
|
-
fields: {
|
|
2063
|
-
platform: { type: "string", required: true, description: "Platform name (kebab-case)" },
|
|
2064
|
-
actionId: { type: "string", required: true, description: "Action ID from `actions search`" },
|
|
2065
|
-
connectionKey: { type: "string", required: true, description: "Connection key (use $.input selector)" },
|
|
2066
|
-
data: { type: "object", required: false, description: "Request body (POST/PUT/PATCH)" },
|
|
2067
|
-
pathVars: { type: "object", required: false, description: "URL path variables" },
|
|
2068
|
-
queryParams: { type: "object", required: false, description: "Query parameters" },
|
|
2069
|
-
headers: { type: "object", required: false, description: "Additional headers" }
|
|
2070
|
-
},
|
|
2071
|
-
example: {
|
|
2072
|
-
id: "findCustomer",
|
|
2073
|
-
name: "Search Stripe customers",
|
|
2074
|
-
type: "action",
|
|
2075
|
-
action: {
|
|
2076
|
-
platform: "stripe",
|
|
2077
|
-
actionId: "conn_mod_def::xxx::yyy",
|
|
2078
|
-
connectionKey: "$.input.stripeConnectionKey",
|
|
2079
|
-
data: { query: "email:'{{$.input.customerEmail}}'" }
|
|
2080
|
-
}
|
|
2081
|
-
}
|
|
2082
|
-
},
|
|
2083
|
-
{
|
|
2084
|
-
type: "transform",
|
|
2085
|
-
configKey: "transform",
|
|
2086
|
-
description: "Single JS expression with implicit return",
|
|
2087
|
-
fields: {
|
|
2088
|
-
expression: { type: "string", required: true, description: "JS expression evaluated with flow context as $" }
|
|
2089
|
-
},
|
|
2090
|
-
example: {
|
|
2091
|
-
id: "extractNames",
|
|
2092
|
-
name: "Extract customer names",
|
|
2093
|
-
type: "transform",
|
|
2094
|
-
transform: { expression: "$.steps.findCustomer.response.data.map(c => c.name)" }
|
|
2095
|
-
}
|
|
2096
|
-
},
|
|
2097
|
-
{
|
|
2098
|
-
type: "code",
|
|
2099
|
-
configKey: "code",
|
|
2100
|
-
description: "Multi-line async JS with explicit return",
|
|
2101
|
-
fields: {
|
|
2102
|
-
source: { type: "string", required: true, description: "JS function body (flow context as $, supports await)" }
|
|
2103
|
-
},
|
|
2104
|
-
example: {
|
|
2105
|
-
id: "processData",
|
|
2106
|
-
name: "Process and enrich data",
|
|
2107
|
-
type: "code",
|
|
2108
|
-
code: { source: "const items = $.steps.fetch.response.data;\nreturn items.filter(i => i.active);" }
|
|
2109
|
-
}
|
|
2110
|
-
},
|
|
2111
|
-
{
|
|
2112
|
-
type: "condition",
|
|
2113
|
-
configKey: "condition",
|
|
2114
|
-
description: "If/then/else branching",
|
|
2115
|
-
fields: {
|
|
2116
|
-
expression: { type: "string", required: true, description: "JS expression \u2014 truthy runs then, falsy runs else" },
|
|
2117
|
-
then: { type: "array", required: true, description: "Steps to run when true", stepsArray: true },
|
|
2118
|
-
else: { type: "array", required: false, description: "Steps to run when false", stepsArray: true }
|
|
2119
|
-
},
|
|
2120
|
-
example: {
|
|
2121
|
-
id: "checkFound",
|
|
2122
|
-
name: "Check if customer exists",
|
|
2123
|
-
type: "condition",
|
|
2124
|
-
condition: {
|
|
2125
|
-
expression: "$.steps.search.response.data.length > 0",
|
|
2126
|
-
then: [{ id: "notify", name: "Send notification", type: "action", action: { platform: "slack", actionId: "...", connectionKey: "$.input.slackKey", data: { text: "Found!" } } }],
|
|
2127
|
-
else: [{ id: "logMiss", name: "Log not found", type: "transform", transform: { expression: "'Not found'" } }]
|
|
2128
|
-
}
|
|
2129
|
-
}
|
|
2130
|
-
},
|
|
2131
|
-
{
|
|
2132
|
-
type: "loop",
|
|
2133
|
-
configKey: "loop",
|
|
2134
|
-
description: "Iterate over an array with optional concurrency",
|
|
2135
|
-
fields: {
|
|
2136
|
-
over: { type: "string", required: true, description: "Selector resolving to an array" },
|
|
2137
|
-
as: { type: "string", required: true, description: "Variable name for current item ($.loop.<as>)" },
|
|
2138
|
-
indexAs: { type: "string", required: false, description: "Variable name for index" },
|
|
2139
|
-
steps: { type: "array", required: true, description: "Steps to run per iteration", stepsArray: true },
|
|
2140
|
-
maxIterations: { type: "number", required: false, description: "Safety cap (default: no limit)" },
|
|
2141
|
-
maxConcurrency: { type: "number", required: false, description: "Parallel batch size (default: 1 = sequential)" }
|
|
2142
|
-
},
|
|
2143
|
-
example: {
|
|
2144
|
-
id: "processOrders",
|
|
2145
|
-
name: "Process each order",
|
|
2146
|
-
type: "loop",
|
|
2147
|
-
loop: {
|
|
2148
|
-
over: "$.steps.listOrders.response.data",
|
|
2149
|
-
as: "order",
|
|
2150
|
-
steps: [{ id: "createInvoice", name: "Create invoice", type: "action", action: { platform: "stripe", actionId: "...", connectionKey: "$.input.stripeKey", data: { amount: "$.loop.order.total" } } }]
|
|
2151
|
-
}
|
|
2152
|
-
}
|
|
2153
|
-
},
|
|
2154
|
-
{
|
|
2155
|
-
type: "parallel",
|
|
2156
|
-
configKey: "parallel",
|
|
2157
|
-
description: "Run steps concurrently",
|
|
2158
|
-
fields: {
|
|
2159
|
-
steps: { type: "array", required: true, description: "Steps to run in parallel", stepsArray: true },
|
|
2160
|
-
maxConcurrency: { type: "number", required: false, description: "Max concurrent steps (default: 5)" }
|
|
2161
|
-
},
|
|
2162
|
-
example: {
|
|
2163
|
-
id: "lookups",
|
|
2164
|
-
name: "Parallel data lookups",
|
|
2165
|
-
type: "parallel",
|
|
2166
|
-
parallel: {
|
|
2167
|
-
steps: [
|
|
2168
|
-
{ id: "getStripe", name: "Get Stripe data", type: "action", action: { platform: "stripe", actionId: "...", connectionKey: "$.input.stripeKey" } },
|
|
2169
|
-
{ id: "getSlack", name: "Get Slack data", type: "action", action: { platform: "slack", actionId: "...", connectionKey: "$.input.slackKey" } }
|
|
2170
|
-
]
|
|
2171
|
-
}
|
|
2172
|
-
}
|
|
2173
|
-
},
|
|
2174
|
-
{
|
|
2175
|
-
type: "file-read",
|
|
2176
|
-
configKey: "fileRead",
|
|
2177
|
-
description: "Read a file (optional JSON parse)",
|
|
2178
|
-
fields: {
|
|
2179
|
-
path: { type: "string", required: true, description: "File path to read" },
|
|
2180
|
-
parseJson: { type: "boolean", required: false, description: "Parse contents as JSON (default: false)" }
|
|
2181
|
-
},
|
|
2182
|
-
example: {
|
|
2183
|
-
id: "readConfig",
|
|
2184
|
-
name: "Read config file",
|
|
2185
|
-
type: "file-read",
|
|
2186
|
-
fileRead: { path: "./data/config.json", parseJson: true }
|
|
2187
|
-
}
|
|
2188
|
-
},
|
|
2189
|
-
{
|
|
2190
|
-
type: "file-write",
|
|
2191
|
-
configKey: "fileWrite",
|
|
2192
|
-
description: "Write or append to a file",
|
|
2193
|
-
fields: {
|
|
2194
|
-
path: { type: "string", required: true, description: "File path to write" },
|
|
2195
|
-
content: { type: "unknown", required: true, description: "Content to write (supports selectors)" },
|
|
2196
|
-
append: { type: "boolean", required: false, description: "Append instead of overwrite (default: false)" }
|
|
2197
|
-
},
|
|
2198
|
-
example: {
|
|
2199
|
-
id: "writeResults",
|
|
2200
|
-
name: "Save results",
|
|
2201
|
-
type: "file-write",
|
|
2202
|
-
fileWrite: { path: "./output/results.json", content: "$.steps.transform.output" }
|
|
2203
|
-
}
|
|
2204
|
-
},
|
|
2205
|
-
{
|
|
2206
|
-
type: "while",
|
|
2207
|
-
configKey: "while",
|
|
2208
|
-
description: "Do-while loop with condition check",
|
|
2209
|
-
fields: {
|
|
2210
|
-
condition: { type: "string", required: true, description: "JS expression checked before each iteration (after first)" },
|
|
2211
|
-
steps: { type: "array", required: true, description: "Steps to run each iteration", stepsArray: true },
|
|
2212
|
-
maxIterations: { type: "number", required: false, description: "Safety cap (default: 100)" }
|
|
2213
|
-
},
|
|
2214
|
-
example: {
|
|
2215
|
-
id: "paginate",
|
|
2216
|
-
name: "Paginate through pages",
|
|
2217
|
-
type: "while",
|
|
2218
|
-
while: {
|
|
2219
|
-
condition: "$.steps.paginate.output.lastResult.nextPageToken != null",
|
|
2220
|
-
maxIterations: 50,
|
|
2221
|
-
steps: [{ id: "fetchPage", name: "Fetch next page", type: "action", action: { platform: "gmail", actionId: "...", connectionKey: "$.input.gmailKey" } }]
|
|
2222
|
-
}
|
|
2223
|
-
}
|
|
2224
|
-
},
|
|
2225
|
-
{
|
|
2226
|
-
type: "flow",
|
|
2227
|
-
configKey: "flow",
|
|
2228
|
-
description: "Execute a sub-flow (supports composition)",
|
|
2229
|
-
fields: {
|
|
2230
|
-
key: { type: "string", required: true, description: "Flow key or path of the sub-flow" },
|
|
2231
|
-
inputs: { type: "object", required: false, description: "Inputs to pass to the sub-flow (supports selectors)" }
|
|
2232
|
-
},
|
|
2233
|
-
example: {
|
|
2234
|
-
id: "enrich",
|
|
2235
|
-
name: "Run enrichment sub-flow",
|
|
2236
|
-
type: "flow",
|
|
2237
|
-
flow: { key: "enrich-customer", inputs: { email: "$.steps.getCustomer.response.email" } }
|
|
2238
|
-
}
|
|
2239
|
-
},
|
|
2240
|
-
{
|
|
2241
|
-
type: "paginate",
|
|
2242
|
-
configKey: "paginate",
|
|
2243
|
-
description: "Auto-paginate API results into a single array",
|
|
2244
|
-
fields: {
|
|
2245
|
-
action: { type: "object", required: true, description: "Action config (same shape as action step: platform, actionId, connectionKey)" },
|
|
2246
|
-
pageTokenField: { type: "string", required: true, description: "Dot-path in response to next page token" },
|
|
2247
|
-
resultsField: { type: "string", required: true, description: "Dot-path in response to results array" },
|
|
2248
|
-
inputTokenParam: { type: "string", required: true, description: "Dot-path in action config where page token is injected" },
|
|
2249
|
-
maxPages: { type: "number", required: false, description: "Max pages to fetch (default: 10)" }
|
|
2250
|
-
},
|
|
2251
|
-
example: {
|
|
2252
|
-
id: "allMessages",
|
|
2253
|
-
name: "Fetch all Gmail messages",
|
|
2254
|
-
type: "paginate",
|
|
2255
|
-
paginate: {
|
|
2256
|
-
action: { platform: "gmail", actionId: "...", connectionKey: "$.input.gmailKey", queryParams: { maxResults: 100 } },
|
|
2257
|
-
pageTokenField: "nextPageToken",
|
|
2258
|
-
resultsField: "messages",
|
|
2259
|
-
inputTokenParam: "queryParams.pageToken",
|
|
2260
|
-
maxPages: 10
|
|
2261
|
-
}
|
|
2262
|
-
}
|
|
2263
|
-
},
|
|
2264
|
-
{
|
|
2265
|
-
type: "bash",
|
|
2266
|
-
configKey: "bash",
|
|
2267
|
-
description: "Shell command (requires --allow-bash)",
|
|
2268
|
-
fields: {
|
|
2269
|
-
command: { type: "string", required: true, description: "Shell command to execute (supports selectors)" },
|
|
2270
|
-
timeout: { type: "number", required: false, description: "Timeout in ms (default: 30000)" },
|
|
2271
|
-
parseJson: { type: "boolean", required: false, description: "Parse stdout as JSON (default: false)" },
|
|
2272
|
-
cwd: { type: "string", required: false, description: "Working directory (supports selectors)" },
|
|
2273
|
-
env: { type: "object", required: false, description: "Additional environment variables" }
|
|
2274
|
-
},
|
|
2275
|
-
example: {
|
|
2276
|
-
id: "analyze",
|
|
2277
|
-
name: "Analyze with Claude",
|
|
2278
|
-
type: "bash",
|
|
2279
|
-
bash: {
|
|
2280
|
-
command: "cat /tmp/data.json | claude --print 'Analyze this data' --output-format json",
|
|
2281
|
-
timeout: 18e4,
|
|
2282
|
-
parseJson: true
|
|
2283
|
-
}
|
|
2284
|
-
}
|
|
2285
|
-
}
|
|
2286
|
-
]
|
|
2287
|
-
};
|
|
2288
|
-
var _coveredTypes = Object.fromEntries(
|
|
2289
|
-
FLOW_SCHEMA.stepTypes.map((st) => [st.type, true])
|
|
2290
|
-
);
|
|
2291
|
-
var _stepTypeMap = new Map(
|
|
2292
|
-
FLOW_SCHEMA.stepTypes.map((st) => [st.type, st])
|
|
2293
|
-
);
|
|
2294
|
-
function getStepTypeDescriptor(type) {
|
|
2295
|
-
return _stepTypeMap.get(type);
|
|
2296
|
-
}
|
|
2297
|
-
function getValidStepTypes() {
|
|
2298
|
-
return FLOW_SCHEMA.stepTypes.map((st) => st.type);
|
|
2299
|
-
}
|
|
2300
|
-
function getNestedStepsKeys() {
|
|
2301
|
-
const result = [];
|
|
2302
|
-
for (const st of FLOW_SCHEMA.stepTypes) {
|
|
2303
|
-
for (const [fieldName, fd] of Object.entries(st.fields)) {
|
|
2304
|
-
if (fd.stepsArray) {
|
|
2305
|
-
result.push({ configKey: st.configKey, fieldName });
|
|
2306
|
-
}
|
|
2307
|
-
}
|
|
2308
|
-
}
|
|
2309
|
-
return result;
|
|
2310
|
-
}
|
|
2311
|
-
function generateFlowGuide() {
|
|
2312
|
-
const validTypes = getValidStepTypes();
|
|
2313
|
-
const sections = [];
|
|
2314
|
-
sections.push(`# One Flows \u2014 Reference
|
|
2315
|
-
|
|
2316
|
-
## Overview
|
|
2317
|
-
|
|
2318
|
-
Workflows are JSON files at \`.one/flows/<key>.flow.json\` that chain actions across platforms.
|
|
2319
|
-
|
|
2320
|
-
## Commands
|
|
2321
|
-
|
|
2322
|
-
\`\`\`bash
|
|
2323
|
-
one --agent flow create <key> --definition '<json>' # Create (or --definition @file.json)
|
|
2324
|
-
one --agent flow create <key> --definition @flow.json # Create from file
|
|
2325
|
-
one --agent flow list # List
|
|
2326
|
-
one --agent flow validate <key> # Validate
|
|
2327
|
-
one --agent flow execute <key> -i name=value # Execute
|
|
2328
|
-
one --agent flow execute <key> --dry-run --mock # Test with mock data
|
|
2329
|
-
one --agent flow execute <key> --allow-bash # Enable bash steps
|
|
2330
|
-
one --agent flow runs [flowKey] # List past runs
|
|
2331
|
-
one --agent flow resume <runId> # Resume failed run
|
|
2332
|
-
one --agent flow scaffold [template] # Generate a starter template
|
|
2333
|
-
\`\`\`
|
|
2334
|
-
|
|
2335
|
-
You can also write the JSON file directly to \`.one/flows/<key>.flow.json\` \u2014 this is often easier than passing large JSON via --definition.
|
|
2336
|
-
|
|
2337
|
-
## Building a Workflow
|
|
2338
|
-
|
|
2339
|
-
1. **Design first** \u2014 clarify the end goal, map the full value chain, identify where AI analysis is needed
|
|
2340
|
-
2. **Discover connections** \u2014 \`one --agent connection list\`
|
|
2341
|
-
3. **Get knowledge** for every action \u2014 \`one --agent actions knowledge <platform> <actionId>\`
|
|
2342
|
-
4. **Construct JSON** \u2014 declare inputs, wire steps with selectors
|
|
2343
|
-
5. **Validate** \u2014 \`one --agent flow validate <key>\`
|
|
2344
|
-
6. **Execute** \u2014 \`one --agent flow execute <key> -i param=value\``);
|
|
2345
|
-
sections.push(`## Flow JSON Schema
|
|
2346
|
-
|
|
2347
|
-
\`\`\`json
|
|
2348
|
-
{
|
|
2349
|
-
"key": "my-workflow",
|
|
2350
|
-
"name": "My Workflow",
|
|
2351
|
-
"description": "What this flow does",
|
|
2352
|
-
"version": "1",
|
|
2353
|
-
"inputs": {
|
|
2354
|
-
"connectionKey": {
|
|
2355
|
-
"type": "string",
|
|
2356
|
-
"required": true,
|
|
2357
|
-
"description": "Platform connection key",
|
|
2358
|
-
"connection": { "platform": "stripe" }
|
|
2359
|
-
},
|
|
2360
|
-
"param": {
|
|
2361
|
-
"type": "string",
|
|
2362
|
-
"required": true,
|
|
2363
|
-
"description": "A user parameter"
|
|
2364
|
-
}
|
|
2365
|
-
},
|
|
2366
|
-
"steps": [
|
|
2367
|
-
{
|
|
2368
|
-
"id": "stepId",
|
|
2369
|
-
"name": "Human-readable step name",
|
|
2370
|
-
"type": "action",
|
|
2371
|
-
"action": {
|
|
2372
|
-
"platform": "stripe",
|
|
2373
|
-
"actionId": "conn_mod_def::xxx::yyy",
|
|
2374
|
-
"connectionKey": "$.input.connectionKey",
|
|
2375
|
-
"data": { "query": "{{$.input.param}}" }
|
|
2376
|
-
}
|
|
2377
|
-
}
|
|
2378
|
-
]
|
|
2379
|
-
}
|
|
2380
|
-
\`\`\`
|
|
2381
|
-
|
|
2382
|
-
### Top-level fields
|
|
2383
|
-
|
|
2384
|
-
| Field | Type | Required | Description |
|
|
2385
|
-
|-------|------|----------|-------------|`);
|
|
2386
|
-
for (const [name, fd] of Object.entries(FLOW_SCHEMA.flowFields)) {
|
|
2387
|
-
sections.push(`| \`${name}\` | ${fd.type} | ${fd.required ? "yes" : "no"} | ${fd.description} |`);
|
|
2388
|
-
}
|
|
2389
|
-
sections.push(`
|
|
2390
|
-
### Input declarations
|
|
2391
|
-
|
|
2392
|
-
| Field | Type | Required | Description |
|
|
2393
|
-
|-------|------|----------|-------------|`);
|
|
2394
|
-
for (const [name, fd] of Object.entries(FLOW_SCHEMA.inputFields)) {
|
|
2395
|
-
sections.push(`| \`${name}\` | ${fd.type} | ${fd.required ? "yes" : "no"} | ${fd.description} |`);
|
|
2396
|
-
}
|
|
2397
|
-
sections.push(`
|
|
2398
|
-
### Step fields (all steps)
|
|
2399
|
-
|
|
2400
|
-
Every step MUST have \`id\`, \`name\`, and \`type\`. The \`type\` determines which config object is required.
|
|
2401
|
-
|
|
2402
|
-
| Field | Type | Required | Description |
|
|
2403
|
-
|-------|------|----------|-------------|`);
|
|
2404
|
-
for (const [name, fd] of Object.entries(FLOW_SCHEMA.stepCommonFields)) {
|
|
2405
|
-
sections.push(`| \`${name}\` | ${fd.type} | ${fd.required ? "yes" : "no"} | ${fd.description} |`);
|
|
2406
|
-
}
|
|
2407
|
-
sections.push(`| \`onError\` | object | no | Error handling: \`{ "strategy": "${FLOW_SCHEMA.errorStrategies.join(" | ")}", "retries": 3, "retryDelayMs": 1000 }\` |`);
|
|
2408
|
-
sections.push(`
|
|
2409
|
-
## Step Types
|
|
2410
|
-
|
|
2411
|
-
**IMPORTANT:** Each step type requires a config object nested under a specific key. The type name and config key differ for some types (noted below).
|
|
2412
|
-
|
|
2413
|
-
| Type | Config Key | Description |
|
|
2414
|
-
|------|-----------|-------------|`);
|
|
2415
|
-
for (const st of FLOW_SCHEMA.stepTypes) {
|
|
2416
|
-
const keyNote = st.type !== st.configKey ? ` \u26A0\uFE0F` : "";
|
|
2417
|
-
sections.push(`| \`${st.type}\` | \`${st.configKey}\`${keyNote} | ${st.description} |`);
|
|
2418
|
-
}
|
|
2419
|
-
sections.push(`
|
|
2420
|
-
## Step Type Reference`);
|
|
2421
|
-
for (const st of FLOW_SCHEMA.stepTypes) {
|
|
2422
|
-
sections.push(`
|
|
2423
|
-
### \`${st.type}\` \u2014 ${st.description}`);
|
|
2424
|
-
if (st.type !== st.configKey) {
|
|
2425
|
-
sections.push(`
|
|
2426
|
-
> **Note:** Type is \`"${st.type}"\` but config key is \`"${st.configKey}"\` (camelCase).`);
|
|
2427
|
-
}
|
|
2428
|
-
sections.push(`
|
|
2429
|
-
| Field | Type | Required | Description |
|
|
2430
|
-
|-------|------|----------|-------------|`);
|
|
2431
|
-
for (const [name, fd] of Object.entries(st.fields)) {
|
|
2432
|
-
sections.push(`| \`${name}\` | ${fd.type} | ${fd.required ? "yes" : "no"} | ${fd.description} |`);
|
|
2433
|
-
}
|
|
2434
|
-
sections.push(`
|
|
2435
|
-
\`\`\`json
|
|
2436
|
-
${JSON.stringify(st.example, null, 2)}
|
|
2437
|
-
\`\`\``);
|
|
2438
|
-
}
|
|
2439
|
-
sections.push(`
|
|
2440
|
-
## Selectors
|
|
2441
|
-
|
|
2442
|
-
| Pattern | Resolves To |
|
|
2443
|
-
|---------|-------------|
|
|
2444
|
-
| \`$.input.paramName\` | Input value |
|
|
2445
|
-
| \`$.steps.stepId.response\` | Full API response |
|
|
2446
|
-
| \`$.steps.stepId.response.data[0].email\` | Nested field |
|
|
2447
|
-
| \`$.steps.stepId.response.data[*].id\` | Wildcard array map |
|
|
2448
|
-
| \`$.env.MY_VAR\` | Environment variable |
|
|
2449
|
-
| \`$.loop.item\` / \`$.loop.i\` | Loop iteration |
|
|
2450
|
-
| \`"Hello {{$.steps.getUser.response.name}}"\` | String interpolation |
|
|
2451
|
-
|
|
2452
|
-
### When to use bare selectors vs \`{{...}}\` interpolation
|
|
2453
|
-
|
|
2454
|
-
- **Bare selectors** (\`$.input.x\`): Use for fields the engine resolves directly \u2014 \`connectionKey\`, \`over\`, \`path\`, \`expression\`, \`condition\`, and any field where the entire value is a single selector. The resolved value keeps its original type (object, array, number).
|
|
2455
|
-
- **Interpolation** (\`{{$.input.x}}\`): Use inside string values where the selector is embedded in text \u2014 e.g., \`"Hello {{$.steps.getUser.response.name}}"\`. The resolved value is always stringified. Use this in \`data\`, \`pathVars\`, and \`queryParams\` when mixing selectors with literal text.
|
|
2456
|
-
- **Rule of thumb**: If the value is purely a selector, use bare. If it's a string containing a selector, use \`{{...}}\`.
|
|
2457
|
-
|
|
2458
|
-
### Selectors vs expressions
|
|
2459
|
-
|
|
2460
|
-
Selectors in data fields (\`data\`, \`queryParams\`, \`pathVars\`, \`connectionKey\`) are **dot-path lookups only** \u2014 they do not support JavaScript operators like \`||\` or \`&&\`. For default values, use the \`default\` field on the input definition:
|
|
2461
|
-
|
|
2462
|
-
\`\`\`json
|
|
2463
|
-
{ "inputs": { "maxResults": { "type": "number", "default": 10 } } }
|
|
2464
|
-
\`\`\`
|
|
2465
|
-
|
|
2466
|
-
The \`if\`, \`unless\`, \`condition.expression\`, \`while.condition\`, \`transform.expression\`, and \`code.source\` fields **do** support full JavaScript expressions (e.g., \`$.input.email && $.input.email.length > 0\`).
|
|
2467
|
-
|
|
2468
|
-
### \`output\` vs \`response\` on step results
|
|
2469
|
-
|
|
2470
|
-
Every completed step produces both \`output\` and \`response\`:
|
|
2471
|
-
- **Action steps**: \`response\` is the raw API response. \`output\` is the same as \`response\`.
|
|
2472
|
-
- **Code/transform steps**: \`output\` is the return value. \`response\` is an alias for \`output\`.
|
|
2473
|
-
- **In practice**: Use \`$.steps.stepId.response\` for action steps (API data) and \`$.steps.stepId.output\` for code/transform steps (computed data). Both work interchangeably, but using the semantically correct one makes flows easier to read.
|
|
2474
|
-
|
|
2475
|
-
## Error Handling
|
|
2476
|
-
|
|
2477
|
-
\`\`\`json
|
|
2478
|
-
{"onError": {"strategy": "retry", "retries": 3, "retryDelayMs": 1000}}
|
|
2479
|
-
\`\`\`
|
|
2480
|
-
|
|
2481
|
-
Strategies: \`${FLOW_SCHEMA.errorStrategies.join("`, `")}\`
|
|
2482
|
-
|
|
2483
|
-
Conditional execution: \`"if": "$.steps.prev.response.data.length > 0"\`
|
|
2484
|
-
|
|
2485
|
-
## Input Connection Auto-Resolution
|
|
2486
|
-
|
|
2487
|
-
When an input has \`"connection": { "platform": "stripe" }\`, the flow engine can automatically resolve the connection key at execution time. If the user has exactly one connection for that platform, the engine fills in the key without requiring \`-i connectionKey=...\`. If multiple connections exist, the user must specify which one. This is metadata for tooling \u2014 it does not affect the flow JSON structure, but it makes execution more convenient.
|
|
2488
|
-
|
|
2489
|
-
## Complete Example: Fetch Data, Transform, Notify
|
|
2490
|
-
|
|
2491
|
-
\`\`\`json
|
|
2492
|
-
{
|
|
2493
|
-
"key": "contacts-to-slack",
|
|
2494
|
-
"name": "CRM Contacts Summary to Slack",
|
|
2495
|
-
"description": "Fetch recent contacts from CRM, build a summary, post to Slack",
|
|
2496
|
-
"version": "1",
|
|
2497
|
-
"inputs": {
|
|
2498
|
-
"crmConnectionKey": {
|
|
2499
|
-
"type": "string",
|
|
2500
|
-
"required": true,
|
|
2501
|
-
"description": "CRM platform connection key",
|
|
2502
|
-
"connection": { "platform": "attio" }
|
|
2503
|
-
},
|
|
2504
|
-
"slackConnectionKey": {
|
|
2505
|
-
"type": "string",
|
|
2506
|
-
"required": true,
|
|
2507
|
-
"description": "Slack connection key",
|
|
2508
|
-
"connection": { "platform": "slack" }
|
|
2509
|
-
},
|
|
2510
|
-
"slackChannel": {
|
|
2511
|
-
"type": "string",
|
|
2512
|
-
"required": true,
|
|
2513
|
-
"description": "Slack channel name or ID"
|
|
2514
|
-
}
|
|
2515
|
-
},
|
|
2516
|
-
"steps": [
|
|
2517
|
-
{
|
|
2518
|
-
"id": "fetchContacts",
|
|
2519
|
-
"name": "Fetch recent contacts",
|
|
2520
|
-
"type": "action",
|
|
2521
|
-
"action": {
|
|
2522
|
-
"platform": "attio",
|
|
2523
|
-
"actionId": "ATTIO_LIST_PEOPLE_ACTION_ID",
|
|
2524
|
-
"connectionKey": "$.input.crmConnectionKey",
|
|
2525
|
-
"queryParams": { "limit": "10" }
|
|
2526
|
-
}
|
|
2527
|
-
},
|
|
2528
|
-
{
|
|
2529
|
-
"id": "buildSummary",
|
|
2530
|
-
"name": "Build formatted summary",
|
|
2531
|
-
"type": "code",
|
|
2532
|
-
"code": {
|
|
2533
|
-
"source": "const contacts = $.steps.fetchContacts.response.data || [];\\nconst lines = contacts.map((c, i) => \`\${i+1}. \${c.name || 'Unknown'} \u2014 \${c.email || 'no email'}\`);\\nreturn { summary: \`Found \${contacts.length} contacts:\\n\${lines.join('\\n')}\` };"
|
|
2534
|
-
}
|
|
2535
|
-
},
|
|
2536
|
-
{
|
|
2537
|
-
"id": "notifySlack",
|
|
2538
|
-
"name": "Post summary to Slack",
|
|
2539
|
-
"type": "action",
|
|
2540
|
-
"action": {
|
|
2541
|
-
"platform": "slack",
|
|
2542
|
-
"actionId": "SLACK_SEND_MESSAGE_ACTION_ID",
|
|
2543
|
-
"connectionKey": "$.input.slackConnectionKey",
|
|
2544
|
-
"data": {
|
|
2545
|
-
"channel": "$.input.slackChannel",
|
|
2546
|
-
"text": "{{$.steps.buildSummary.output.summary}}"
|
|
2547
|
-
}
|
|
2548
|
-
}
|
|
2549
|
-
}
|
|
2550
|
-
]
|
|
2551
|
-
}
|
|
2552
|
-
\`\`\`
|
|
2553
|
-
|
|
2554
|
-
Note: Action IDs above are placeholders. Always use \`one --agent actions search <platform> "<query>"\` to find real IDs.
|
|
2555
|
-
|
|
2556
|
-
## AI-Augmented Pattern
|
|
2557
|
-
|
|
2558
|
-
For workflows that need analysis/summarization, use the file-write \u2192 bash \u2192 code pattern:
|
|
2559
|
-
|
|
2560
|
-
1. \`file-write\` \u2014 save data to temp file
|
|
2561
|
-
2. \`bash\` \u2014 \`claude --print\` analyzes it (\`parseJson: true\`, \`timeout: 180000\`)
|
|
2562
|
-
3. \`code\` \u2014 parse and structure the output
|
|
2563
|
-
|
|
2564
|
-
Set timeout to at least 180000ms (3 min). Run Claude-heavy flows sequentially, not in parallel.
|
|
2565
|
-
|
|
2566
|
-
## Notes
|
|
2567
|
-
|
|
2568
|
-
- Connection keys are **inputs**, not hardcoded
|
|
2569
|
-
- Action IDs in examples are placeholders \u2014 always use \`actions search\`
|
|
2570
|
-
- Code steps allow \`crypto\`, \`buffer\`, \`url\`, \`path\` \u2014 \`fs\`, \`http\`, \`child_process\` are blocked
|
|
2571
|
-
- Bash steps require \`--allow-bash\` flag
|
|
2572
|
-
- State is persisted after every step \u2014 resume picks up where it left off`);
|
|
2573
|
-
return sections.join("\n");
|
|
2574
|
-
}
|
|
2575
|
-
|
|
2576
2245
|
// src/lib/flow-validator.ts
|
|
2577
2246
|
function validateFlowSchema(flow2) {
|
|
2578
2247
|
const errors = [];
|
|
@@ -2632,26 +2301,26 @@ function validateStepsArray(steps, pathPrefix, errors) {
|
|
|
2632
2301
|
const validTypes = FLOW_SCHEMA.stepTypes.map((st) => st.type);
|
|
2633
2302
|
for (let i = 0; i < steps.length; i++) {
|
|
2634
2303
|
const step = steps[i];
|
|
2635
|
-
const
|
|
2304
|
+
const path6 = `${pathPrefix}[${i}]`;
|
|
2636
2305
|
if (!step || typeof step !== "object" || Array.isArray(step)) {
|
|
2637
|
-
errors.push({ path:
|
|
2306
|
+
errors.push({ path: path6, message: "Step must be an object" });
|
|
2638
2307
|
continue;
|
|
2639
2308
|
}
|
|
2640
2309
|
const s = step;
|
|
2641
2310
|
if (!s.id || typeof s.id !== "string") {
|
|
2642
|
-
errors.push({ path: `${
|
|
2311
|
+
errors.push({ path: `${path6}.id`, message: 'Step must have a string "id"' });
|
|
2643
2312
|
}
|
|
2644
2313
|
if (!s.name || typeof s.name !== "string") {
|
|
2645
|
-
errors.push({ path: `${
|
|
2314
|
+
errors.push({ path: `${path6}.name`, message: 'Step must have a string "name"' });
|
|
2646
2315
|
}
|
|
2647
2316
|
if (!s.type || !validTypes.includes(s.type)) {
|
|
2648
|
-
errors.push({ path: `${
|
|
2317
|
+
errors.push({ path: `${path6}.type`, message: `Step type must be one of: ${validTypes.join(", ")}` });
|
|
2649
2318
|
continue;
|
|
2650
2319
|
}
|
|
2651
2320
|
if (s.onError && typeof s.onError === "object") {
|
|
2652
2321
|
const oe = s.onError;
|
|
2653
2322
|
if (!FLOW_SCHEMA.errorStrategies.includes(oe.strategy)) {
|
|
2654
|
-
errors.push({ path: `${
|
|
2323
|
+
errors.push({ path: `${path6}.onError.strategy`, message: `Error strategy must be one of: ${FLOW_SCHEMA.errorStrategies.join(", ")}` });
|
|
2655
2324
|
}
|
|
2656
2325
|
}
|
|
2657
2326
|
const descriptor = getStepTypeDescriptor(s.type);
|
|
@@ -2661,15 +2330,15 @@ function validateStepsArray(steps, pathPrefix, errors) {
|
|
|
2661
2330
|
if (!configObj || typeof configObj !== "object") {
|
|
2662
2331
|
const hint = detectFlatConfigHint(s, descriptor);
|
|
2663
2332
|
errors.push({
|
|
2664
|
-
path: `${
|
|
2333
|
+
path: `${path6}.${configKey}`,
|
|
2665
2334
|
message: `${capitalize(descriptor.type)} step must have a "${configKey}" config object${hint}`
|
|
2666
2335
|
});
|
|
2667
2336
|
continue;
|
|
2668
2337
|
}
|
|
2669
|
-
const
|
|
2338
|
+
const config2 = configObj;
|
|
2670
2339
|
for (const [fieldName, fd] of Object.entries(descriptor.fields)) {
|
|
2671
|
-
const fieldPath = `${
|
|
2672
|
-
const value =
|
|
2340
|
+
const fieldPath = `${path6}.${configKey}.${fieldName}`;
|
|
2341
|
+
const value = config2[fieldName];
|
|
2673
2342
|
if (fd.required && (value === void 0 || value === null || value === "")) {
|
|
2674
2343
|
errors.push({ path: fieldPath, message: `${capitalize(descriptor.type)} must have ${fd.type === "string" ? "a string" : fd.type === "array" ? "a" : "a"} "${fieldName}"` });
|
|
2675
2344
|
continue;
|
|
@@ -2700,6 +2369,25 @@ function validateStepsArray(steps, pathPrefix, errors) {
|
|
|
2700
2369
|
}
|
|
2701
2370
|
}
|
|
2702
2371
|
}
|
|
2372
|
+
if (descriptor.type === "code") {
|
|
2373
|
+
const hasSource = typeof config2.source === "string" && config2.source.length > 0;
|
|
2374
|
+
const hasModule = typeof config2.module === "string" && config2.module.length > 0;
|
|
2375
|
+
if (!hasSource && !hasModule) {
|
|
2376
|
+
errors.push({ path: `${path6}.${configKey}`, message: 'Code step must define either "source" (inline JS) or "module" (path to .mjs file)' });
|
|
2377
|
+
} else if (hasSource && hasModule) {
|
|
2378
|
+
errors.push({ path: `${path6}.${configKey}`, message: 'Code step cannot define both "source" and "module" \u2014 pick one' });
|
|
2379
|
+
}
|
|
2380
|
+
if (hasModule) {
|
|
2381
|
+
const m = config2.module;
|
|
2382
|
+
if (m.startsWith("/") || /^[a-zA-Z]:[\\/]/.test(m)) {
|
|
2383
|
+
errors.push({ path: `${path6}.${configKey}.module`, message: "Code module path must be relative to the flow folder (no absolute paths)" });
|
|
2384
|
+
} else if (m.split(/[\\/]/).includes("..")) {
|
|
2385
|
+
errors.push({ path: `${path6}.${configKey}.module`, message: 'Code module path must not escape the flow folder ("..")' });
|
|
2386
|
+
} else if (!m.endsWith(".mjs")) {
|
|
2387
|
+
errors.push({ path: `${path6}.${configKey}.module`, message: "Code module must be a .mjs file" });
|
|
2388
|
+
}
|
|
2389
|
+
}
|
|
2390
|
+
}
|
|
2703
2391
|
}
|
|
2704
2392
|
}
|
|
2705
2393
|
function detectFlatConfigHint(step, descriptor) {
|
|
@@ -2720,16 +2408,16 @@ function validateStepIds(flow2) {
|
|
|
2720
2408
|
function collectIds(steps, pathPrefix) {
|
|
2721
2409
|
for (let i = 0; i < steps.length; i++) {
|
|
2722
2410
|
const step = steps[i];
|
|
2723
|
-
const
|
|
2411
|
+
const path6 = `${pathPrefix}[${i}]`;
|
|
2724
2412
|
if (seen.has(step.id)) {
|
|
2725
|
-
errors.push({ path: `${
|
|
2413
|
+
errors.push({ path: `${path6}.id`, message: `Duplicate step ID: "${step.id}"` });
|
|
2726
2414
|
} else {
|
|
2727
2415
|
seen.add(step.id);
|
|
2728
2416
|
}
|
|
2729
2417
|
for (const { configKey, fieldName } of nestedKeys) {
|
|
2730
|
-
const
|
|
2731
|
-
if (
|
|
2732
|
-
collectIds(
|
|
2418
|
+
const config2 = step[configKey];
|
|
2419
|
+
if (config2 && Array.isArray(config2[fieldName])) {
|
|
2420
|
+
collectIds(config2[fieldName], `${path6}.${configKey}.${fieldName}`);
|
|
2733
2421
|
}
|
|
2734
2422
|
}
|
|
2735
2423
|
}
|
|
@@ -2746,9 +2434,9 @@ function validateSelectorReferences(flow2) {
|
|
|
2746
2434
|
for (const step of steps) {
|
|
2747
2435
|
ids.add(step.id);
|
|
2748
2436
|
for (const { configKey, fieldName } of nestedKeys) {
|
|
2749
|
-
const
|
|
2750
|
-
if (
|
|
2751
|
-
for (const id of getAllStepIds(
|
|
2437
|
+
const config2 = step[configKey];
|
|
2438
|
+
if (config2 && Array.isArray(config2[fieldName])) {
|
|
2439
|
+
for (const id of getAllStepIds(config2[fieldName])) ids.add(id);
|
|
2752
2440
|
}
|
|
2753
2441
|
}
|
|
2754
2442
|
}
|
|
@@ -2777,7 +2465,7 @@ function validateSelectorReferences(flow2) {
|
|
|
2777
2465
|
}
|
|
2778
2466
|
return selectors;
|
|
2779
2467
|
}
|
|
2780
|
-
function checkSelectors(selectors,
|
|
2468
|
+
function checkSelectors(selectors, path6) {
|
|
2781
2469
|
for (const selector of selectors) {
|
|
2782
2470
|
const parts = selector.split(".");
|
|
2783
2471
|
if (parts.length < 3) continue;
|
|
@@ -2785,31 +2473,31 @@ function validateSelectorReferences(flow2) {
|
|
|
2785
2473
|
if (root === "input") {
|
|
2786
2474
|
const inputName = parts[2];
|
|
2787
2475
|
if (!inputNames.has(inputName)) {
|
|
2788
|
-
errors.push({ path:
|
|
2476
|
+
errors.push({ path: path6, message: `Selector "${selector}" references undefined input "${inputName}"` });
|
|
2789
2477
|
}
|
|
2790
2478
|
} else if (root === "steps") {
|
|
2791
2479
|
const stepId = parts[2];
|
|
2792
2480
|
if (!allStepIds.has(stepId)) {
|
|
2793
|
-
errors.push({ path:
|
|
2481
|
+
errors.push({ path: path6, message: `Selector "${selector}" references undefined step "${stepId}"` });
|
|
2794
2482
|
}
|
|
2795
2483
|
}
|
|
2796
2484
|
}
|
|
2797
2485
|
}
|
|
2798
2486
|
const EXPRESSION_FIELDS = /* @__PURE__ */ new Set(["condition.expression", "while.condition"]);
|
|
2799
|
-
function checkOperatorsInSelectorField(value,
|
|
2487
|
+
function checkOperatorsInSelectorField(value, path6) {
|
|
2800
2488
|
if (typeof value === "string" && value.startsWith("$.")) {
|
|
2801
2489
|
if (value.includes("||")) {
|
|
2802
|
-
errors.push({ path:
|
|
2490
|
+
errors.push({ path: path6, message: `Selector "${value}" contains unsupported operator "||". Selectors in data fields use dot-path resolution, not JS evaluation. Use the "default" field on the input definition instead, or use a "code" step for complex expressions.` });
|
|
2803
2491
|
} else if (value.includes("&&")) {
|
|
2804
|
-
errors.push({ path:
|
|
2492
|
+
errors.push({ path: path6, message: `Selector "${value}" contains unsupported operator "&&". Selectors in data fields use dot-path resolution, not JS evaluation. Use a "condition" step or "code" step for complex expressions.` });
|
|
2805
2493
|
}
|
|
2806
2494
|
} else if (value && typeof value === "object" && !Array.isArray(value)) {
|
|
2807
2495
|
for (const [k, v] of Object.entries(value)) {
|
|
2808
|
-
checkOperatorsInSelectorField(v, `${
|
|
2496
|
+
checkOperatorsInSelectorField(v, `${path6}.${k}`);
|
|
2809
2497
|
}
|
|
2810
2498
|
} else if (Array.isArray(value)) {
|
|
2811
2499
|
for (let i = 0; i < value.length; i++) {
|
|
2812
|
-
checkOperatorsInSelectorField(value[i], `${
|
|
2500
|
+
checkOperatorsInSelectorField(value[i], `${path6}[${i}]`);
|
|
2813
2501
|
}
|
|
2814
2502
|
}
|
|
2815
2503
|
}
|
|
@@ -2818,12 +2506,12 @@ function validateSelectorReferences(flow2) {
|
|
|
2818
2506
|
if (step.unless) checkSelectors(extractSelectors(step.unless), `${pathPrefix}.unless`);
|
|
2819
2507
|
const descriptor = getStepTypeDescriptor(step.type);
|
|
2820
2508
|
if (descriptor) {
|
|
2821
|
-
const
|
|
2822
|
-
if (
|
|
2509
|
+
const config2 = step[descriptor.configKey];
|
|
2510
|
+
if (config2 && typeof config2 === "object") {
|
|
2823
2511
|
if (step.type !== "transform" && step.type !== "code") {
|
|
2824
2512
|
for (const [fieldName, fd] of Object.entries(descriptor.fields)) {
|
|
2825
2513
|
if (fd.stepsArray) continue;
|
|
2826
|
-
const value =
|
|
2514
|
+
const value = config2[fieldName];
|
|
2827
2515
|
if (value !== void 0) {
|
|
2828
2516
|
const fieldKey = `${descriptor.configKey}.${fieldName}`;
|
|
2829
2517
|
const fieldPath = `${pathPrefix}.${fieldKey}`;
|
|
@@ -2861,7 +2549,7 @@ function validateFlow(flow2) {
|
|
|
2861
2549
|
}
|
|
2862
2550
|
|
|
2863
2551
|
// src/commands/flow.ts
|
|
2864
|
-
import
|
|
2552
|
+
import fs6 from "fs";
|
|
2865
2553
|
function getConfig2() {
|
|
2866
2554
|
const apiKey = getApiKey();
|
|
2867
2555
|
if (!apiKey) {
|
|
@@ -2919,7 +2607,7 @@ async function flowCreateCommand(key, options) {
|
|
|
2919
2607
|
if (raw.startsWith("@")) {
|
|
2920
2608
|
const filePath = raw.slice(1);
|
|
2921
2609
|
try {
|
|
2922
|
-
raw =
|
|
2610
|
+
raw = fs6.readFileSync(filePath, "utf-8");
|
|
2923
2611
|
} catch (err) {
|
|
2924
2612
|
error(`Cannot read file "${filePath}": ${err.message}`);
|
|
2925
2613
|
}
|
|
@@ -2971,14 +2659,35 @@ async function flowExecuteCommand(keyOrPath, options) {
|
|
|
2971
2659
|
const spinner5 = createSpinner();
|
|
2972
2660
|
spinner5.start(`Loading workflow "${keyOrPath}"...`);
|
|
2973
2661
|
let flow2;
|
|
2662
|
+
let rootDir;
|
|
2663
|
+
let flowFilePath;
|
|
2974
2664
|
try {
|
|
2975
|
-
|
|
2665
|
+
const loaded = loadFlowWithMeta(keyOrPath);
|
|
2666
|
+
flow2 = loaded.flow;
|
|
2667
|
+
rootDir = loaded.rootDir;
|
|
2668
|
+
flowFilePath = loaded.filePath;
|
|
2976
2669
|
} catch (err) {
|
|
2977
2670
|
spinner5.stop("Workflow not found");
|
|
2978
2671
|
error(err instanceof Error ? err.message : String(err));
|
|
2979
2672
|
return;
|
|
2980
2673
|
}
|
|
2981
2674
|
spinner5.stop(`Workflow: ${flow2.name} (${flow2.steps.length} steps)`);
|
|
2675
|
+
if (flowFilePath.endsWith(".flow.json")) {
|
|
2676
|
+
const msg = `Workflow "${flow2.key}" uses the deprecated single-file layout. Migrate to .one/flows/${flow2.key}/flow.json (see: one guide flows).`;
|
|
2677
|
+
if (isAgentMode()) {
|
|
2678
|
+
json({ event: "flow:deprecation", flowKey: flow2.key, warning: msg });
|
|
2679
|
+
} else {
|
|
2680
|
+
console.error(pc7.yellow(`\u26A0 ${msg}`));
|
|
2681
|
+
}
|
|
2682
|
+
}
|
|
2683
|
+
if (!options.allowBash && flowRequiresBash(flow2)) {
|
|
2684
|
+
const msg = `Workflow "${flow2.key}" contains bash steps. Re-run with --allow-bash to permit shell execution.`;
|
|
2685
|
+
if (isAgentMode()) {
|
|
2686
|
+
json({ error: msg, requiresBash: true, flowKey: flow2.key });
|
|
2687
|
+
process.exit(1);
|
|
2688
|
+
}
|
|
2689
|
+
error(msg);
|
|
2690
|
+
}
|
|
2982
2691
|
const inputs = parseInputs(options.input || []);
|
|
2983
2692
|
const resolvedInputs = await autoResolveConnectionInputs(flow2, inputs, api);
|
|
2984
2693
|
const runner = new FlowRunner(flow2, resolvedInputs);
|
|
@@ -3017,6 +2726,7 @@ ${pc7.yellow("Pausing after current step completes...")} (run ID: ${runId})`);
|
|
|
3017
2726
|
mock: options.mock,
|
|
3018
2727
|
verbose: options.verbose,
|
|
3019
2728
|
allowBash: options.allowBash,
|
|
2729
|
+
rootDir,
|
|
3020
2730
|
onEvent
|
|
3021
2731
|
});
|
|
3022
2732
|
process.off("SIGINT", sigintHandler);
|
|
@@ -3081,16 +2791,18 @@ async function flowListCommand() {
|
|
|
3081
2791
|
[
|
|
3082
2792
|
{ key: "key", label: "Key" },
|
|
3083
2793
|
{ key: "name", label: "Name" },
|
|
3084
|
-
{ key: "
|
|
2794
|
+
{ key: "layout", label: "Layout" },
|
|
3085
2795
|
{ key: "inputCount", label: "Inputs" },
|
|
3086
|
-
{ key: "stepCount", label: "Steps" }
|
|
2796
|
+
{ key: "stepCount", label: "Steps" },
|
|
2797
|
+
{ key: "flags", label: "Requires" }
|
|
3087
2798
|
],
|
|
3088
2799
|
flows.map((f) => ({
|
|
3089
2800
|
key: f.key,
|
|
3090
2801
|
name: f.name,
|
|
3091
|
-
|
|
2802
|
+
layout: f.layout,
|
|
3092
2803
|
inputCount: String(f.inputCount),
|
|
3093
|
-
stepCount: String(f.stepCount)
|
|
2804
|
+
stepCount: String(f.stepCount),
|
|
2805
|
+
flags: f.requiresBash ? "--allow-bash" : ""
|
|
3094
2806
|
}))
|
|
3095
2807
|
);
|
|
3096
2808
|
console.log();
|
|
@@ -3102,7 +2814,7 @@ async function flowValidateCommand(keyOrPath) {
|
|
|
3102
2814
|
let flowData;
|
|
3103
2815
|
try {
|
|
3104
2816
|
const flowPath = resolveFlowPath(keyOrPath);
|
|
3105
|
-
const content =
|
|
2817
|
+
const content = fs6.readFileSync(flowPath, "utf-8");
|
|
3106
2818
|
flowData = JSON.parse(content);
|
|
3107
2819
|
} catch (err) {
|
|
3108
2820
|
spinner5.stop("Validation failed");
|
|
@@ -3141,8 +2853,11 @@ async function flowResumeCommand(runId) {
|
|
|
3141
2853
|
const { apiKey, permissions, actionIds } = getConfig2();
|
|
3142
2854
|
const api = new OneApi(apiKey, getApiBase());
|
|
3143
2855
|
let flow2;
|
|
2856
|
+
let rootDir;
|
|
3144
2857
|
try {
|
|
3145
|
-
|
|
2858
|
+
const loaded = loadFlowWithMeta(state.flowKey);
|
|
2859
|
+
flow2 = loaded.flow;
|
|
2860
|
+
rootDir = loaded.rootDir;
|
|
3146
2861
|
} catch (err) {
|
|
3147
2862
|
error(`Could not load workflow "${state.flowKey}": ${err instanceof Error ? err.message : String(err)}`);
|
|
3148
2863
|
return;
|
|
@@ -3156,7 +2871,7 @@ async function flowResumeCommand(runId) {
|
|
|
3156
2871
|
const spinner5 = createSpinner();
|
|
3157
2872
|
spinner5.start(`Resuming run ${runId} (${state.completedSteps.length} steps already completed)...`);
|
|
3158
2873
|
try {
|
|
3159
|
-
const context = await runner.resume(flow2, api, permissions, actionIds, { onEvent });
|
|
2874
|
+
const context = await runner.resume(flow2, api, permissions, actionIds, { onEvent, rootDir });
|
|
3160
2875
|
spinner5.stop("Workflow completed");
|
|
3161
2876
|
if (isAgentMode()) {
|
|
3162
2877
|
json({
|
|
@@ -3857,11 +3572,11 @@ async function cacheUpdateAllCommand() {
|
|
|
3857
3572
|
if (e.type === "knowledge") {
|
|
3858
3573
|
const result = await api.getActionKnowledgeWithMeta(e.entry.key);
|
|
3859
3574
|
const newEntry = makeCacheEntry(e.entry.key, result.data, result.etag);
|
|
3860
|
-
|
|
3575
|
+
writeCache2(e.filePath, newEntry);
|
|
3861
3576
|
updated++;
|
|
3862
3577
|
} else {
|
|
3863
3578
|
const refreshed = { ...e.entry, cachedAt: (/* @__PURE__ */ new Date()).toISOString() };
|
|
3864
|
-
|
|
3579
|
+
writeCache2(e.filePath, refreshed);
|
|
3865
3580
|
updated++;
|
|
3866
3581
|
}
|
|
3867
3582
|
} catch (err) {
|
|
@@ -3946,7 +3661,8 @@ one --agent flow list # List all workflows
|
|
|
3946
3661
|
\`\`\`
|
|
3947
3662
|
|
|
3948
3663
|
**Key concepts:**
|
|
3949
|
-
- Workflows
|
|
3664
|
+
- Workflows live at \`.one/flows/<key>/flow.json\` (folder layout \u2014 REQUIRED for new flows). The legacy \`.one/flows/<key>.flow.json\` single-file layout is DEPRECATED but still loads for backward compatibility
|
|
3665
|
+
- Code steps can reference an external \`.mjs\` module under the flow's \`lib/\` folder (stdin JSON in, stdout JSON out) \u2014 keeps JS out of JSON strings and makes flows shareable
|
|
3950
3666
|
- 12 step types: action, transform, code, condition, loop, parallel, file-read, file-write, while, flow, paginate, bash
|
|
3951
3667
|
- Data wiring via selectors: \`$.input.param\`, \`$.steps.stepId.response\`, \`$.loop.item\`
|
|
3952
3668
|
- AI analysis via bash steps: \`claude --print\` with \`parseJson: true\`
|
|
@@ -3988,6 +3704,7 @@ Request specific sections:
|
|
|
3988
3704
|
- Always use the **exact action ID** from search results \u2014 don't guess
|
|
3989
3705
|
- Always read **knowledge** before executing any action
|
|
3990
3706
|
- Connection keys come from \`one connection list\` \u2014 don't hardcode them
|
|
3707
|
+
- Skills stay in lockstep with the CLI version automatically \u2014 every command checks a \`.one-cli-version\` marker in the canonical skill dir and refreshes the files if the CLI has been upgraded. Check manually with \`one config skills status\`; force a resync with \`one config skills sync\`
|
|
3991
3708
|
`;
|
|
3992
3709
|
var GUIDE_ACTIONS = `# One Actions \u2014 Reference
|
|
3993
3710
|
|
|
@@ -4591,118 +4308,6 @@ function buildWorkflowIdeas(connections) {
|
|
|
4591
4308
|
return lines.join("\n");
|
|
4592
4309
|
}
|
|
4593
4310
|
|
|
4594
|
-
// src/commands/update.ts
|
|
4595
|
-
import { createRequire } from "module";
|
|
4596
|
-
import { spawn } from "child_process";
|
|
4597
|
-
import { readFileSync, writeFileSync, mkdirSync } from "fs";
|
|
4598
|
-
import { homedir } from "os";
|
|
4599
|
-
import { join } from "path";
|
|
4600
|
-
var require2 = createRequire(import.meta.url);
|
|
4601
|
-
var { version: currentVersion } = require2("../package.json");
|
|
4602
|
-
var CACHE_PATH = join(homedir(), ".one", "update-check.json");
|
|
4603
|
-
var CHECK_INTERVAL_MS = 4 * 60 * 60 * 1e3;
|
|
4604
|
-
var AGE_GATE_MS = 30 * 60 * 1e3;
|
|
4605
|
-
async function fetchLatestVersionInfo() {
|
|
4606
|
-
try {
|
|
4607
|
-
const res = await fetch("https://registry.npmjs.org/@withone/cli");
|
|
4608
|
-
if (!res.ok) return null;
|
|
4609
|
-
const data = await res.json();
|
|
4610
|
-
const latest = data["dist-tags"]?.latest;
|
|
4611
|
-
if (!latest) return null;
|
|
4612
|
-
return { version: latest, publishedAt: data.time?.[latest] ?? null };
|
|
4613
|
-
} catch {
|
|
4614
|
-
return null;
|
|
4615
|
-
}
|
|
4616
|
-
}
|
|
4617
|
-
function readCache3() {
|
|
4618
|
-
try {
|
|
4619
|
-
return JSON.parse(readFileSync(CACHE_PATH, "utf8"));
|
|
4620
|
-
} catch {
|
|
4621
|
-
return null;
|
|
4622
|
-
}
|
|
4623
|
-
}
|
|
4624
|
-
function writeCache2(latestVersion, publishedAt) {
|
|
4625
|
-
try {
|
|
4626
|
-
mkdirSync(join(homedir(), ".one"), { recursive: true });
|
|
4627
|
-
writeFileSync(CACHE_PATH, JSON.stringify({ lastCheck: Date.now(), latestVersion, publishedAt }));
|
|
4628
|
-
} catch {
|
|
4629
|
-
}
|
|
4630
|
-
}
|
|
4631
|
-
async function checkLatestVersion() {
|
|
4632
|
-
const info = await fetchLatestVersionInfo();
|
|
4633
|
-
if (info) writeCache2(info.version, info.publishedAt);
|
|
4634
|
-
return info?.version ?? null;
|
|
4635
|
-
}
|
|
4636
|
-
async function checkLatestVersionCached() {
|
|
4637
|
-
const cache2 = readCache3();
|
|
4638
|
-
if (cache2 && Date.now() - cache2.lastCheck < CHECK_INTERVAL_MS) {
|
|
4639
|
-
return { version: cache2.latestVersion, publishedAt: cache2.publishedAt ?? null };
|
|
4640
|
-
}
|
|
4641
|
-
const info = await fetchLatestVersionInfo();
|
|
4642
|
-
if (info) writeCache2(info.version, info.publishedAt);
|
|
4643
|
-
return info;
|
|
4644
|
-
}
|
|
4645
|
-
function getCurrentVersion() {
|
|
4646
|
-
return currentVersion;
|
|
4647
|
-
}
|
|
4648
|
-
async function updateCommand() {
|
|
4649
|
-
const s = createSpinner();
|
|
4650
|
-
s.start("Checking for updates...");
|
|
4651
|
-
const latestVersion = await checkLatestVersion();
|
|
4652
|
-
if (!latestVersion) {
|
|
4653
|
-
s.stop("");
|
|
4654
|
-
error("Failed to check for updates \u2014 could not reach npm registry");
|
|
4655
|
-
}
|
|
4656
|
-
if (currentVersion === latestVersion) {
|
|
4657
|
-
s.stop("Already up to date");
|
|
4658
|
-
if (isAgentMode()) {
|
|
4659
|
-
json({ current: currentVersion, latest: latestVersion, updated: false, message: "Already up to date" });
|
|
4660
|
-
} else {
|
|
4661
|
-
console.log(`Already up to date (v${currentVersion})`);
|
|
4662
|
-
}
|
|
4663
|
-
return;
|
|
4664
|
-
}
|
|
4665
|
-
s.stop(`Update available: v${currentVersion} \u2192 v${latestVersion}`);
|
|
4666
|
-
console.log(`Updating @withone/cli: v${currentVersion} \u2192 v${latestVersion}...`);
|
|
4667
|
-
const code = await new Promise((resolve) => {
|
|
4668
|
-
const child = spawn("npm", ["install", "-g", "@withone/cli@latest", "--force"], {
|
|
4669
|
-
stdio: isAgentMode() ? "pipe" : "inherit",
|
|
4670
|
-
shell: true
|
|
4671
|
-
});
|
|
4672
|
-
child.on("close", resolve);
|
|
4673
|
-
child.on("error", () => resolve(1));
|
|
4674
|
-
});
|
|
4675
|
-
if (code === 0) {
|
|
4676
|
-
if (isAgentMode()) {
|
|
4677
|
-
json({ current: currentVersion, latest: latestVersion, updated: true, message: "Updated successfully" });
|
|
4678
|
-
} else {
|
|
4679
|
-
console.log(`Successfully updated to v${latestVersion}`);
|
|
4680
|
-
}
|
|
4681
|
-
} else {
|
|
4682
|
-
error("Update failed \u2014 try running: npm install -g @withone/cli@latest");
|
|
4683
|
-
}
|
|
4684
|
-
}
|
|
4685
|
-
function isNewerVersion(latest, current) {
|
|
4686
|
-
const parse = (v) => v.split(".").map(Number);
|
|
4687
|
-
const [lMaj, lMin, lPat] = parse(latest);
|
|
4688
|
-
const [cMaj, cMin, cPat] = parse(current);
|
|
4689
|
-
if (lMaj !== cMaj) return lMaj > cMaj;
|
|
4690
|
-
if (lMin !== cMin) return lMin > cMin;
|
|
4691
|
-
return lPat > cPat;
|
|
4692
|
-
}
|
|
4693
|
-
function autoUpdate(targetVersion, publishedAt) {
|
|
4694
|
-
if (publishedAt) {
|
|
4695
|
-
const age = Date.now() - new Date(publishedAt).getTime();
|
|
4696
|
-
if (age < AGE_GATE_MS) return;
|
|
4697
|
-
}
|
|
4698
|
-
const child = spawn("npm", ["install", "-g", `@withone/cli@${targetVersion}`], {
|
|
4699
|
-
detached: true,
|
|
4700
|
-
stdio: "ignore",
|
|
4701
|
-
shell: true
|
|
4702
|
-
});
|
|
4703
|
-
child.unref();
|
|
4704
|
-
}
|
|
4705
|
-
|
|
4706
4311
|
// src/index.ts
|
|
4707
4312
|
var require3 = createRequire2(import.meta.url);
|
|
4708
4313
|
var { version } = require3("../package.json");
|
|
@@ -4768,6 +4373,12 @@ program.hook("preAction", (thisCommand) => {
|
|
|
4768
4373
|
if (commandName !== "update") {
|
|
4769
4374
|
updateCheckPromise = checkLatestVersionCached();
|
|
4770
4375
|
}
|
|
4376
|
+
if (commandName !== "init" && commandName !== "update") {
|
|
4377
|
+
try {
|
|
4378
|
+
syncSkillsIfStale();
|
|
4379
|
+
} catch {
|
|
4380
|
+
}
|
|
4381
|
+
}
|
|
4771
4382
|
});
|
|
4772
4383
|
program.hook("postAction", async () => {
|
|
4773
4384
|
if (!updateCheckPromise) return;
|
|
@@ -4780,9 +4391,48 @@ program.hook("postAction", async () => {
|
|
|
4780
4391
|
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) => {
|
|
4781
4392
|
await initCommand(options);
|
|
4782
4393
|
});
|
|
4783
|
-
program.command("config").description("Configure
|
|
4394
|
+
var config = program.command("config").description("Configure the CLI (access control, skills, ...)").action(async () => {
|
|
4784
4395
|
await configCommand();
|
|
4785
4396
|
});
|
|
4397
|
+
var configSkills = config.command("skills").description("Manage locally-installed skill files");
|
|
4398
|
+
configSkills.command("sync").description("Re-copy packaged skill files over the local install (runs automatically after CLI upgrades)").action(async () => {
|
|
4399
|
+
const result = forceSyncSkills();
|
|
4400
|
+
if (isAgentMode()) {
|
|
4401
|
+
json({ command: "config skills sync", ...result });
|
|
4402
|
+
return;
|
|
4403
|
+
}
|
|
4404
|
+
if (result.reason === "not-installed") {
|
|
4405
|
+
console.log("No skill is installed yet. Run 'one init' first and opt in to skill installation.");
|
|
4406
|
+
return;
|
|
4407
|
+
}
|
|
4408
|
+
if (result.synced) {
|
|
4409
|
+
console.log(`\u2713 Skills synced to v${result.to}`);
|
|
4410
|
+
return;
|
|
4411
|
+
}
|
|
4412
|
+
if (result.reason === "source-missing") {
|
|
4413
|
+
console.log("\u2717 Packaged skill source not found in this CLI build");
|
|
4414
|
+
return;
|
|
4415
|
+
}
|
|
4416
|
+
console.log(`\u2717 Sync failed${result.error ? ": " + result.error : ""}`);
|
|
4417
|
+
});
|
|
4418
|
+
configSkills.command("status").description("Show installed skill version and whether it matches the current CLI").action(async () => {
|
|
4419
|
+
const status = getSkillStatus();
|
|
4420
|
+
if (isAgentMode()) {
|
|
4421
|
+
json({ command: "config skills status", ...status });
|
|
4422
|
+
return;
|
|
4423
|
+
}
|
|
4424
|
+
if (!status.installed) {
|
|
4425
|
+
console.log("Skill is not installed. Run 'one init' to install it.");
|
|
4426
|
+
console.log(`Canonical path (empty): ${status.canonicalPath}`);
|
|
4427
|
+
return;
|
|
4428
|
+
}
|
|
4429
|
+
const marker = status.installedVersion ?? "(no marker \u2014 pre-sync install)";
|
|
4430
|
+
const state = status.upToDate ? "\u2713 up to date" : "\u26A0 stale \u2014 will sync on next command";
|
|
4431
|
+
console.log(`Skill: ${state}`);
|
|
4432
|
+
console.log(` installed: ${marker}`);
|
|
4433
|
+
console.log(` current: ${status.currentVersion}`);
|
|
4434
|
+
console.log(` path: ${status.canonicalPath}`);
|
|
4435
|
+
});
|
|
4786
4436
|
var connection = program.command("connection").description("Manage connections");
|
|
4787
4437
|
connection.command("add [platform]").alias("a").description("Add a new connection").action(async (platform) => {
|
|
4788
4438
|
await connectionAddCommand(platform);
|
|
@@ -4815,7 +4465,7 @@ actions.command("execute <platform> <actionId> <connectionKey>").alias("x").desc
|
|
|
4815
4465
|
});
|
|
4816
4466
|
});
|
|
4817
4467
|
var flow = program.command("flow").alias("f").description("Create, execute, and manage multi-step workflows");
|
|
4818
|
-
flow.command("create [key]").description("Create a new workflow from JSON definition").option("--definition <json>", "Workflow definition as JSON string").option("-o, --output <path>", "Custom output path (default .one/flows/<key
|
|
4468
|
+
flow.command("create [key]").description("Create a new workflow from JSON definition").option("--definition <json>", "Workflow definition as JSON string").option("-o, --output <path>", "Custom output path (default .one/flows/<key>/flow.json)").action(async (key, options) => {
|
|
4819
4469
|
await flowCreateCommand(key, options);
|
|
4820
4470
|
});
|
|
4821
4471
|
flow.command("execute <keyOrPath>").alias("x").description("Execute a workflow by key or file path").option("-i, --input <name=value>", "Input parameter (repeatable)", collect, []).option("--dry-run", "Validate and show execution plan without running").option("--mock", "With --dry-run: execute transforms/code with mock API responses").option("--allow-bash", "Allow bash step execution (disabled by default for security)").option("-v, --verbose", "Show full request/response for each step").action(async (keyOrPath, options) => {
|