@withone/cli 1.15.0 → 1.17.1
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/dist/{chunk-SX2Y4HDW.js → chunk-QW4MBV4F.js} +6 -4
- package/dist/{flow-runner-7RUK2WMF.js → flow-runner-AJGIUT6E.js} +1 -1
- package/dist/index.js +1170 -635
- package/package.json +1 -1
- package/skills/one/SKILL.md +116 -0
- package/skills/one/references/flows.md +278 -0
- package/skills/one/references/relay.md +179 -0
- package/skills/one-actions/SKILL.md +0 -157
- package/skills/one-flow/SKILL.md +0 -915
- package/skills/one-relay/SKILL.md +0 -303
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-QW4MBV4F.js";
|
|
15
15
|
|
|
16
16
|
// src/index.ts
|
|
17
17
|
import { createRequire as createRequire2 } from "module";
|
|
@@ -19,9 +19,10 @@ import { Command } from "commander";
|
|
|
19
19
|
|
|
20
20
|
// src/commands/init.ts
|
|
21
21
|
import * as p3 from "@clack/prompts";
|
|
22
|
-
import
|
|
22
|
+
import pc2 from "picocolors";
|
|
23
|
+
import fs3 from "fs";
|
|
23
24
|
import path3 from "path";
|
|
24
|
-
import
|
|
25
|
+
import os3 from "os";
|
|
25
26
|
import { fileURLToPath } from "url";
|
|
26
27
|
|
|
27
28
|
// src/lib/config.ts
|
|
@@ -530,122 +531,69 @@ function formatList(list) {
|
|
|
530
531
|
|
|
531
532
|
// src/commands/init.ts
|
|
532
533
|
import open2 from "open";
|
|
533
|
-
|
|
534
|
-
// src/lib/table.ts
|
|
535
|
-
import pc2 from "picocolors";
|
|
536
|
-
function printTable(columns, rows) {
|
|
537
|
-
if (rows.length === 0) return;
|
|
538
|
-
const gap = " ";
|
|
539
|
-
const indent = " ";
|
|
540
|
-
const widths = columns.map((col) => {
|
|
541
|
-
const headerLen = col.label.length;
|
|
542
|
-
const maxValueLen = Math.max(...rows.map((row) => stripAnsi(row[col.key] || "").length));
|
|
543
|
-
return Math.max(headerLen, maxValueLen);
|
|
544
|
-
});
|
|
545
|
-
const header = columns.map((col, i) => {
|
|
546
|
-
const padded = col.align === "right" ? col.label.padStart(widths[i]) : col.label.padEnd(widths[i]);
|
|
547
|
-
return pc2.dim(padded);
|
|
548
|
-
}).join(gap);
|
|
549
|
-
console.log(`${indent}${header}`);
|
|
550
|
-
const separator = columns.map((_, i) => pc2.dim("\u2500".repeat(widths[i]))).join(gap);
|
|
551
|
-
console.log(`${indent}${separator}`);
|
|
552
|
-
for (const row of rows) {
|
|
553
|
-
const line = columns.map((col, i) => {
|
|
554
|
-
const raw = row[col.key] || "";
|
|
555
|
-
const rawLen = stripAnsi(raw).length;
|
|
556
|
-
const padding = widths[i] - rawLen;
|
|
557
|
-
const colored = col.color ? col.color(raw) : raw;
|
|
558
|
-
if (col.align === "right") {
|
|
559
|
-
return " ".repeat(Math.max(0, padding)) + colored;
|
|
560
|
-
}
|
|
561
|
-
return colored + " ".repeat(Math.max(0, padding));
|
|
562
|
-
}).join(gap);
|
|
563
|
-
console.log(`${indent}${line}`);
|
|
564
|
-
}
|
|
565
|
-
}
|
|
566
|
-
function stripAnsi(str) {
|
|
567
|
-
return str.replace(/\x1b\[[0-9;]*m/g, "");
|
|
568
|
-
}
|
|
569
|
-
|
|
570
|
-
// src/commands/init.ts
|
|
571
534
|
async function initCommand(options) {
|
|
572
535
|
if (isAgentMode()) {
|
|
573
536
|
error("This command requires interactive input. Run without --agent.");
|
|
574
537
|
}
|
|
575
538
|
const existingConfig = readConfig();
|
|
539
|
+
printBanner();
|
|
576
540
|
if (existingConfig) {
|
|
577
|
-
p3.intro(pc3.bgCyan(pc3.black(" One ")));
|
|
578
541
|
await handleExistingConfig(existingConfig.apiKey, options);
|
|
579
542
|
return;
|
|
580
543
|
}
|
|
581
|
-
printBanner();
|
|
582
544
|
await freshSetup(options);
|
|
583
545
|
}
|
|
584
546
|
async function handleExistingConfig(apiKey, options) {
|
|
585
547
|
const statuses = getAgentStatuses();
|
|
586
548
|
const masked = maskApiKey(apiKey);
|
|
549
|
+
const skillInstalled = isSkillInstalled();
|
|
587
550
|
console.log();
|
|
588
|
-
console.log(` ${
|
|
589
|
-
console.log(` ${
|
|
590
|
-
console.log(` ${
|
|
591
|
-
console.log(` ${
|
|
592
|
-
console.log();
|
|
593
|
-
printTable(
|
|
594
|
-
[
|
|
595
|
-
{ key: "agent", label: "Agent" },
|
|
596
|
-
{ key: "global", label: "Global" },
|
|
597
|
-
{ key: "project", label: "Project" }
|
|
598
|
-
],
|
|
599
|
-
statuses.map((s) => ({
|
|
600
|
-
agent: s.agent.name,
|
|
601
|
-
global: !s.detected ? pc3.dim("-") : s.globalMcp ? pc3.green("\u25CF yes") : pc3.yellow("\u25CB no"),
|
|
602
|
-
project: s.projectMcp === null ? pc3.dim("-") : s.projectMcp ? pc3.green("\u25CF yes") : pc3.yellow("\u25CB no")
|
|
603
|
-
}))
|
|
604
|
-
);
|
|
605
|
-
const notDetected = statuses.filter((s) => !s.detected);
|
|
606
|
-
if (notDetected.length > 0) {
|
|
607
|
-
console.log(` ${pc3.dim("- = not detected on this machine")}`);
|
|
608
|
-
}
|
|
551
|
+
console.log(` ${pc2.bold("Current Setup")}`);
|
|
552
|
+
console.log(` ${pc2.dim("\u2500".repeat(42))}`);
|
|
553
|
+
console.log(` ${pc2.dim("API Key:")} ${masked}`);
|
|
554
|
+
console.log(` ${pc2.dim("Skill:")} ${skillInstalled ? pc2.green("installed") : pc2.yellow("not installed")}`);
|
|
555
|
+
console.log(` ${pc2.dim("Config:")} ${getConfigPath()}`);
|
|
609
556
|
const ac = getAccessControl();
|
|
610
557
|
if (Object.keys(ac).length > 0) {
|
|
611
|
-
console.log(` ${pc3.bold("Access Control")}`);
|
|
612
|
-
console.log(` ${pc3.dim("\u2500".repeat(42))}`);
|
|
613
|
-
if (ac.permissions) console.log(` ${pc3.dim("Permissions:")} ${ac.permissions}`);
|
|
614
|
-
if (ac.connectionKeys) console.log(` ${pc3.dim("Connections:")} ${ac.connectionKeys.join(", ")}`);
|
|
615
|
-
if (ac.actionIds) console.log(` ${pc3.dim("Action IDs:")} ${ac.actionIds.join(", ")}`);
|
|
616
|
-
if (ac.knowledgeAgent) console.log(` ${pc3.dim("Knowledge only:")} yes`);
|
|
617
558
|
console.log();
|
|
559
|
+
console.log(` ${pc2.bold("Access Control")}`);
|
|
560
|
+
console.log(` ${pc2.dim("\u2500".repeat(42))}`);
|
|
561
|
+
if (ac.permissions) console.log(` ${pc2.dim("Permissions:")} ${ac.permissions}`);
|
|
562
|
+
if (ac.connectionKeys) console.log(` ${pc2.dim("Connections:")} ${ac.connectionKeys.join(", ")}`);
|
|
563
|
+
if (ac.actionIds) console.log(` ${pc2.dim("Action IDs:")} ${ac.actionIds.join(", ")}`);
|
|
564
|
+
if (ac.knowledgeAgent) console.log(` ${pc2.dim("Knowledge only:")} yes`);
|
|
618
565
|
}
|
|
566
|
+
console.log();
|
|
619
567
|
const actionOptions = [];
|
|
620
568
|
actionOptions.push({
|
|
621
|
-
value: "
|
|
622
|
-
label: "Update
|
|
569
|
+
value: "install-skills",
|
|
570
|
+
label: skillInstalled ? "Update skill" : "Install skill",
|
|
571
|
+
hint: skillInstalled ? "reinstall latest version" : "recommended"
|
|
623
572
|
});
|
|
624
573
|
actionOptions.push({
|
|
625
|
-
value: "
|
|
626
|
-
label: "
|
|
574
|
+
value: "show-prompt",
|
|
575
|
+
label: "Show agent onboarding prompt",
|
|
576
|
+
hint: "copy-paste to your AI agent"
|
|
577
|
+
});
|
|
578
|
+
actionOptions.push({
|
|
579
|
+
value: "add-connection",
|
|
580
|
+
label: "Connect a platform",
|
|
581
|
+
hint: "add Gmail, Slack, Shopify, etc."
|
|
582
|
+
});
|
|
583
|
+
actionOptions.push({
|
|
584
|
+
value: "update-key",
|
|
585
|
+
label: "Update API key"
|
|
627
586
|
});
|
|
628
|
-
const agentsMissingGlobal = statuses.filter((s) => s.detected && !s.globalMcp);
|
|
629
|
-
if (agentsMissingGlobal.length > 0) {
|
|
630
|
-
actionOptions.push({
|
|
631
|
-
value: "install-more",
|
|
632
|
-
label: "Install MCP to more agents",
|
|
633
|
-
hint: `${agentsMissingGlobal.map((s) => s.agent.name).join(", ")} (not recommended)`
|
|
634
|
-
});
|
|
635
|
-
}
|
|
636
|
-
const agentsMissingProject = statuses.filter((s) => s.projectMcp === false);
|
|
637
|
-
if (agentsMissingProject.length > 0) {
|
|
638
|
-
actionOptions.push({
|
|
639
|
-
value: "install-project",
|
|
640
|
-
label: "Install MCP for this project",
|
|
641
|
-
hint: `${agentsMissingProject.map((s) => s.agent.name).join(", ")} (not recommended)`
|
|
642
|
-
});
|
|
643
|
-
}
|
|
644
587
|
actionOptions.push({
|
|
645
588
|
value: "access-control",
|
|
646
589
|
label: "Configure access control",
|
|
647
590
|
hint: "permissions, connections, actions"
|
|
648
591
|
});
|
|
592
|
+
actionOptions.push({
|
|
593
|
+
value: "install-mcp",
|
|
594
|
+
label: "Install MCP server",
|
|
595
|
+
hint: "not recommended \u2014 use skills instead"
|
|
596
|
+
});
|
|
649
597
|
actionOptions.push({
|
|
650
598
|
value: "start-fresh",
|
|
651
599
|
label: "Start fresh (reconfigure everything)"
|
|
@@ -659,30 +607,34 @@ async function handleExistingConfig(apiKey, options) {
|
|
|
659
607
|
return;
|
|
660
608
|
}
|
|
661
609
|
switch (action) {
|
|
662
|
-
case "update-key":
|
|
663
|
-
await handleUpdateKey(statuses);
|
|
664
|
-
break;
|
|
665
610
|
case "install-skills": {
|
|
666
|
-
const success = await
|
|
611
|
+
const success = await promptSkillInstall();
|
|
667
612
|
if (success) {
|
|
668
|
-
|
|
613
|
+
printOnboardingPrompt();
|
|
614
|
+
p3.outro("Skill installed. Paste the prompt above to your AI agent.");
|
|
669
615
|
} else {
|
|
670
|
-
const __dirname2 = path3.dirname(fileURLToPath(import.meta.url));
|
|
671
|
-
const skillsDir = path3.resolve(__dirname2, "..", "skills");
|
|
672
|
-
p3.log.warn(`Skills installation failed. You can try manually: ${pc3.cyan(`npx skills add ${skillsDir}`)}`);
|
|
673
616
|
p3.outro("Done.");
|
|
674
617
|
}
|
|
675
618
|
break;
|
|
676
619
|
}
|
|
677
|
-
case "
|
|
678
|
-
|
|
620
|
+
case "show-prompt":
|
|
621
|
+
printOnboardingPrompt();
|
|
622
|
+
p3.outro("Paste the prompt above to your AI agent.");
|
|
679
623
|
break;
|
|
680
|
-
case "
|
|
681
|
-
await
|
|
624
|
+
case "add-connection":
|
|
625
|
+
await promptConnectIntegrations(apiKey);
|
|
626
|
+
p3.outro("Done.");
|
|
627
|
+
break;
|
|
628
|
+
case "update-key":
|
|
629
|
+
await handleUpdateKey(statuses);
|
|
682
630
|
break;
|
|
683
631
|
case "access-control":
|
|
684
632
|
await configCommand();
|
|
685
633
|
break;
|
|
634
|
+
case "install-mcp":
|
|
635
|
+
await promptAndInstallMcp(apiKey, options);
|
|
636
|
+
p3.outro("Done.");
|
|
637
|
+
break;
|
|
686
638
|
case "start-fresh":
|
|
687
639
|
await freshSetup({ yes: true });
|
|
688
640
|
break;
|
|
@@ -690,7 +642,7 @@ async function handleExistingConfig(apiKey, options) {
|
|
|
690
642
|
}
|
|
691
643
|
async function handleUpdateKey(statuses) {
|
|
692
644
|
p3.note(`Get your API key at:
|
|
693
|
-
${
|
|
645
|
+
${pc2.cyan(getApiKeyUrl())}`, "API Key");
|
|
694
646
|
const openBrowser = await p3.confirm({
|
|
695
647
|
message: "Open browser to get API key?",
|
|
696
648
|
initialValue: true
|
|
@@ -751,108 +703,161 @@ ${pc3.cyan(getApiKeyUrl())}`, "API Key");
|
|
|
751
703
|
}
|
|
752
704
|
p3.outro("API key updated.");
|
|
753
705
|
}
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
706
|
+
var SKILL_AGENTS = [
|
|
707
|
+
{ id: "claude-code", name: "Claude Code", skillDir: ".claude/skills", primary: true },
|
|
708
|
+
{ id: "claude-desktop", name: "Claude Desktop", skillDir: ".claude/skills", primary: true },
|
|
709
|
+
{ id: "codex", name: "Codex", skillDir: ".codex/skills", primary: true },
|
|
710
|
+
{ id: "cursor", name: "Cursor", skillDir: ".cursor/skills" },
|
|
711
|
+
{ id: "windsurf", name: "Windsurf", skillDir: ".codeium/windsurf/skills" },
|
|
712
|
+
{ id: "kiro", name: "Kiro", skillDir: ".kiro/skills" },
|
|
713
|
+
{ id: "goose", name: "Goose", skillDir: ".config/goose/skills" },
|
|
714
|
+
{ id: "amp", name: "Amp", skillDir: ".amp/skills" },
|
|
715
|
+
{ id: "opencode", name: "OpenCode", skillDir: ".opencode/skills" },
|
|
716
|
+
{ id: "roo", name: "Roo", skillDir: ".roo/skills" }
|
|
717
|
+
];
|
|
718
|
+
var CANONICAL_SKILL_DIR = ".agents/skills";
|
|
719
|
+
function getSkillSourceDir() {
|
|
720
|
+
const __dirname2 = path3.dirname(fileURLToPath(import.meta.url));
|
|
721
|
+
return path3.resolve(__dirname2, "..", "skills", "one");
|
|
722
|
+
}
|
|
723
|
+
function getCanonicalSkillPath() {
|
|
724
|
+
return path3.join(os3.homedir(), CANONICAL_SKILL_DIR, "one");
|
|
725
|
+
}
|
|
726
|
+
function getAgentSkillPath(agent) {
|
|
727
|
+
return path3.join(os3.homedir(), agent.skillDir, "one");
|
|
728
|
+
}
|
|
729
|
+
function isSkillInstalled() {
|
|
730
|
+
return fs3.existsSync(path3.join(getCanonicalSkillPath(), "SKILL.md"));
|
|
731
|
+
}
|
|
732
|
+
function isSkillInstalledForAgent(agent) {
|
|
733
|
+
return fs3.existsSync(path3.join(getAgentSkillPath(agent), "SKILL.md"));
|
|
734
|
+
}
|
|
735
|
+
function copyDirSync(src, dest) {
|
|
736
|
+
fs3.mkdirSync(dest, { recursive: true });
|
|
737
|
+
for (const entry of fs3.readdirSync(src, { withFileTypes: true })) {
|
|
738
|
+
const srcPath = path3.join(src, entry.name);
|
|
739
|
+
const destPath = path3.join(dest, entry.name);
|
|
740
|
+
if (entry.isDirectory()) {
|
|
741
|
+
copyDirSync(srcPath, destPath);
|
|
742
|
+
} else {
|
|
743
|
+
fs3.copyFileSync(srcPath, destPath);
|
|
765
744
|
}
|
|
766
|
-
installMcpConfig(agent, apiKey, "global", ac);
|
|
767
|
-
updateConfigAgents(agent.id);
|
|
768
|
-
p3.log.success(`${agent.name}: MCP installed`);
|
|
769
|
-
p3.outro("Done.");
|
|
770
|
-
return;
|
|
771
745
|
}
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
p3.outro("No changes made.");
|
|
781
|
-
return;
|
|
746
|
+
}
|
|
747
|
+
function installSkillForAgents(agentIds) {
|
|
748
|
+
const source = getSkillSourceDir();
|
|
749
|
+
const canonical = getCanonicalSkillPath();
|
|
750
|
+
const installed = [];
|
|
751
|
+
const failed = [];
|
|
752
|
+
if (!fs3.existsSync(path3.join(source, "SKILL.md"))) {
|
|
753
|
+
return { installed: [], failed: ["skill source not found"] };
|
|
782
754
|
}
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
755
|
+
try {
|
|
756
|
+
if (fs3.existsSync(canonical)) {
|
|
757
|
+
fs3.rmSync(canonical, { recursive: true });
|
|
758
|
+
}
|
|
759
|
+
copyDirSync(source, canonical);
|
|
760
|
+
} catch {
|
|
761
|
+
return { installed: [], failed: ["canonical copy"] };
|
|
788
762
|
}
|
|
789
|
-
|
|
763
|
+
const seen = /* @__PURE__ */ new Map();
|
|
764
|
+
for (const id of agentIds) {
|
|
765
|
+
const agent = SKILL_AGENTS.find((a) => a.id === id);
|
|
766
|
+
if (!agent) continue;
|
|
767
|
+
const agentPath = getAgentSkillPath(agent);
|
|
768
|
+
if (seen.has(agentPath)) {
|
|
769
|
+
(seen.get(agentPath) ? installed : failed).push(agent.name);
|
|
770
|
+
continue;
|
|
771
|
+
}
|
|
772
|
+
try {
|
|
773
|
+
const agentSkillsDir = path3.dirname(agentPath);
|
|
774
|
+
fs3.mkdirSync(agentSkillsDir, { recursive: true });
|
|
775
|
+
try {
|
|
776
|
+
fs3.lstatSync(agentPath);
|
|
777
|
+
fs3.rmSync(agentPath, { recursive: true });
|
|
778
|
+
} catch {
|
|
779
|
+
}
|
|
780
|
+
const relative = path3.relative(agentSkillsDir, canonical);
|
|
781
|
+
fs3.symlinkSync(relative, agentPath);
|
|
782
|
+
installed.push(agent.name);
|
|
783
|
+
seen.set(agentPath, true);
|
|
784
|
+
} catch {
|
|
785
|
+
failed.push(agent.name);
|
|
786
|
+
seen.set(agentPath, false);
|
|
787
|
+
}
|
|
788
|
+
}
|
|
789
|
+
return { installed, failed };
|
|
790
790
|
}
|
|
791
|
-
async function
|
|
792
|
-
const
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
791
|
+
async function promptSkillInstall() {
|
|
792
|
+
const primaryAgents = SKILL_AGENTS.filter((a) => a.primary);
|
|
793
|
+
const otherAgents = SKILL_AGENTS.filter((a) => !a.primary);
|
|
794
|
+
const options = [
|
|
795
|
+
...primaryAgents.map((a) => ({
|
|
796
|
+
value: a.id,
|
|
797
|
+
label: a.name,
|
|
798
|
+
hint: isSkillInstalledForAgent(a) ? pc2.green("installed") : void 0
|
|
799
|
+
})),
|
|
800
|
+
{
|
|
801
|
+
value: "_other",
|
|
802
|
+
label: "Other agents",
|
|
803
|
+
hint: otherAgents.map((a) => a.name).join(", ")
|
|
804
|
+
}
|
|
805
|
+
];
|
|
806
|
+
const choice = await p3.multiselect({
|
|
807
|
+
message: "Install the One skill to:",
|
|
808
|
+
options,
|
|
809
|
+
initialValues: primaryAgents.map((a) => a.id)
|
|
810
|
+
});
|
|
811
|
+
if (p3.isCancel(choice)) {
|
|
812
|
+
return false;
|
|
813
|
+
}
|
|
814
|
+
let selectedIds = choice;
|
|
815
|
+
if (selectedIds.includes("_other")) {
|
|
816
|
+
selectedIds = selectedIds.filter((id) => id !== "_other");
|
|
817
|
+
const otherChoice = await p3.multiselect({
|
|
818
|
+
message: "Select additional agents:",
|
|
819
|
+
options: otherAgents.map((a) => ({
|
|
820
|
+
value: a.id,
|
|
821
|
+
label: a.name,
|
|
822
|
+
hint: isSkillInstalledForAgent(a) ? pc2.green("installed") : void 0
|
|
823
|
+
}))
|
|
798
824
|
});
|
|
799
|
-
if (p3.isCancel(
|
|
800
|
-
|
|
801
|
-
return;
|
|
825
|
+
if (!p3.isCancel(otherChoice)) {
|
|
826
|
+
selectedIds.push(...otherChoice);
|
|
802
827
|
}
|
|
803
|
-
installMcpConfig(agent, apiKey, "project", ac);
|
|
804
|
-
const configPath = getAgentConfigPath(agent, "project");
|
|
805
|
-
p3.log.success(`${agent.name}: ${configPath} created`);
|
|
806
|
-
p3.note(
|
|
807
|
-
pc3.yellow("Project config files can be committed to share with your team.\n") + pc3.yellow("Team members will need their own API key."),
|
|
808
|
-
"Tip"
|
|
809
|
-
);
|
|
810
|
-
p3.outro("Done.");
|
|
811
|
-
return;
|
|
812
828
|
}
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
value: s.agent.id,
|
|
817
|
-
label: s.agent.name
|
|
818
|
-
}))
|
|
819
|
-
});
|
|
820
|
-
if (p3.isCancel(selected)) {
|
|
821
|
-
p3.outro("No changes made.");
|
|
822
|
-
return;
|
|
829
|
+
if (selectedIds.length === 0) {
|
|
830
|
+
p3.log.info("No agents selected.");
|
|
831
|
+
return false;
|
|
823
832
|
}
|
|
824
|
-
const
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
833
|
+
const spinner5 = p3.spinner();
|
|
834
|
+
spinner5.start("Installing skill...");
|
|
835
|
+
const { installed, failed } = installSkillForAgents(selectedIds);
|
|
836
|
+
spinner5.stop(installed.length > 0 ? "Skill installed" : "Installation failed");
|
|
837
|
+
for (const name of installed) {
|
|
838
|
+
p3.log.success(`${name}: ${pc2.green("\u2713")} skill installed`);
|
|
829
839
|
}
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
p3.outro("Done.");
|
|
840
|
+
for (const name of failed) {
|
|
841
|
+
p3.log.warn(`${name}: failed to install`);
|
|
842
|
+
}
|
|
843
|
+
return installed.length > 0;
|
|
835
844
|
}
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
process.removeListener("SIGINT", handler);
|
|
853
|
-
resolve(false);
|
|
854
|
-
});
|
|
855
|
-
});
|
|
845
|
+
function printOnboardingPrompt() {
|
|
846
|
+
console.log();
|
|
847
|
+
console.log(pc2.bold(" Copy-paste this to your AI agent:"));
|
|
848
|
+
console.log();
|
|
849
|
+
console.log(pc2.cyan(" \u250C\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"));
|
|
850
|
+
console.log(pc2.cyan(" \u2502"));
|
|
851
|
+
console.log(pc2.cyan(" \u2502 The One CLI (`one`) is now installed on this machine. It"));
|
|
852
|
+
console.log(pc2.cyan(" \u2502 lets you interact with 250+ third-party platforms (Gmail,"));
|
|
853
|
+
console.log(pc2.cyan(" \u2502 Slack, Shopify, HubSpot, Stripe, GitHub, etc.) through"));
|
|
854
|
+
console.log(pc2.cyan(" \u2502 their APIs. Whenever you need to interact with any"));
|
|
855
|
+
console.log(pc2.cyan(" \u2502 third-party platform or external service, use the `one`"));
|
|
856
|
+
console.log(pc2.cyan(" \u2502 skill. Please save this to your project instructions or"));
|
|
857
|
+
console.log(pc2.cyan(" \u2502 memory so you remember it in future conversations."));
|
|
858
|
+
console.log(pc2.cyan(" \u2502"));
|
|
859
|
+
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
|
+
console.log();
|
|
856
861
|
}
|
|
857
862
|
async function promptAndInstallMcp(apiKey, options) {
|
|
858
863
|
const allAgents = getAllAgents();
|
|
@@ -935,7 +940,7 @@ Project scope is supported by: ${supported}`,
|
|
|
935
940
|
const allInstalled = [...projectAgents, ...nonProjectAgents];
|
|
936
941
|
updateConfigAgentsList(allInstalled.map((a) => a.id));
|
|
937
942
|
p3.note(
|
|
938
|
-
|
|
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."),
|
|
939
944
|
"Tip"
|
|
940
945
|
);
|
|
941
946
|
return;
|
|
@@ -952,7 +957,7 @@ Project scope is supported by: ${supported}`,
|
|
|
952
957
|
}
|
|
953
958
|
async function freshSetup(options) {
|
|
954
959
|
p3.note(`Get your API key at:
|
|
955
|
-
${
|
|
960
|
+
${pc2.cyan(getApiKeyUrl())}`, "API Key");
|
|
956
961
|
const openBrowser = await p3.confirm({
|
|
957
962
|
message: "Open browser to get API key?",
|
|
958
963
|
initialValue: true
|
|
@@ -994,20 +999,7 @@ ${pc3.cyan(getApiKeyUrl())}`, "API Key");
|
|
|
994
999
|
installedAgents: [],
|
|
995
1000
|
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
996
1001
|
});
|
|
997
|
-
|
|
998
|
-
message: "Install One skills to your AI agents? (recommended)",
|
|
999
|
-
initialValue: true
|
|
1000
|
-
});
|
|
1001
|
-
if (!p3.isCancel(installSkills) && installSkills) {
|
|
1002
|
-
const success = await runSkillsInstall();
|
|
1003
|
-
if (success) {
|
|
1004
|
-
p3.log.success("Skills installed successfully.");
|
|
1005
|
-
} else {
|
|
1006
|
-
const __dirname2 = path3.dirname(fileURLToPath(import.meta.url));
|
|
1007
|
-
const skillsDir = path3.resolve(__dirname2, "..", "skills");
|
|
1008
|
-
p3.log.warn(`Skills installation failed. You can try manually: ${pc3.cyan(`npx skills add ${skillsDir}`)}`);
|
|
1009
|
-
}
|
|
1010
|
-
}
|
|
1002
|
+
await promptSkillInstall();
|
|
1011
1003
|
await promptConnectIntegrations(apiKey);
|
|
1012
1004
|
const installMcp = await p3.confirm({
|
|
1013
1005
|
message: "Install One MCP server to your AI agents? (not recommended)",
|
|
@@ -1017,29 +1009,26 @@ ${pc3.cyan(getApiKeyUrl())}`, "API Key");
|
|
|
1017
1009
|
await promptAndInstallMcp(apiKey, options);
|
|
1018
1010
|
}
|
|
1019
1011
|
p3.note(
|
|
1020
|
-
`Config saved to: ${
|
|
1012
|
+
`Config saved to: ${pc2.dim(getConfigPath())}`,
|
|
1021
1013
|
"Setup Complete"
|
|
1022
1014
|
);
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
"Next Step"
|
|
1026
|
-
);
|
|
1027
|
-
p3.outro("Your AI agents now have access to One integrations!");
|
|
1015
|
+
printOnboardingPrompt();
|
|
1016
|
+
p3.outro("Done! Paste the prompt above to your AI agent.");
|
|
1028
1017
|
}
|
|
1029
1018
|
function printBanner() {
|
|
1030
1019
|
console.log();
|
|
1031
|
-
console.log(
|
|
1032
|
-
console.log(
|
|
1033
|
-
console.log(
|
|
1034
|
-
console.log(
|
|
1035
|
-
console.log(
|
|
1036
|
-
console.log(
|
|
1037
|
-
console.log(
|
|
1038
|
-
console.log(
|
|
1039
|
-
console.log(
|
|
1040
|
-
console.log(
|
|
1020
|
+
console.log(pc2.yellow(" \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588"));
|
|
1021
|
+
console.log(pc2.yellow(" \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588"));
|
|
1022
|
+
console.log(pc2.yellow(" \u2588\u2588\u2588\u2588\u2588\u2588 \u2588\u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588\u2588 "));
|
|
1023
|
+
console.log(pc2.yellow(" \u2588\u2588\u2588\u2588\u2588\u2588 \u2588\u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588\u2588 "));
|
|
1024
|
+
console.log(pc2.yellow(" \u2588\u2588\u2588\u2588\u2588\u2588 \u2588\u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588 "));
|
|
1025
|
+
console.log(pc2.yellow(" \u2588\u2588\u2588\u2588\u2588\u2588 \u2588\u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588 "));
|
|
1026
|
+
console.log(pc2.yellow(" \u2588\u2588\u2588\u2588\u2588\u2588 \u2588\u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588\u2588 "));
|
|
1027
|
+
console.log(pc2.yellow(" \u2588\u2588\u2588\u2588\u2588\u2588 \u2588\u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588\u2588 "));
|
|
1028
|
+
console.log(pc2.yellow(" \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588"));
|
|
1029
|
+
console.log(pc2.yellow(" \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588"));
|
|
1041
1030
|
console.log();
|
|
1042
|
-
console.log(
|
|
1031
|
+
console.log(pc2.dim(" I N F R A S T R U C T U R E F O R A G E N T S"));
|
|
1043
1032
|
console.log();
|
|
1044
1033
|
}
|
|
1045
1034
|
var TOP_INTEGRATIONS = [
|
|
@@ -1084,13 +1073,13 @@ async function promptConnectIntegrations(apiKey) {
|
|
|
1084
1073
|
} catch {
|
|
1085
1074
|
p3.note("https://app.withone.ai/connections", "Open in browser");
|
|
1086
1075
|
}
|
|
1087
|
-
p3.log.info(`Connect from the dashboard, or use ${
|
|
1076
|
+
p3.log.info(`Connect from the dashboard, or use ${pc2.cyan("one add <platform>")}`);
|
|
1088
1077
|
break;
|
|
1089
1078
|
}
|
|
1090
1079
|
const platform = choice;
|
|
1091
1080
|
const integration = TOP_INTEGRATIONS.find((i) => i.value === platform);
|
|
1092
1081
|
const label = integration?.label ?? platform;
|
|
1093
|
-
p3.log.info(`Opening browser to connect ${
|
|
1082
|
+
p3.log.info(`Opening browser to connect ${pc2.cyan(label)}...`);
|
|
1094
1083
|
try {
|
|
1095
1084
|
await openConnectionPage(platform);
|
|
1096
1085
|
} catch {
|
|
@@ -1103,13 +1092,13 @@ async function promptConnectIntegrations(apiKey) {
|
|
|
1103
1092
|
try {
|
|
1104
1093
|
await api.waitForConnection(platform, 5 * 60 * 1e3, 5e3);
|
|
1105
1094
|
spinner5.stop(`${label} connected!`);
|
|
1106
|
-
p3.log.success(`${
|
|
1095
|
+
p3.log.success(`${pc2.green("\u2713")} ${label} is now available to your AI agents`);
|
|
1107
1096
|
connected.push(platform);
|
|
1108
1097
|
first = false;
|
|
1109
1098
|
} catch (error2) {
|
|
1110
1099
|
spinner5.stop("Connection timed out");
|
|
1111
1100
|
if (error2 instanceof TimeoutError) {
|
|
1112
|
-
p3.log.warn(`No worries. Connect later with: ${
|
|
1101
|
+
p3.log.warn(`No worries. Connect later with: ${pc2.cyan(`one add ${platform}`)}`);
|
|
1113
1102
|
}
|
|
1114
1103
|
first = false;
|
|
1115
1104
|
}
|
|
@@ -1123,14 +1112,6 @@ function maskApiKey(key) {
|
|
|
1123
1112
|
if (key.length <= 12) return key.slice(0, 8) + "...";
|
|
1124
1113
|
return key.slice(0, 8) + "..." + key.slice(-4);
|
|
1125
1114
|
}
|
|
1126
|
-
function updateConfigAgents(agentId) {
|
|
1127
|
-
const config = readConfig();
|
|
1128
|
-
if (!config) return;
|
|
1129
|
-
if (!config.installedAgents.includes(agentId)) {
|
|
1130
|
-
config.installedAgents.push(agentId);
|
|
1131
|
-
writeConfig(config);
|
|
1132
|
-
}
|
|
1133
|
-
}
|
|
1134
1115
|
function updateConfigAgentsList(agentIds) {
|
|
1135
1116
|
const config = readConfig();
|
|
1136
1117
|
if (!config) return;
|
|
@@ -1181,6 +1162,42 @@ function countMatchingChars(a, b) {
|
|
|
1181
1162
|
return count;
|
|
1182
1163
|
}
|
|
1183
1164
|
|
|
1165
|
+
// src/lib/table.ts
|
|
1166
|
+
import pc3 from "picocolors";
|
|
1167
|
+
function printTable(columns, rows) {
|
|
1168
|
+
if (rows.length === 0) return;
|
|
1169
|
+
const gap = " ";
|
|
1170
|
+
const indent = " ";
|
|
1171
|
+
const widths = columns.map((col) => {
|
|
1172
|
+
const headerLen = col.label.length;
|
|
1173
|
+
const maxValueLen = Math.max(...rows.map((row) => stripAnsi(row[col.key] || "").length));
|
|
1174
|
+
return Math.max(headerLen, maxValueLen);
|
|
1175
|
+
});
|
|
1176
|
+
const header = columns.map((col, i) => {
|
|
1177
|
+
const padded = col.align === "right" ? col.label.padStart(widths[i]) : col.label.padEnd(widths[i]);
|
|
1178
|
+
return pc3.dim(padded);
|
|
1179
|
+
}).join(gap);
|
|
1180
|
+
console.log(`${indent}${header}`);
|
|
1181
|
+
const separator = columns.map((_, i) => pc3.dim("\u2500".repeat(widths[i]))).join(gap);
|
|
1182
|
+
console.log(`${indent}${separator}`);
|
|
1183
|
+
for (const row of rows) {
|
|
1184
|
+
const line = columns.map((col, i) => {
|
|
1185
|
+
const raw = row[col.key] || "";
|
|
1186
|
+
const rawLen = stripAnsi(raw).length;
|
|
1187
|
+
const padding = widths[i] - rawLen;
|
|
1188
|
+
const colored = col.color ? col.color(raw) : raw;
|
|
1189
|
+
if (col.align === "right") {
|
|
1190
|
+
return " ".repeat(Math.max(0, padding)) + colored;
|
|
1191
|
+
}
|
|
1192
|
+
return colored + " ".repeat(Math.max(0, padding));
|
|
1193
|
+
}).join(gap);
|
|
1194
|
+
console.log(`${indent}${line}`);
|
|
1195
|
+
}
|
|
1196
|
+
}
|
|
1197
|
+
function stripAnsi(str) {
|
|
1198
|
+
return str.replace(/\x1b\[[0-9;]*m/g, "");
|
|
1199
|
+
}
|
|
1200
|
+
|
|
1184
1201
|
// src/commands/connection.ts
|
|
1185
1202
|
async function connectionAddCommand(platformArg) {
|
|
1186
1203
|
if (isAgentMode()) {
|
|
@@ -1680,57 +1697,576 @@ async function actionsExecuteCommand(platform, actionId, connectionKey, options)
|
|
|
1680
1697
|
console.log(pc6.dim("Body:"));
|
|
1681
1698
|
console.log(pc6.dim(JSON.stringify(result.requestConfig.data, null, 2)));
|
|
1682
1699
|
}
|
|
1683
|
-
console.log();
|
|
1684
|
-
note2("Dry run \u2014 request was not sent", "Dry Run");
|
|
1685
|
-
} else {
|
|
1686
|
-
console.log();
|
|
1687
|
-
console.log(pc6.bold("Response:"));
|
|
1688
|
-
console.log(JSON.stringify(result.responseData, null, 2));
|
|
1700
|
+
console.log();
|
|
1701
|
+
note2("Dry run \u2014 request was not sent", "Dry Run");
|
|
1702
|
+
} else {
|
|
1703
|
+
console.log();
|
|
1704
|
+
console.log(pc6.bold("Response:"));
|
|
1705
|
+
console.log(JSON.stringify(result.responseData, null, 2));
|
|
1706
|
+
}
|
|
1707
|
+
} catch (error2) {
|
|
1708
|
+
spinner5.stop("Execution failed");
|
|
1709
|
+
error(
|
|
1710
|
+
`Error: ${error2 instanceof Error ? error2.message : "Unknown error"}`
|
|
1711
|
+
);
|
|
1712
|
+
}
|
|
1713
|
+
}
|
|
1714
|
+
function colorMethod(method) {
|
|
1715
|
+
switch (method.toUpperCase()) {
|
|
1716
|
+
case "GET":
|
|
1717
|
+
return pc6.green(method);
|
|
1718
|
+
case "POST":
|
|
1719
|
+
return pc6.yellow(method);
|
|
1720
|
+
case "PUT":
|
|
1721
|
+
return pc6.blue(method);
|
|
1722
|
+
case "PATCH":
|
|
1723
|
+
return pc6.magenta(method);
|
|
1724
|
+
case "DELETE":
|
|
1725
|
+
return pc6.red(method);
|
|
1726
|
+
default:
|
|
1727
|
+
return method;
|
|
1728
|
+
}
|
|
1729
|
+
}
|
|
1730
|
+
|
|
1731
|
+
// src/commands/flow.ts
|
|
1732
|
+
import pc7 from "picocolors";
|
|
1733
|
+
|
|
1734
|
+
// src/lib/flow-schema.ts
|
|
1735
|
+
var FLOW_SCHEMA = {
|
|
1736
|
+
errorStrategies: ["fail", "continue", "retry", "fallback"],
|
|
1737
|
+
validInputTypes: ["string", "number", "boolean", "object", "array"],
|
|
1738
|
+
flowFields: {
|
|
1739
|
+
key: { type: "string", required: true, description: "Unique kebab-case identifier", pattern: /^[a-z0-9][a-z0-9-]*[a-z0-9]$/ },
|
|
1740
|
+
name: { type: "string", required: true, description: "Human-readable flow name" },
|
|
1741
|
+
description: { type: "string", required: false, description: "What this flow does" },
|
|
1742
|
+
version: { type: "string", required: false, description: "Semver or arbitrary version string" },
|
|
1743
|
+
inputs: { type: "object", required: true, description: "Input declarations (Record<string, InputDeclaration>)" },
|
|
1744
|
+
steps: { type: "array", required: true, description: "Ordered array of steps", stepsArray: true }
|
|
1745
|
+
},
|
|
1746
|
+
inputFields: {
|
|
1747
|
+
type: { type: "string", required: true, description: "Data type: string, number, boolean, object, array", enum: ["string", "number", "boolean", "object", "array"] },
|
|
1748
|
+
required: { type: "boolean", required: false, description: "Whether this input must be provided" },
|
|
1749
|
+
default: { type: "unknown", required: false, description: "Default value if not provided" },
|
|
1750
|
+
description: { type: "string", required: false, description: "Human-readable description" },
|
|
1751
|
+
connection: { type: "object", required: false, description: 'Connection metadata: { platform: "gmail" } \u2014 enables auto-resolution' }
|
|
1752
|
+
},
|
|
1753
|
+
stepCommonFields: {
|
|
1754
|
+
id: { type: "string", required: true, description: "Unique step identifier (used in selectors)" },
|
|
1755
|
+
name: { type: "string", required: true, description: "Human-readable step label" },
|
|
1756
|
+
type: { type: "string", required: true, description: "Step type (determines which config object is required)" },
|
|
1757
|
+
if: { type: "string", required: false, description: "JS expression \u2014 skip step if falsy" },
|
|
1758
|
+
unless: { type: "string", required: false, description: "JS expression \u2014 skip step if truthy" }
|
|
1759
|
+
},
|
|
1760
|
+
stepTypes: [
|
|
1761
|
+
{
|
|
1762
|
+
type: "action",
|
|
1763
|
+
configKey: "action",
|
|
1764
|
+
description: "Execute a platform API action",
|
|
1765
|
+
fields: {
|
|
1766
|
+
platform: { type: "string", required: true, description: "Platform name (kebab-case)" },
|
|
1767
|
+
actionId: { type: "string", required: true, description: "Action ID from `actions search`" },
|
|
1768
|
+
connectionKey: { type: "string", required: true, description: "Connection key (use $.input selector)" },
|
|
1769
|
+
data: { type: "object", required: false, description: "Request body (POST/PUT/PATCH)" },
|
|
1770
|
+
pathVars: { type: "object", required: false, description: "URL path variables" },
|
|
1771
|
+
queryParams: { type: "object", required: false, description: "Query parameters" },
|
|
1772
|
+
headers: { type: "object", required: false, description: "Additional headers" }
|
|
1773
|
+
},
|
|
1774
|
+
example: {
|
|
1775
|
+
id: "findCustomer",
|
|
1776
|
+
name: "Search Stripe customers",
|
|
1777
|
+
type: "action",
|
|
1778
|
+
action: {
|
|
1779
|
+
platform: "stripe",
|
|
1780
|
+
actionId: "conn_mod_def::xxx::yyy",
|
|
1781
|
+
connectionKey: "$.input.stripeConnectionKey",
|
|
1782
|
+
data: { query: "email:'{{$.input.customerEmail}}'" }
|
|
1783
|
+
}
|
|
1784
|
+
}
|
|
1785
|
+
},
|
|
1786
|
+
{
|
|
1787
|
+
type: "transform",
|
|
1788
|
+
configKey: "transform",
|
|
1789
|
+
description: "Single JS expression with implicit return",
|
|
1790
|
+
fields: {
|
|
1791
|
+
expression: { type: "string", required: true, description: "JS expression evaluated with flow context as $" }
|
|
1792
|
+
},
|
|
1793
|
+
example: {
|
|
1794
|
+
id: "extractNames",
|
|
1795
|
+
name: "Extract customer names",
|
|
1796
|
+
type: "transform",
|
|
1797
|
+
transform: { expression: "$.steps.findCustomer.response.data.map(c => c.name)" }
|
|
1798
|
+
}
|
|
1799
|
+
},
|
|
1800
|
+
{
|
|
1801
|
+
type: "code",
|
|
1802
|
+
configKey: "code",
|
|
1803
|
+
description: "Multi-line async JS with explicit return",
|
|
1804
|
+
fields: {
|
|
1805
|
+
source: { type: "string", required: true, description: "JS function body (flow context as $, supports await)" }
|
|
1806
|
+
},
|
|
1807
|
+
example: {
|
|
1808
|
+
id: "processData",
|
|
1809
|
+
name: "Process and enrich data",
|
|
1810
|
+
type: "code",
|
|
1811
|
+
code: { source: "const items = $.steps.fetch.response.data;\nreturn items.filter(i => i.active);" }
|
|
1812
|
+
}
|
|
1813
|
+
},
|
|
1814
|
+
{
|
|
1815
|
+
type: "condition",
|
|
1816
|
+
configKey: "condition",
|
|
1817
|
+
description: "If/then/else branching",
|
|
1818
|
+
fields: {
|
|
1819
|
+
expression: { type: "string", required: true, description: "JS expression \u2014 truthy runs then, falsy runs else" },
|
|
1820
|
+
then: { type: "array", required: true, description: "Steps to run when true", stepsArray: true },
|
|
1821
|
+
else: { type: "array", required: false, description: "Steps to run when false", stepsArray: true }
|
|
1822
|
+
},
|
|
1823
|
+
example: {
|
|
1824
|
+
id: "checkFound",
|
|
1825
|
+
name: "Check if customer exists",
|
|
1826
|
+
type: "condition",
|
|
1827
|
+
condition: {
|
|
1828
|
+
expression: "$.steps.search.response.data.length > 0",
|
|
1829
|
+
then: [{ id: "notify", name: "Send notification", type: "action", action: { platform: "slack", actionId: "...", connectionKey: "$.input.slackKey", data: { text: "Found!" } } }],
|
|
1830
|
+
else: [{ id: "logMiss", name: "Log not found", type: "transform", transform: { expression: "'Not found'" } }]
|
|
1831
|
+
}
|
|
1832
|
+
}
|
|
1833
|
+
},
|
|
1834
|
+
{
|
|
1835
|
+
type: "loop",
|
|
1836
|
+
configKey: "loop",
|
|
1837
|
+
description: "Iterate over an array with optional concurrency",
|
|
1838
|
+
fields: {
|
|
1839
|
+
over: { type: "string", required: true, description: "Selector resolving to an array" },
|
|
1840
|
+
as: { type: "string", required: true, description: "Variable name for current item ($.loop.<as>)" },
|
|
1841
|
+
indexAs: { type: "string", required: false, description: "Variable name for index" },
|
|
1842
|
+
steps: { type: "array", required: true, description: "Steps to run per iteration", stepsArray: true },
|
|
1843
|
+
maxIterations: { type: "number", required: false, description: "Safety cap (default: no limit)" },
|
|
1844
|
+
maxConcurrency: { type: "number", required: false, description: "Parallel batch size (default: 1 = sequential)" }
|
|
1845
|
+
},
|
|
1846
|
+
example: {
|
|
1847
|
+
id: "processOrders",
|
|
1848
|
+
name: "Process each order",
|
|
1849
|
+
type: "loop",
|
|
1850
|
+
loop: {
|
|
1851
|
+
over: "$.steps.listOrders.response.data",
|
|
1852
|
+
as: "order",
|
|
1853
|
+
steps: [{ id: "createInvoice", name: "Create invoice", type: "action", action: { platform: "stripe", actionId: "...", connectionKey: "$.input.stripeKey", data: { amount: "$.loop.order.total" } } }]
|
|
1854
|
+
}
|
|
1855
|
+
}
|
|
1856
|
+
},
|
|
1857
|
+
{
|
|
1858
|
+
type: "parallel",
|
|
1859
|
+
configKey: "parallel",
|
|
1860
|
+
description: "Run steps concurrently",
|
|
1861
|
+
fields: {
|
|
1862
|
+
steps: { type: "array", required: true, description: "Steps to run in parallel", stepsArray: true },
|
|
1863
|
+
maxConcurrency: { type: "number", required: false, description: "Max concurrent steps (default: 5)" }
|
|
1864
|
+
},
|
|
1865
|
+
example: {
|
|
1866
|
+
id: "lookups",
|
|
1867
|
+
name: "Parallel data lookups",
|
|
1868
|
+
type: "parallel",
|
|
1869
|
+
parallel: {
|
|
1870
|
+
steps: [
|
|
1871
|
+
{ id: "getStripe", name: "Get Stripe data", type: "action", action: { platform: "stripe", actionId: "...", connectionKey: "$.input.stripeKey" } },
|
|
1872
|
+
{ id: "getSlack", name: "Get Slack data", type: "action", action: { platform: "slack", actionId: "...", connectionKey: "$.input.slackKey" } }
|
|
1873
|
+
]
|
|
1874
|
+
}
|
|
1875
|
+
}
|
|
1876
|
+
},
|
|
1877
|
+
{
|
|
1878
|
+
type: "file-read",
|
|
1879
|
+
configKey: "fileRead",
|
|
1880
|
+
description: "Read a file (optional JSON parse)",
|
|
1881
|
+
fields: {
|
|
1882
|
+
path: { type: "string", required: true, description: "File path to read" },
|
|
1883
|
+
parseJson: { type: "boolean", required: false, description: "Parse contents as JSON (default: false)" }
|
|
1884
|
+
},
|
|
1885
|
+
example: {
|
|
1886
|
+
id: "readConfig",
|
|
1887
|
+
name: "Read config file",
|
|
1888
|
+
type: "file-read",
|
|
1889
|
+
fileRead: { path: "./data/config.json", parseJson: true }
|
|
1890
|
+
}
|
|
1891
|
+
},
|
|
1892
|
+
{
|
|
1893
|
+
type: "file-write",
|
|
1894
|
+
configKey: "fileWrite",
|
|
1895
|
+
description: "Write or append to a file",
|
|
1896
|
+
fields: {
|
|
1897
|
+
path: { type: "string", required: true, description: "File path to write" },
|
|
1898
|
+
content: { type: "unknown", required: true, description: "Content to write (supports selectors)" },
|
|
1899
|
+
append: { type: "boolean", required: false, description: "Append instead of overwrite (default: false)" }
|
|
1900
|
+
},
|
|
1901
|
+
example: {
|
|
1902
|
+
id: "writeResults",
|
|
1903
|
+
name: "Save results",
|
|
1904
|
+
type: "file-write",
|
|
1905
|
+
fileWrite: { path: "./output/results.json", content: "$.steps.transform.output" }
|
|
1906
|
+
}
|
|
1907
|
+
},
|
|
1908
|
+
{
|
|
1909
|
+
type: "while",
|
|
1910
|
+
configKey: "while",
|
|
1911
|
+
description: "Do-while loop with condition check",
|
|
1912
|
+
fields: {
|
|
1913
|
+
condition: { type: "string", required: true, description: "JS expression checked before each iteration (after first)" },
|
|
1914
|
+
steps: { type: "array", required: true, description: "Steps to run each iteration", stepsArray: true },
|
|
1915
|
+
maxIterations: { type: "number", required: false, description: "Safety cap (default: 100)" }
|
|
1916
|
+
},
|
|
1917
|
+
example: {
|
|
1918
|
+
id: "paginate",
|
|
1919
|
+
name: "Paginate through pages",
|
|
1920
|
+
type: "while",
|
|
1921
|
+
while: {
|
|
1922
|
+
condition: "$.steps.paginate.output.lastResult.nextPageToken != null",
|
|
1923
|
+
maxIterations: 50,
|
|
1924
|
+
steps: [{ id: "fetchPage", name: "Fetch next page", type: "action", action: { platform: "gmail", actionId: "...", connectionKey: "$.input.gmailKey" } }]
|
|
1925
|
+
}
|
|
1926
|
+
}
|
|
1927
|
+
},
|
|
1928
|
+
{
|
|
1929
|
+
type: "flow",
|
|
1930
|
+
configKey: "flow",
|
|
1931
|
+
description: "Execute a sub-flow (supports composition)",
|
|
1932
|
+
fields: {
|
|
1933
|
+
key: { type: "string", required: true, description: "Flow key or path of the sub-flow" },
|
|
1934
|
+
inputs: { type: "object", required: false, description: "Inputs to pass to the sub-flow (supports selectors)" }
|
|
1935
|
+
},
|
|
1936
|
+
example: {
|
|
1937
|
+
id: "enrich",
|
|
1938
|
+
name: "Run enrichment sub-flow",
|
|
1939
|
+
type: "flow",
|
|
1940
|
+
flow: { key: "enrich-customer", inputs: { email: "$.steps.getCustomer.response.email" } }
|
|
1941
|
+
}
|
|
1942
|
+
},
|
|
1943
|
+
{
|
|
1944
|
+
type: "paginate",
|
|
1945
|
+
configKey: "paginate",
|
|
1946
|
+
description: "Auto-paginate API results into a single array",
|
|
1947
|
+
fields: {
|
|
1948
|
+
action: { type: "object", required: true, description: "Action config (same shape as action step: platform, actionId, connectionKey)" },
|
|
1949
|
+
pageTokenField: { type: "string", required: true, description: "Dot-path in response to next page token" },
|
|
1950
|
+
resultsField: { type: "string", required: true, description: "Dot-path in response to results array" },
|
|
1951
|
+
inputTokenParam: { type: "string", required: true, description: "Dot-path in action config where page token is injected" },
|
|
1952
|
+
maxPages: { type: "number", required: false, description: "Max pages to fetch (default: 10)" }
|
|
1953
|
+
},
|
|
1954
|
+
example: {
|
|
1955
|
+
id: "allMessages",
|
|
1956
|
+
name: "Fetch all Gmail messages",
|
|
1957
|
+
type: "paginate",
|
|
1958
|
+
paginate: {
|
|
1959
|
+
action: { platform: "gmail", actionId: "...", connectionKey: "$.input.gmailKey", queryParams: { maxResults: 100 } },
|
|
1960
|
+
pageTokenField: "nextPageToken",
|
|
1961
|
+
resultsField: "messages",
|
|
1962
|
+
inputTokenParam: "queryParams.pageToken",
|
|
1963
|
+
maxPages: 10
|
|
1964
|
+
}
|
|
1965
|
+
}
|
|
1966
|
+
},
|
|
1967
|
+
{
|
|
1968
|
+
type: "bash",
|
|
1969
|
+
configKey: "bash",
|
|
1970
|
+
description: "Shell command (requires --allow-bash)",
|
|
1971
|
+
fields: {
|
|
1972
|
+
command: { type: "string", required: true, description: "Shell command to execute (supports selectors)" },
|
|
1973
|
+
timeout: { type: "number", required: false, description: "Timeout in ms (default: 30000)" },
|
|
1974
|
+
parseJson: { type: "boolean", required: false, description: "Parse stdout as JSON (default: false)" },
|
|
1975
|
+
cwd: { type: "string", required: false, description: "Working directory (supports selectors)" },
|
|
1976
|
+
env: { type: "object", required: false, description: "Additional environment variables" }
|
|
1977
|
+
},
|
|
1978
|
+
example: {
|
|
1979
|
+
id: "analyze",
|
|
1980
|
+
name: "Analyze with Claude",
|
|
1981
|
+
type: "bash",
|
|
1982
|
+
bash: {
|
|
1983
|
+
command: "cat /tmp/data.json | claude --print 'Analyze this data' --output-format json",
|
|
1984
|
+
timeout: 18e4,
|
|
1985
|
+
parseJson: true
|
|
1986
|
+
}
|
|
1987
|
+
}
|
|
1988
|
+
}
|
|
1989
|
+
]
|
|
1990
|
+
};
|
|
1991
|
+
var _coveredTypes = Object.fromEntries(
|
|
1992
|
+
FLOW_SCHEMA.stepTypes.map((st) => [st.type, true])
|
|
1993
|
+
);
|
|
1994
|
+
var _stepTypeMap = new Map(
|
|
1995
|
+
FLOW_SCHEMA.stepTypes.map((st) => [st.type, st])
|
|
1996
|
+
);
|
|
1997
|
+
function getStepTypeDescriptor(type) {
|
|
1998
|
+
return _stepTypeMap.get(type);
|
|
1999
|
+
}
|
|
2000
|
+
function getValidStepTypes() {
|
|
2001
|
+
return FLOW_SCHEMA.stepTypes.map((st) => st.type);
|
|
2002
|
+
}
|
|
2003
|
+
function getNestedStepsKeys() {
|
|
2004
|
+
const result = [];
|
|
2005
|
+
for (const st of FLOW_SCHEMA.stepTypes) {
|
|
2006
|
+
for (const [fieldName, fd] of Object.entries(st.fields)) {
|
|
2007
|
+
if (fd.stepsArray) {
|
|
2008
|
+
result.push({ configKey: st.configKey, fieldName });
|
|
2009
|
+
}
|
|
2010
|
+
}
|
|
2011
|
+
}
|
|
2012
|
+
return result;
|
|
2013
|
+
}
|
|
2014
|
+
function generateFlowGuide() {
|
|
2015
|
+
const validTypes = getValidStepTypes();
|
|
2016
|
+
const sections = [];
|
|
2017
|
+
sections.push(`# One Flows \u2014 Reference
|
|
2018
|
+
|
|
2019
|
+
## Overview
|
|
2020
|
+
|
|
2021
|
+
Workflows are JSON files at \`.one/flows/<key>.flow.json\` that chain actions across platforms.
|
|
2022
|
+
|
|
2023
|
+
## Commands
|
|
2024
|
+
|
|
2025
|
+
\`\`\`bash
|
|
2026
|
+
one --agent flow create <key> --definition '<json>' # Create (or --definition @file.json)
|
|
2027
|
+
one --agent flow create <key> --definition @flow.json # Create from file
|
|
2028
|
+
one --agent flow list # List
|
|
2029
|
+
one --agent flow validate <key> # Validate
|
|
2030
|
+
one --agent flow execute <key> -i name=value # Execute
|
|
2031
|
+
one --agent flow execute <key> --dry-run --mock # Test with mock data
|
|
2032
|
+
one --agent flow execute <key> --allow-bash # Enable bash steps
|
|
2033
|
+
one --agent flow runs [flowKey] # List past runs
|
|
2034
|
+
one --agent flow resume <runId> # Resume failed run
|
|
2035
|
+
one --agent flow scaffold [template] # Generate a starter template
|
|
2036
|
+
\`\`\`
|
|
2037
|
+
|
|
2038
|
+
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.
|
|
2039
|
+
|
|
2040
|
+
## Building a Workflow
|
|
2041
|
+
|
|
2042
|
+
1. **Design first** \u2014 clarify the end goal, map the full value chain, identify where AI analysis is needed
|
|
2043
|
+
2. **Discover connections** \u2014 \`one --agent connection list\`
|
|
2044
|
+
3. **Get knowledge** for every action \u2014 \`one --agent actions knowledge <platform> <actionId>\`
|
|
2045
|
+
4. **Construct JSON** \u2014 declare inputs, wire steps with selectors
|
|
2046
|
+
5. **Validate** \u2014 \`one --agent flow validate <key>\`
|
|
2047
|
+
6. **Execute** \u2014 \`one --agent flow execute <key> -i param=value\``);
|
|
2048
|
+
sections.push(`## Flow JSON Schema
|
|
2049
|
+
|
|
2050
|
+
\`\`\`json
|
|
2051
|
+
{
|
|
2052
|
+
"key": "my-workflow",
|
|
2053
|
+
"name": "My Workflow",
|
|
2054
|
+
"description": "What this flow does",
|
|
2055
|
+
"version": "1",
|
|
2056
|
+
"inputs": {
|
|
2057
|
+
"connectionKey": {
|
|
2058
|
+
"type": "string",
|
|
2059
|
+
"required": true,
|
|
2060
|
+
"description": "Platform connection key",
|
|
2061
|
+
"connection": { "platform": "stripe" }
|
|
2062
|
+
},
|
|
2063
|
+
"param": {
|
|
2064
|
+
"type": "string",
|
|
2065
|
+
"required": true,
|
|
2066
|
+
"description": "A user parameter"
|
|
2067
|
+
}
|
|
2068
|
+
},
|
|
2069
|
+
"steps": [
|
|
2070
|
+
{
|
|
2071
|
+
"id": "stepId",
|
|
2072
|
+
"name": "Human-readable step name",
|
|
2073
|
+
"type": "action",
|
|
2074
|
+
"action": {
|
|
2075
|
+
"platform": "stripe",
|
|
2076
|
+
"actionId": "conn_mod_def::xxx::yyy",
|
|
2077
|
+
"connectionKey": "$.input.connectionKey",
|
|
2078
|
+
"data": { "query": "{{$.input.param}}" }
|
|
2079
|
+
}
|
|
2080
|
+
}
|
|
2081
|
+
]
|
|
2082
|
+
}
|
|
2083
|
+
\`\`\`
|
|
2084
|
+
|
|
2085
|
+
### Top-level fields
|
|
2086
|
+
|
|
2087
|
+
| Field | Type | Required | Description |
|
|
2088
|
+
|-------|------|----------|-------------|`);
|
|
2089
|
+
for (const [name, fd] of Object.entries(FLOW_SCHEMA.flowFields)) {
|
|
2090
|
+
sections.push(`| \`${name}\` | ${fd.type} | ${fd.required ? "yes" : "no"} | ${fd.description} |`);
|
|
2091
|
+
}
|
|
2092
|
+
sections.push(`
|
|
2093
|
+
### Input declarations
|
|
2094
|
+
|
|
2095
|
+
| Field | Type | Required | Description |
|
|
2096
|
+
|-------|------|----------|-------------|`);
|
|
2097
|
+
for (const [name, fd] of Object.entries(FLOW_SCHEMA.inputFields)) {
|
|
2098
|
+
sections.push(`| \`${name}\` | ${fd.type} | ${fd.required ? "yes" : "no"} | ${fd.description} |`);
|
|
2099
|
+
}
|
|
2100
|
+
sections.push(`
|
|
2101
|
+
### Step fields (all steps)
|
|
2102
|
+
|
|
2103
|
+
Every step MUST have \`id\`, \`name\`, and \`type\`. The \`type\` determines which config object is required.
|
|
2104
|
+
|
|
2105
|
+
| Field | Type | Required | Description |
|
|
2106
|
+
|-------|------|----------|-------------|`);
|
|
2107
|
+
for (const [name, fd] of Object.entries(FLOW_SCHEMA.stepCommonFields)) {
|
|
2108
|
+
sections.push(`| \`${name}\` | ${fd.type} | ${fd.required ? "yes" : "no"} | ${fd.description} |`);
|
|
2109
|
+
}
|
|
2110
|
+
sections.push(`| \`onError\` | object | no | Error handling: \`{ "strategy": "${FLOW_SCHEMA.errorStrategies.join(" | ")}", "retries": 3, "retryDelayMs": 1000 }\` |`);
|
|
2111
|
+
sections.push(`
|
|
2112
|
+
## Step Types
|
|
2113
|
+
|
|
2114
|
+
**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).
|
|
2115
|
+
|
|
2116
|
+
| Type | Config Key | Description |
|
|
2117
|
+
|------|-----------|-------------|`);
|
|
2118
|
+
for (const st of FLOW_SCHEMA.stepTypes) {
|
|
2119
|
+
const keyNote = st.type !== st.configKey ? ` \u26A0\uFE0F` : "";
|
|
2120
|
+
sections.push(`| \`${st.type}\` | \`${st.configKey}\`${keyNote} | ${st.description} |`);
|
|
2121
|
+
}
|
|
2122
|
+
sections.push(`
|
|
2123
|
+
## Step Type Reference`);
|
|
2124
|
+
for (const st of FLOW_SCHEMA.stepTypes) {
|
|
2125
|
+
sections.push(`
|
|
2126
|
+
### \`${st.type}\` \u2014 ${st.description}`);
|
|
2127
|
+
if (st.type !== st.configKey) {
|
|
2128
|
+
sections.push(`
|
|
2129
|
+
> **Note:** Type is \`"${st.type}"\` but config key is \`"${st.configKey}"\` (camelCase).`);
|
|
2130
|
+
}
|
|
2131
|
+
sections.push(`
|
|
2132
|
+
| Field | Type | Required | Description |
|
|
2133
|
+
|-------|------|----------|-------------|`);
|
|
2134
|
+
for (const [name, fd] of Object.entries(st.fields)) {
|
|
2135
|
+
sections.push(`| \`${name}\` | ${fd.type} | ${fd.required ? "yes" : "no"} | ${fd.description} |`);
|
|
2136
|
+
}
|
|
2137
|
+
sections.push(`
|
|
2138
|
+
\`\`\`json
|
|
2139
|
+
${JSON.stringify(st.example, null, 2)}
|
|
2140
|
+
\`\`\``);
|
|
2141
|
+
}
|
|
2142
|
+
sections.push(`
|
|
2143
|
+
## Selectors
|
|
2144
|
+
|
|
2145
|
+
| Pattern | Resolves To |
|
|
2146
|
+
|---------|-------------|
|
|
2147
|
+
| \`$.input.paramName\` | Input value |
|
|
2148
|
+
| \`$.steps.stepId.response\` | Full API response |
|
|
2149
|
+
| \`$.steps.stepId.response.data[0].email\` | Nested field |
|
|
2150
|
+
| \`$.steps.stepId.response.data[*].id\` | Wildcard array map |
|
|
2151
|
+
| \`$.env.MY_VAR\` | Environment variable |
|
|
2152
|
+
| \`$.loop.item\` / \`$.loop.i\` | Loop iteration |
|
|
2153
|
+
| \`"Hello {{$.steps.getUser.response.name}}"\` | String interpolation |
|
|
2154
|
+
|
|
2155
|
+
### When to use bare selectors vs \`{{...}}\` interpolation
|
|
2156
|
+
|
|
2157
|
+
- **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).
|
|
2158
|
+
- **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.
|
|
2159
|
+
- **Rule of thumb**: If the value is purely a selector, use bare. If it's a string containing a selector, use \`{{...}}\`.
|
|
2160
|
+
|
|
2161
|
+
### \`output\` vs \`response\` on step results
|
|
2162
|
+
|
|
2163
|
+
Every completed step produces both \`output\` and \`response\`:
|
|
2164
|
+
- **Action steps**: \`response\` is the raw API response. \`output\` is the same as \`response\`.
|
|
2165
|
+
- **Code/transform steps**: \`output\` is the return value. \`response\` is an alias for \`output\`.
|
|
2166
|
+
- **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.
|
|
2167
|
+
|
|
2168
|
+
## Error Handling
|
|
2169
|
+
|
|
2170
|
+
\`\`\`json
|
|
2171
|
+
{"onError": {"strategy": "retry", "retries": 3, "retryDelayMs": 1000}}
|
|
2172
|
+
\`\`\`
|
|
2173
|
+
|
|
2174
|
+
Strategies: \`${FLOW_SCHEMA.errorStrategies.join("`, `")}\`
|
|
2175
|
+
|
|
2176
|
+
Conditional execution: \`"if": "$.steps.prev.response.data.length > 0"\`
|
|
2177
|
+
|
|
2178
|
+
## Input Connection Auto-Resolution
|
|
2179
|
+
|
|
2180
|
+
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.
|
|
2181
|
+
|
|
2182
|
+
## Complete Example: Fetch Data, Transform, Notify
|
|
2183
|
+
|
|
2184
|
+
\`\`\`json
|
|
2185
|
+
{
|
|
2186
|
+
"key": "contacts-to-slack",
|
|
2187
|
+
"name": "CRM Contacts Summary to Slack",
|
|
2188
|
+
"description": "Fetch recent contacts from CRM, build a summary, post to Slack",
|
|
2189
|
+
"version": "1",
|
|
2190
|
+
"inputs": {
|
|
2191
|
+
"crmConnectionKey": {
|
|
2192
|
+
"type": "string",
|
|
2193
|
+
"required": true,
|
|
2194
|
+
"description": "CRM platform connection key",
|
|
2195
|
+
"connection": { "platform": "attio" }
|
|
2196
|
+
},
|
|
2197
|
+
"slackConnectionKey": {
|
|
2198
|
+
"type": "string",
|
|
2199
|
+
"required": true,
|
|
2200
|
+
"description": "Slack connection key",
|
|
2201
|
+
"connection": { "platform": "slack" }
|
|
2202
|
+
},
|
|
2203
|
+
"slackChannel": {
|
|
2204
|
+
"type": "string",
|
|
2205
|
+
"required": true,
|
|
2206
|
+
"description": "Slack channel name or ID"
|
|
2207
|
+
}
|
|
2208
|
+
},
|
|
2209
|
+
"steps": [
|
|
2210
|
+
{
|
|
2211
|
+
"id": "fetchContacts",
|
|
2212
|
+
"name": "Fetch recent contacts",
|
|
2213
|
+
"type": "action",
|
|
2214
|
+
"action": {
|
|
2215
|
+
"platform": "attio",
|
|
2216
|
+
"actionId": "ATTIO_LIST_PEOPLE_ACTION_ID",
|
|
2217
|
+
"connectionKey": "$.input.crmConnectionKey",
|
|
2218
|
+
"queryParams": { "limit": "10" }
|
|
2219
|
+
}
|
|
2220
|
+
},
|
|
2221
|
+
{
|
|
2222
|
+
"id": "buildSummary",
|
|
2223
|
+
"name": "Build formatted summary",
|
|
2224
|
+
"type": "code",
|
|
2225
|
+
"code": {
|
|
2226
|
+
"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')}\` };"
|
|
2227
|
+
}
|
|
2228
|
+
},
|
|
2229
|
+
{
|
|
2230
|
+
"id": "notifySlack",
|
|
2231
|
+
"name": "Post summary to Slack",
|
|
2232
|
+
"type": "action",
|
|
2233
|
+
"action": {
|
|
2234
|
+
"platform": "slack",
|
|
2235
|
+
"actionId": "SLACK_SEND_MESSAGE_ACTION_ID",
|
|
2236
|
+
"connectionKey": "$.input.slackConnectionKey",
|
|
2237
|
+
"data": {
|
|
2238
|
+
"channel": "$.input.slackChannel",
|
|
2239
|
+
"text": "{{$.steps.buildSummary.output.summary}}"
|
|
2240
|
+
}
|
|
2241
|
+
}
|
|
1689
2242
|
}
|
|
1690
|
-
|
|
1691
|
-
spinner5.stop("Execution failed");
|
|
1692
|
-
error(
|
|
1693
|
-
`Error: ${error2 instanceof Error ? error2.message : "Unknown error"}`
|
|
1694
|
-
);
|
|
1695
|
-
}
|
|
1696
|
-
}
|
|
1697
|
-
function colorMethod(method) {
|
|
1698
|
-
switch (method.toUpperCase()) {
|
|
1699
|
-
case "GET":
|
|
1700
|
-
return pc6.green(method);
|
|
1701
|
-
case "POST":
|
|
1702
|
-
return pc6.yellow(method);
|
|
1703
|
-
case "PUT":
|
|
1704
|
-
return pc6.blue(method);
|
|
1705
|
-
case "PATCH":
|
|
1706
|
-
return pc6.magenta(method);
|
|
1707
|
-
case "DELETE":
|
|
1708
|
-
return pc6.red(method);
|
|
1709
|
-
default:
|
|
1710
|
-
return method;
|
|
1711
|
-
}
|
|
2243
|
+
]
|
|
1712
2244
|
}
|
|
2245
|
+
\`\`\`
|
|
1713
2246
|
|
|
1714
|
-
|
|
1715
|
-
|
|
2247
|
+
Note: Action IDs above are placeholders. Always use \`one --agent actions search <platform> "<query>"\` to find real IDs.
|
|
2248
|
+
|
|
2249
|
+
## AI-Augmented Pattern
|
|
2250
|
+
|
|
2251
|
+
For workflows that need analysis/summarization, use the file-write \u2192 bash \u2192 code pattern:
|
|
2252
|
+
|
|
2253
|
+
1. \`file-write\` \u2014 save data to temp file
|
|
2254
|
+
2. \`bash\` \u2014 \`claude --print\` analyzes it (\`parseJson: true\`, \`timeout: 180000\`)
|
|
2255
|
+
3. \`code\` \u2014 parse and structure the output
|
|
2256
|
+
|
|
2257
|
+
Set timeout to at least 180000ms (3 min). Run Claude-heavy flows sequentially, not in parallel.
|
|
2258
|
+
|
|
2259
|
+
## Notes
|
|
2260
|
+
|
|
2261
|
+
- Connection keys are **inputs**, not hardcoded
|
|
2262
|
+
- Action IDs in examples are placeholders \u2014 always use \`actions search\`
|
|
2263
|
+
- Code steps allow \`crypto\`, \`buffer\`, \`url\`, \`path\` \u2014 \`fs\`, \`http\`, \`child_process\` are blocked
|
|
2264
|
+
- Bash steps require \`--allow-bash\` flag
|
|
2265
|
+
- State is persisted after every step \u2014 resume picks up where it left off`);
|
|
2266
|
+
return sections.join("\n");
|
|
2267
|
+
}
|
|
1716
2268
|
|
|
1717
2269
|
// src/lib/flow-validator.ts
|
|
1718
|
-
var VALID_STEP_TYPES = [
|
|
1719
|
-
"action",
|
|
1720
|
-
"transform",
|
|
1721
|
-
"code",
|
|
1722
|
-
"condition",
|
|
1723
|
-
"loop",
|
|
1724
|
-
"parallel",
|
|
1725
|
-
"file-read",
|
|
1726
|
-
"file-write",
|
|
1727
|
-
"while",
|
|
1728
|
-
"flow",
|
|
1729
|
-
"paginate",
|
|
1730
|
-
"bash"
|
|
1731
|
-
];
|
|
1732
|
-
var VALID_INPUT_TYPES = ["string", "number", "boolean", "object", "array"];
|
|
1733
|
-
var VALID_ERROR_STRATEGIES = ["fail", "continue", "retry", "fallback"];
|
|
1734
2270
|
function validateFlowSchema(flow2) {
|
|
1735
2271
|
const errors = [];
|
|
1736
2272
|
if (!flow2 || typeof flow2 !== "object") {
|
|
@@ -1740,7 +2276,7 @@ function validateFlowSchema(flow2) {
|
|
|
1740
2276
|
const f = flow2;
|
|
1741
2277
|
if (!f.key || typeof f.key !== "string") {
|
|
1742
2278
|
errors.push({ path: "key", message: 'Flow must have a string "key"' });
|
|
1743
|
-
} else if (
|
|
2279
|
+
} else if (FLOW_SCHEMA.flowFields.key.pattern && !FLOW_SCHEMA.flowFields.key.pattern.test(f.key) && f.key.length > 1) {
|
|
1744
2280
|
errors.push({ path: "key", message: "Flow key must be kebab-case (lowercase letters, numbers, hyphens)" });
|
|
1745
2281
|
}
|
|
1746
2282
|
if (!f.name || typeof f.name !== "string") {
|
|
@@ -1763,8 +2299,8 @@ function validateFlowSchema(flow2) {
|
|
|
1763
2299
|
continue;
|
|
1764
2300
|
}
|
|
1765
2301
|
const d = decl;
|
|
1766
|
-
if (!d.type || !
|
|
1767
|
-
errors.push({ path: `${prefix}.type`, message: `Input type must be one of: ${
|
|
2302
|
+
if (!d.type || !FLOW_SCHEMA.validInputTypes.includes(d.type)) {
|
|
2303
|
+
errors.push({ path: `${prefix}.type`, message: `Input type must be one of: ${FLOW_SCHEMA.validInputTypes.join(", ")}` });
|
|
1768
2304
|
}
|
|
1769
2305
|
if (d.connection !== void 0) {
|
|
1770
2306
|
if (!d.connection || typeof d.connection !== "object") {
|
|
@@ -1786,6 +2322,7 @@ function validateFlowSchema(flow2) {
|
|
|
1786
2322
|
return errors;
|
|
1787
2323
|
}
|
|
1788
2324
|
function validateStepsArray(steps, pathPrefix, errors) {
|
|
2325
|
+
const validTypes = FLOW_SCHEMA.stepTypes.map((st) => st.type);
|
|
1789
2326
|
for (let i = 0; i < steps.length; i++) {
|
|
1790
2327
|
const step = steps[i];
|
|
1791
2328
|
const path4 = `${pathPrefix}[${i}]`;
|
|
@@ -1800,189 +2337,79 @@ function validateStepsArray(steps, pathPrefix, errors) {
|
|
|
1800
2337
|
if (!s.name || typeof s.name !== "string") {
|
|
1801
2338
|
errors.push({ path: `${path4}.name`, message: 'Step must have a string "name"' });
|
|
1802
2339
|
}
|
|
1803
|
-
if (!s.type || !
|
|
1804
|
-
errors.push({ path: `${path4}.type`, message: `Step type must be one of: ${
|
|
2340
|
+
if (!s.type || !validTypes.includes(s.type)) {
|
|
2341
|
+
errors.push({ path: `${path4}.type`, message: `Step type must be one of: ${validTypes.join(", ")}` });
|
|
2342
|
+
continue;
|
|
1805
2343
|
}
|
|
1806
2344
|
if (s.onError && typeof s.onError === "object") {
|
|
1807
2345
|
const oe = s.onError;
|
|
1808
|
-
if (!
|
|
1809
|
-
errors.push({ path: `${path4}.onError.strategy`, message: `Error strategy must be one of: ${
|
|
2346
|
+
if (!FLOW_SCHEMA.errorStrategies.includes(oe.strategy)) {
|
|
2347
|
+
errors.push({ path: `${path4}.onError.strategy`, message: `Error strategy must be one of: ${FLOW_SCHEMA.errorStrategies.join(", ")}` });
|
|
1810
2348
|
}
|
|
1811
2349
|
}
|
|
1812
|
-
const
|
|
1813
|
-
if (
|
|
1814
|
-
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
}
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
|
|
1827
|
-
|
|
1828
|
-
|
|
1829
|
-
}
|
|
1830
|
-
|
|
1831
|
-
} else if (type === "code") {
|
|
1832
|
-
if (!s.code || typeof s.code !== "object") {
|
|
1833
|
-
errors.push({ path: `${path4}.code`, message: 'Code step must have a "code" config object' });
|
|
1834
|
-
} else {
|
|
1835
|
-
const c = s.code;
|
|
1836
|
-
if (!c.source || typeof c.source !== "string") {
|
|
1837
|
-
errors.push({ path: `${path4}.code.source`, message: 'Code must have a string "source"' });
|
|
1838
|
-
}
|
|
1839
|
-
}
|
|
1840
|
-
} else if (type === "condition") {
|
|
1841
|
-
if (!s.condition || typeof s.condition !== "object") {
|
|
1842
|
-
errors.push({ path: `${path4}.condition`, message: 'Condition step must have a "condition" config object' });
|
|
1843
|
-
} else {
|
|
1844
|
-
const c = s.condition;
|
|
1845
|
-
if (!c.expression || typeof c.expression !== "string") {
|
|
1846
|
-
errors.push({ path: `${path4}.condition.expression`, message: 'Condition must have a string "expression"' });
|
|
1847
|
-
}
|
|
1848
|
-
if (!Array.isArray(c.then)) {
|
|
1849
|
-
errors.push({ path: `${path4}.condition.then`, message: 'Condition must have a "then" steps array' });
|
|
1850
|
-
} else {
|
|
1851
|
-
validateStepsArray(c.then, `${path4}.condition.then`, errors);
|
|
1852
|
-
}
|
|
1853
|
-
if (c.else !== void 0) {
|
|
1854
|
-
if (!Array.isArray(c.else)) {
|
|
1855
|
-
errors.push({ path: `${path4}.condition.else`, message: 'Condition "else" must be a steps array' });
|
|
1856
|
-
} else {
|
|
1857
|
-
validateStepsArray(c.else, `${path4}.condition.else`, errors);
|
|
1858
|
-
}
|
|
1859
|
-
}
|
|
1860
|
-
}
|
|
1861
|
-
} else if (type === "loop") {
|
|
1862
|
-
if (!s.loop || typeof s.loop !== "object") {
|
|
1863
|
-
errors.push({ path: `${path4}.loop`, message: 'Loop step must have a "loop" config object' });
|
|
1864
|
-
} else {
|
|
1865
|
-
const l = s.loop;
|
|
1866
|
-
if (!l.over || typeof l.over !== "string") {
|
|
1867
|
-
errors.push({ path: `${path4}.loop.over`, message: 'Loop must have a string "over" selector' });
|
|
1868
|
-
}
|
|
1869
|
-
if (!l.as || typeof l.as !== "string") {
|
|
1870
|
-
errors.push({ path: `${path4}.loop.as`, message: 'Loop must have a string "as" variable name' });
|
|
1871
|
-
}
|
|
1872
|
-
if (!Array.isArray(l.steps)) {
|
|
1873
|
-
errors.push({ path: `${path4}.loop.steps`, message: 'Loop must have a "steps" array' });
|
|
1874
|
-
} else {
|
|
1875
|
-
validateStepsArray(l.steps, `${path4}.loop.steps`, errors);
|
|
1876
|
-
}
|
|
1877
|
-
}
|
|
1878
|
-
} else if (type === "parallel") {
|
|
1879
|
-
if (!s.parallel || typeof s.parallel !== "object") {
|
|
1880
|
-
errors.push({ path: `${path4}.parallel`, message: 'Parallel step must have a "parallel" config object' });
|
|
1881
|
-
} else {
|
|
1882
|
-
const par = s.parallel;
|
|
1883
|
-
if (!Array.isArray(par.steps)) {
|
|
1884
|
-
errors.push({ path: `${path4}.parallel.steps`, message: 'Parallel must have a "steps" array' });
|
|
1885
|
-
} else {
|
|
1886
|
-
validateStepsArray(par.steps, `${path4}.parallel.steps`, errors);
|
|
1887
|
-
}
|
|
1888
|
-
}
|
|
1889
|
-
} else if (type === "file-read") {
|
|
1890
|
-
if (!s.fileRead || typeof s.fileRead !== "object") {
|
|
1891
|
-
errors.push({ path: `${path4}.fileRead`, message: 'File-read step must have a "fileRead" config object' });
|
|
1892
|
-
} else {
|
|
1893
|
-
const fr = s.fileRead;
|
|
1894
|
-
if (!fr.path || typeof fr.path !== "string") {
|
|
1895
|
-
errors.push({ path: `${path4}.fileRead.path`, message: 'File-read must have a string "path"' });
|
|
1896
|
-
}
|
|
2350
|
+
const descriptor = getStepTypeDescriptor(s.type);
|
|
2351
|
+
if (!descriptor) continue;
|
|
2352
|
+
const configKey = descriptor.configKey;
|
|
2353
|
+
const configObj = s[configKey];
|
|
2354
|
+
if (!configObj || typeof configObj !== "object") {
|
|
2355
|
+
const hint = detectFlatConfigHint(s, descriptor);
|
|
2356
|
+
errors.push({
|
|
2357
|
+
path: `${path4}.${configKey}`,
|
|
2358
|
+
message: `${capitalize(descriptor.type)} step must have a "${configKey}" config object${hint}`
|
|
2359
|
+
});
|
|
2360
|
+
continue;
|
|
2361
|
+
}
|
|
2362
|
+
const config = configObj;
|
|
2363
|
+
for (const [fieldName, fd] of Object.entries(descriptor.fields)) {
|
|
2364
|
+
const fieldPath = `${path4}.${configKey}.${fieldName}`;
|
|
2365
|
+
const value = config[fieldName];
|
|
2366
|
+
if (fd.required && (value === void 0 || value === null || value === "")) {
|
|
2367
|
+
errors.push({ path: fieldPath, message: `${capitalize(descriptor.type)} must have ${fd.type === "string" ? "a string" : fd.type === "array" ? "a" : "a"} "${fieldName}"` });
|
|
2368
|
+
continue;
|
|
1897
2369
|
}
|
|
1898
|
-
|
|
1899
|
-
if (
|
|
1900
|
-
errors.push({ path:
|
|
1901
|
-
} else {
|
|
1902
|
-
const fw = s.fileWrite;
|
|
1903
|
-
if (!fw.path || typeof fw.path !== "string") {
|
|
1904
|
-
errors.push({ path: `${path4}.fileWrite.path`, message: 'File-write must have a string "path"' });
|
|
1905
|
-
}
|
|
1906
|
-
if (fw.content === void 0) {
|
|
1907
|
-
errors.push({ path: `${path4}.fileWrite.content`, message: 'File-write must have "content"' });
|
|
1908
|
-
}
|
|
2370
|
+
if (value === void 0) continue;
|
|
2371
|
+
if (fd.type === "string" && fd.required && typeof value !== "string") {
|
|
2372
|
+
errors.push({ path: fieldPath, message: `"${fieldName}" must be a string` });
|
|
1909
2373
|
}
|
|
1910
|
-
|
|
1911
|
-
|
|
1912
|
-
errors.push({ path: `${path4}.while`, message: 'While step must have a "while" config object' });
|
|
1913
|
-
} else {
|
|
1914
|
-
const w = s.while;
|
|
1915
|
-
if (!w.condition || typeof w.condition !== "string") {
|
|
1916
|
-
errors.push({ path: `${path4}.while.condition`, message: 'While must have a string "condition"' });
|
|
1917
|
-
}
|
|
1918
|
-
if (!Array.isArray(w.steps)) {
|
|
1919
|
-
errors.push({ path: `${path4}.while.steps`, message: 'While must have a "steps" array' });
|
|
1920
|
-
} else {
|
|
1921
|
-
validateStepsArray(w.steps, `${path4}.while.steps`, errors);
|
|
1922
|
-
}
|
|
1923
|
-
if (w.maxIterations !== void 0 && (typeof w.maxIterations !== "number" || w.maxIterations <= 0)) {
|
|
1924
|
-
errors.push({ path: `${path4}.while.maxIterations`, message: "maxIterations must be a positive number" });
|
|
1925
|
-
}
|
|
2374
|
+
if (fd.type === "number" && value !== void 0 && (typeof value !== "number" || value <= 0)) {
|
|
2375
|
+
errors.push({ path: fieldPath, message: `${fieldName} must be a positive number` });
|
|
1926
2376
|
}
|
|
1927
|
-
|
|
1928
|
-
|
|
1929
|
-
errors.push({ path: `${path4}.flow`, message: 'Flow step must have a "flow" config object' });
|
|
1930
|
-
} else {
|
|
1931
|
-
const f = s.flow;
|
|
1932
|
-
if (!f.key || typeof f.key !== "string") {
|
|
1933
|
-
errors.push({ path: `${path4}.flow.key`, message: 'Flow must have a string "key"' });
|
|
1934
|
-
}
|
|
1935
|
-
if (f.inputs !== void 0 && (typeof f.inputs !== "object" || Array.isArray(f.inputs))) {
|
|
1936
|
-
errors.push({ path: `${path4}.flow.inputs`, message: "Flow inputs must be an object" });
|
|
1937
|
-
}
|
|
2377
|
+
if (fd.type === "boolean" && value !== void 0 && typeof value !== "boolean") {
|
|
2378
|
+
errors.push({ path: fieldPath, message: `${fieldName} must be a boolean` });
|
|
1938
2379
|
}
|
|
1939
|
-
|
|
1940
|
-
|
|
1941
|
-
|
|
1942
|
-
} else {
|
|
1943
|
-
const p7 = s.paginate;
|
|
1944
|
-
if (!p7.action || typeof p7.action !== "object") {
|
|
1945
|
-
errors.push({ path: `${path4}.paginate.action`, message: 'Paginate must have an "action" config object' });
|
|
2380
|
+
if (fd.stepsArray) {
|
|
2381
|
+
if (!Array.isArray(value)) {
|
|
2382
|
+
errors.push({ path: fieldPath, message: `"${fieldName}" must be a steps array` });
|
|
1946
2383
|
} else {
|
|
1947
|
-
|
|
1948
|
-
if (!a.platform) errors.push({ path: `${path4}.paginate.action.platform`, message: 'Action must have "platform"' });
|
|
1949
|
-
if (!a.actionId) errors.push({ path: `${path4}.paginate.action.actionId`, message: 'Action must have "actionId"' });
|
|
1950
|
-
if (!a.connectionKey) errors.push({ path: `${path4}.paginate.action.connectionKey`, message: 'Action must have "connectionKey"' });
|
|
1951
|
-
}
|
|
1952
|
-
if (!p7.pageTokenField || typeof p7.pageTokenField !== "string") {
|
|
1953
|
-
errors.push({ path: `${path4}.paginate.pageTokenField`, message: 'Paginate must have a string "pageTokenField"' });
|
|
1954
|
-
}
|
|
1955
|
-
if (!p7.resultsField || typeof p7.resultsField !== "string") {
|
|
1956
|
-
errors.push({ path: `${path4}.paginate.resultsField`, message: 'Paginate must have a string "resultsField"' });
|
|
1957
|
-
}
|
|
1958
|
-
if (!p7.inputTokenParam || typeof p7.inputTokenParam !== "string") {
|
|
1959
|
-
errors.push({ path: `${path4}.paginate.inputTokenParam`, message: 'Paginate must have a string "inputTokenParam"' });
|
|
1960
|
-
}
|
|
1961
|
-
if (p7.maxPages !== void 0 && (typeof p7.maxPages !== "number" || p7.maxPages <= 0)) {
|
|
1962
|
-
errors.push({ path: `${path4}.paginate.maxPages`, message: "maxPages must be a positive number" });
|
|
2384
|
+
validateStepsArray(value, fieldPath, errors);
|
|
1963
2385
|
}
|
|
1964
2386
|
}
|
|
1965
|
-
|
|
1966
|
-
|
|
1967
|
-
|
|
1968
|
-
|
|
1969
|
-
|
|
1970
|
-
|
|
1971
|
-
errors.push({ path: `${path4}.bash.command`, message: 'Bash must have a string "command"' });
|
|
1972
|
-
}
|
|
1973
|
-
if (b.timeout !== void 0 && (typeof b.timeout !== "number" || b.timeout <= 0)) {
|
|
1974
|
-
errors.push({ path: `${path4}.bash.timeout`, message: "timeout must be a positive number" });
|
|
1975
|
-
}
|
|
1976
|
-
if (b.parseJson !== void 0 && typeof b.parseJson !== "boolean") {
|
|
1977
|
-
errors.push({ path: `${path4}.bash.parseJson`, message: "parseJson must be a boolean" });
|
|
2387
|
+
if (descriptor.type === "paginate" && fieldName === "action") {
|
|
2388
|
+
if (typeof value === "object" && value !== null) {
|
|
2389
|
+
const a = value;
|
|
2390
|
+
if (!a.platform) errors.push({ path: `${fieldPath}.platform`, message: 'Action must have "platform"' });
|
|
2391
|
+
if (!a.actionId) errors.push({ path: `${fieldPath}.actionId`, message: 'Action must have "actionId"' });
|
|
2392
|
+
if (!a.connectionKey) errors.push({ path: `${fieldPath}.connectionKey`, message: 'Action must have "connectionKey"' });
|
|
1978
2393
|
}
|
|
1979
2394
|
}
|
|
1980
2395
|
}
|
|
1981
2396
|
}
|
|
1982
2397
|
}
|
|
2398
|
+
function detectFlatConfigHint(step, descriptor) {
|
|
2399
|
+
const requiredFields = Object.entries(descriptor.fields).filter(([, fd]) => fd.required).map(([name]) => name);
|
|
2400
|
+
const flatFields = requiredFields.filter((f) => f in step);
|
|
2401
|
+
if (flatFields.length > 0) {
|
|
2402
|
+
return `. Hint: "${flatFields.join('", "')}" must be nested inside "${descriptor.configKey}": { ... }, not placed directly on the step`;
|
|
2403
|
+
}
|
|
2404
|
+
return "";
|
|
2405
|
+
}
|
|
2406
|
+
function capitalize(s) {
|
|
2407
|
+
return s.charAt(0).toUpperCase() + s.slice(1);
|
|
2408
|
+
}
|
|
1983
2409
|
function validateStepIds(flow2) {
|
|
1984
2410
|
const errors = [];
|
|
1985
2411
|
const seen = /* @__PURE__ */ new Set();
|
|
2412
|
+
const nestedKeys = getNestedStepsKeys();
|
|
1986
2413
|
function collectIds(steps, pathPrefix) {
|
|
1987
2414
|
for (let i = 0; i < steps.length; i++) {
|
|
1988
2415
|
const step = steps[i];
|
|
@@ -1992,13 +2419,12 @@ function validateStepIds(flow2) {
|
|
|
1992
2419
|
} else {
|
|
1993
2420
|
seen.add(step.id);
|
|
1994
2421
|
}
|
|
1995
|
-
|
|
1996
|
-
|
|
1997
|
-
if (
|
|
2422
|
+
for (const { configKey, fieldName } of nestedKeys) {
|
|
2423
|
+
const config = step[configKey];
|
|
2424
|
+
if (config && Array.isArray(config[fieldName])) {
|
|
2425
|
+
collectIds(config[fieldName], `${path4}.${configKey}.${fieldName}`);
|
|
2426
|
+
}
|
|
1998
2427
|
}
|
|
1999
|
-
if (step.loop?.steps) collectIds(step.loop.steps, `${path4}.loop.steps`);
|
|
2000
|
-
if (step.parallel?.steps) collectIds(step.parallel.steps, `${path4}.parallel.steps`);
|
|
2001
|
-
if (step.while?.steps) collectIds(step.while.steps, `${path4}.while.steps`);
|
|
2002
2428
|
}
|
|
2003
2429
|
}
|
|
2004
2430
|
collectIds(flow2.steps, "steps");
|
|
@@ -2007,25 +2433,17 @@ function validateStepIds(flow2) {
|
|
|
2007
2433
|
function validateSelectorReferences(flow2) {
|
|
2008
2434
|
const errors = [];
|
|
2009
2435
|
const inputNames = new Set(Object.keys(flow2.inputs));
|
|
2436
|
+
const nestedKeys = getNestedStepsKeys();
|
|
2010
2437
|
function getAllStepIds(steps) {
|
|
2011
2438
|
const ids = /* @__PURE__ */ new Set();
|
|
2012
2439
|
for (const step of steps) {
|
|
2013
2440
|
ids.add(step.id);
|
|
2014
|
-
|
|
2015
|
-
|
|
2016
|
-
if (
|
|
2017
|
-
for (const id of getAllStepIds(
|
|
2441
|
+
for (const { configKey, fieldName } of nestedKeys) {
|
|
2442
|
+
const config = step[configKey];
|
|
2443
|
+
if (config && Array.isArray(config[fieldName])) {
|
|
2444
|
+
for (const id of getAllStepIds(config[fieldName])) ids.add(id);
|
|
2018
2445
|
}
|
|
2019
2446
|
}
|
|
2020
|
-
if (step.loop?.steps) {
|
|
2021
|
-
for (const id of getAllStepIds(step.loop.steps)) ids.add(id);
|
|
2022
|
-
}
|
|
2023
|
-
if (step.parallel?.steps) {
|
|
2024
|
-
for (const id of getAllStepIds(step.parallel.steps)) ids.add(id);
|
|
2025
|
-
}
|
|
2026
|
-
if (step.while?.steps) {
|
|
2027
|
-
for (const id of getAllStepIds(step.while.steps)) ids.add(id);
|
|
2028
|
-
}
|
|
2029
2447
|
}
|
|
2030
2448
|
return ids;
|
|
2031
2449
|
}
|
|
@@ -2072,49 +2490,29 @@ function validateSelectorReferences(flow2) {
|
|
|
2072
2490
|
function checkStep(step, pathPrefix) {
|
|
2073
2491
|
if (step.if) checkSelectors(extractSelectors(step.if), `${pathPrefix}.if`);
|
|
2074
2492
|
if (step.unless) checkSelectors(extractSelectors(step.unless), `${pathPrefix}.unless`);
|
|
2075
|
-
|
|
2076
|
-
|
|
2077
|
-
|
|
2078
|
-
|
|
2079
|
-
|
|
2080
|
-
|
|
2081
|
-
|
|
2082
|
-
|
|
2083
|
-
|
|
2084
|
-
|
|
2085
|
-
|
|
2086
|
-
|
|
2087
|
-
|
|
2088
|
-
}
|
|
2089
|
-
if (step.parallel) {
|
|
2090
|
-
step.parallel.steps.forEach((s, i) => checkStep(s, `${pathPrefix}.parallel.steps[${i}]`));
|
|
2091
|
-
}
|
|
2092
|
-
if (step.fileRead) {
|
|
2093
|
-
checkSelectors(extractSelectors(step.fileRead.path), `${pathPrefix}.fileRead.path`);
|
|
2094
|
-
}
|
|
2095
|
-
if (step.fileWrite) {
|
|
2096
|
-
checkSelectors(extractSelectors(step.fileWrite.path), `${pathPrefix}.fileWrite.path`);
|
|
2097
|
-
checkSelectors(extractSelectors(step.fileWrite.content), `${pathPrefix}.fileWrite.content`);
|
|
2098
|
-
}
|
|
2099
|
-
if (step.while) {
|
|
2100
|
-
step.while.steps.forEach((s, i) => checkStep(s, `${pathPrefix}.while.steps[${i}]`));
|
|
2101
|
-
}
|
|
2102
|
-
if (step.flow) {
|
|
2103
|
-
checkSelectors(extractSelectors(step.flow.key), `${pathPrefix}.flow.key`);
|
|
2104
|
-
if (step.flow.inputs) {
|
|
2105
|
-
checkSelectors(extractSelectors(step.flow.inputs), `${pathPrefix}.flow.inputs`);
|
|
2106
|
-
}
|
|
2107
|
-
}
|
|
2108
|
-
if (step.paginate) {
|
|
2109
|
-
checkSelectors(extractSelectors(step.paginate.action), `${pathPrefix}.paginate.action`);
|
|
2110
|
-
}
|
|
2111
|
-
if (step.bash) {
|
|
2112
|
-
checkSelectors(extractSelectors(step.bash.command), `${pathPrefix}.bash.command`);
|
|
2113
|
-
if (step.bash.cwd) {
|
|
2114
|
-
checkSelectors(extractSelectors(step.bash.cwd), `${pathPrefix}.bash.cwd`);
|
|
2493
|
+
const descriptor = getStepTypeDescriptor(step.type);
|
|
2494
|
+
if (descriptor) {
|
|
2495
|
+
const config = step[descriptor.configKey];
|
|
2496
|
+
if (config && typeof config === "object") {
|
|
2497
|
+
if (step.type !== "transform" && step.type !== "code") {
|
|
2498
|
+
for (const [fieldName, fd] of Object.entries(descriptor.fields)) {
|
|
2499
|
+
if (fd.stepsArray) continue;
|
|
2500
|
+
const value = config[fieldName];
|
|
2501
|
+
if (value !== void 0) {
|
|
2502
|
+
checkSelectors(extractSelectors(value), `${pathPrefix}.${descriptor.configKey}.${fieldName}`);
|
|
2503
|
+
}
|
|
2504
|
+
}
|
|
2505
|
+
}
|
|
2115
2506
|
}
|
|
2116
|
-
|
|
2117
|
-
|
|
2507
|
+
for (const { configKey, fieldName } of nestedKeys) {
|
|
2508
|
+
if (configKey === descriptor.configKey) {
|
|
2509
|
+
const c = step[configKey];
|
|
2510
|
+
if (c && Array.isArray(c[fieldName])) {
|
|
2511
|
+
c[fieldName].forEach(
|
|
2512
|
+
(s, i) => checkStep(s, `${pathPrefix}.${configKey}.${fieldName}[${i}]`)
|
|
2513
|
+
);
|
|
2514
|
+
}
|
|
2515
|
+
}
|
|
2118
2516
|
}
|
|
2119
2517
|
}
|
|
2120
2518
|
}
|
|
@@ -2132,7 +2530,7 @@ function validateFlow(flow2) {
|
|
|
2132
2530
|
}
|
|
2133
2531
|
|
|
2134
2532
|
// src/commands/flow.ts
|
|
2135
|
-
import
|
|
2533
|
+
import fs4 from "fs";
|
|
2136
2534
|
function getConfig2() {
|
|
2137
2535
|
const apiKey = getApiKey();
|
|
2138
2536
|
if (!apiKey) {
|
|
@@ -2186,10 +2584,19 @@ async function flowCreateCommand(key, options) {
|
|
|
2186
2584
|
intro2(pc7.bgCyan(pc7.black(" One Workflow ")));
|
|
2187
2585
|
let flow2;
|
|
2188
2586
|
if (options.definition) {
|
|
2587
|
+
let raw = options.definition;
|
|
2588
|
+
if (raw.startsWith("@")) {
|
|
2589
|
+
const filePath = raw.slice(1);
|
|
2590
|
+
try {
|
|
2591
|
+
raw = fs4.readFileSync(filePath, "utf-8");
|
|
2592
|
+
} catch (err) {
|
|
2593
|
+
error(`Cannot read file "${filePath}": ${err.message}`);
|
|
2594
|
+
}
|
|
2595
|
+
}
|
|
2189
2596
|
try {
|
|
2190
|
-
flow2 = JSON.parse(
|
|
2597
|
+
flow2 = JSON.parse(raw);
|
|
2191
2598
|
} catch {
|
|
2192
|
-
error("Invalid JSON in --definition");
|
|
2599
|
+
error("Invalid JSON in --definition. If your JSON contains special characters (like :: in action IDs), try --definition @file.json instead.");
|
|
2193
2600
|
}
|
|
2194
2601
|
} else if (!process.stdin.isTTY) {
|
|
2195
2602
|
const chunks = [];
|
|
@@ -2364,7 +2771,7 @@ async function flowValidateCommand(keyOrPath) {
|
|
|
2364
2771
|
let flowData;
|
|
2365
2772
|
try {
|
|
2366
2773
|
const flowPath = resolveFlowPath(keyOrPath);
|
|
2367
|
-
const content =
|
|
2774
|
+
const content = fs4.readFileSync(flowPath, "utf-8");
|
|
2368
2775
|
flowData = JSON.parse(content);
|
|
2369
2776
|
} catch (err) {
|
|
2370
2777
|
spinner5.stop("Validation failed");
|
|
@@ -2496,6 +2903,205 @@ function colorStatus(status) {
|
|
|
2496
2903
|
return status;
|
|
2497
2904
|
}
|
|
2498
2905
|
}
|
|
2906
|
+
var SCAFFOLD_TEMPLATES = {
|
|
2907
|
+
basic: () => ({
|
|
2908
|
+
key: "my-workflow",
|
|
2909
|
+
name: "My Workflow",
|
|
2910
|
+
description: "A basic workflow with a single action step",
|
|
2911
|
+
version: "1",
|
|
2912
|
+
inputs: {
|
|
2913
|
+
connectionKey: {
|
|
2914
|
+
type: "string",
|
|
2915
|
+
required: true,
|
|
2916
|
+
description: "Connection key for the platform",
|
|
2917
|
+
connection: { platform: "PLATFORM_NAME" }
|
|
2918
|
+
}
|
|
2919
|
+
},
|
|
2920
|
+
steps: [
|
|
2921
|
+
{
|
|
2922
|
+
id: "step1",
|
|
2923
|
+
name: "Execute action",
|
|
2924
|
+
type: "action",
|
|
2925
|
+
action: {
|
|
2926
|
+
platform: "PLATFORM_NAME",
|
|
2927
|
+
actionId: "ACTION_ID_FROM_SEARCH",
|
|
2928
|
+
connectionKey: "$.input.connectionKey",
|
|
2929
|
+
data: {}
|
|
2930
|
+
}
|
|
2931
|
+
}
|
|
2932
|
+
]
|
|
2933
|
+
}),
|
|
2934
|
+
conditional: () => ({
|
|
2935
|
+
key: "my-conditional-workflow",
|
|
2936
|
+
name: "Conditional Workflow",
|
|
2937
|
+
description: "Fetch data, then branch based on results",
|
|
2938
|
+
version: "1",
|
|
2939
|
+
inputs: {
|
|
2940
|
+
connectionKey: {
|
|
2941
|
+
type: "string",
|
|
2942
|
+
required: true,
|
|
2943
|
+
description: "Connection key",
|
|
2944
|
+
connection: { platform: "PLATFORM_NAME" }
|
|
2945
|
+
}
|
|
2946
|
+
},
|
|
2947
|
+
steps: [
|
|
2948
|
+
{
|
|
2949
|
+
id: "fetch",
|
|
2950
|
+
name: "Fetch data",
|
|
2951
|
+
type: "action",
|
|
2952
|
+
action: {
|
|
2953
|
+
platform: "PLATFORM_NAME",
|
|
2954
|
+
actionId: "ACTION_ID_FROM_SEARCH",
|
|
2955
|
+
connectionKey: "$.input.connectionKey"
|
|
2956
|
+
}
|
|
2957
|
+
},
|
|
2958
|
+
{
|
|
2959
|
+
id: "decide",
|
|
2960
|
+
name: "Check results",
|
|
2961
|
+
type: "condition",
|
|
2962
|
+
condition: {
|
|
2963
|
+
expression: "$.steps.fetch.response.data && $.steps.fetch.response.data.length > 0",
|
|
2964
|
+
then: [
|
|
2965
|
+
{
|
|
2966
|
+
id: "handleFound",
|
|
2967
|
+
name: "Handle found",
|
|
2968
|
+
type: "transform",
|
|
2969
|
+
transform: { expression: "$.steps.fetch.response.data[0]" }
|
|
2970
|
+
}
|
|
2971
|
+
],
|
|
2972
|
+
else: [
|
|
2973
|
+
{
|
|
2974
|
+
id: "handleNotFound",
|
|
2975
|
+
name: "Handle not found",
|
|
2976
|
+
type: "transform",
|
|
2977
|
+
transform: { expression: "({ error: 'No results found' })" }
|
|
2978
|
+
}
|
|
2979
|
+
]
|
|
2980
|
+
}
|
|
2981
|
+
}
|
|
2982
|
+
]
|
|
2983
|
+
}),
|
|
2984
|
+
loop: () => ({
|
|
2985
|
+
key: "my-loop-workflow",
|
|
2986
|
+
name: "Loop Workflow",
|
|
2987
|
+
description: "Fetch a list, then process each item",
|
|
2988
|
+
version: "1",
|
|
2989
|
+
inputs: {
|
|
2990
|
+
connectionKey: {
|
|
2991
|
+
type: "string",
|
|
2992
|
+
required: true,
|
|
2993
|
+
description: "Connection key",
|
|
2994
|
+
connection: { platform: "PLATFORM_NAME" }
|
|
2995
|
+
}
|
|
2996
|
+
},
|
|
2997
|
+
steps: [
|
|
2998
|
+
{
|
|
2999
|
+
id: "fetchList",
|
|
3000
|
+
name: "Fetch items",
|
|
3001
|
+
type: "action",
|
|
3002
|
+
action: {
|
|
3003
|
+
platform: "PLATFORM_NAME",
|
|
3004
|
+
actionId: "ACTION_ID_FROM_SEARCH",
|
|
3005
|
+
connectionKey: "$.input.connectionKey"
|
|
3006
|
+
}
|
|
3007
|
+
},
|
|
3008
|
+
{
|
|
3009
|
+
id: "processItems",
|
|
3010
|
+
name: "Process each item",
|
|
3011
|
+
type: "loop",
|
|
3012
|
+
loop: {
|
|
3013
|
+
over: "$.steps.fetchList.response.data",
|
|
3014
|
+
as: "item",
|
|
3015
|
+
steps: [
|
|
3016
|
+
{
|
|
3017
|
+
id: "processItem",
|
|
3018
|
+
name: "Process single item",
|
|
3019
|
+
type: "transform",
|
|
3020
|
+
transform: { expression: "({ id: $.loop.item.id, processed: true })" }
|
|
3021
|
+
}
|
|
3022
|
+
]
|
|
3023
|
+
}
|
|
3024
|
+
},
|
|
3025
|
+
{
|
|
3026
|
+
id: "summary",
|
|
3027
|
+
name: "Generate summary",
|
|
3028
|
+
type: "transform",
|
|
3029
|
+
transform: { expression: "({ total: $.steps.fetchList.response.data.length })" }
|
|
3030
|
+
}
|
|
3031
|
+
]
|
|
3032
|
+
}),
|
|
3033
|
+
ai: () => ({
|
|
3034
|
+
key: "my-ai-workflow",
|
|
3035
|
+
name: "AI Analysis Workflow",
|
|
3036
|
+
description: "Fetch data, analyze with Claude, and send results",
|
|
3037
|
+
version: "1",
|
|
3038
|
+
inputs: {
|
|
3039
|
+
connectionKey: {
|
|
3040
|
+
type: "string",
|
|
3041
|
+
required: true,
|
|
3042
|
+
description: "Connection key for data source",
|
|
3043
|
+
connection: { platform: "PLATFORM_NAME" }
|
|
3044
|
+
}
|
|
3045
|
+
},
|
|
3046
|
+
steps: [
|
|
3047
|
+
{
|
|
3048
|
+
id: "fetchData",
|
|
3049
|
+
name: "Fetch raw data",
|
|
3050
|
+
type: "action",
|
|
3051
|
+
action: {
|
|
3052
|
+
platform: "PLATFORM_NAME",
|
|
3053
|
+
actionId: "ACTION_ID_FROM_SEARCH",
|
|
3054
|
+
connectionKey: "$.input.connectionKey"
|
|
3055
|
+
}
|
|
3056
|
+
},
|
|
3057
|
+
{
|
|
3058
|
+
id: "writeData",
|
|
3059
|
+
name: "Write data for analysis",
|
|
3060
|
+
type: "file-write",
|
|
3061
|
+
fileWrite: {
|
|
3062
|
+
path: "/tmp/workflow-data.json",
|
|
3063
|
+
content: "$.steps.fetchData.response"
|
|
3064
|
+
}
|
|
3065
|
+
},
|
|
3066
|
+
{
|
|
3067
|
+
id: "analyze",
|
|
3068
|
+
name: "Analyze with Claude",
|
|
3069
|
+
type: "bash",
|
|
3070
|
+
bash: {
|
|
3071
|
+
command: `cat /tmp/workflow-data.json | claude --print 'Analyze this data and return JSON with: {"summary": "...", "insights": [...], "recommendations": [...]}. Return ONLY valid JSON.' --output-format json`,
|
|
3072
|
+
timeout: 18e4,
|
|
3073
|
+
parseJson: true
|
|
3074
|
+
}
|
|
3075
|
+
},
|
|
3076
|
+
{
|
|
3077
|
+
id: "formatResult",
|
|
3078
|
+
name: "Format analysis output",
|
|
3079
|
+
type: "code",
|
|
3080
|
+
code: {
|
|
3081
|
+
source: "const a = $.steps.analyze.output;\nreturn {\n summary: a.summary,\n insights: a.insights,\n recommendations: a.recommendations\n};"
|
|
3082
|
+
}
|
|
3083
|
+
}
|
|
3084
|
+
]
|
|
3085
|
+
})
|
|
3086
|
+
};
|
|
3087
|
+
async function flowScaffoldCommand(template) {
|
|
3088
|
+
const templateName = template || "basic";
|
|
3089
|
+
const templateFn = SCAFFOLD_TEMPLATES[templateName];
|
|
3090
|
+
if (!templateFn) {
|
|
3091
|
+
const available = Object.keys(SCAFFOLD_TEMPLATES).join(", ");
|
|
3092
|
+
if (isAgentMode()) {
|
|
3093
|
+
json({ error: `Unknown template "${templateName}". Available: ${available}` });
|
|
3094
|
+
process.exit(1);
|
|
3095
|
+
}
|
|
3096
|
+
error(`Unknown template "${templateName}". Available: ${available}`);
|
|
3097
|
+
}
|
|
3098
|
+
const scaffold = templateFn();
|
|
3099
|
+
if (isAgentMode()) {
|
|
3100
|
+
json(scaffold);
|
|
3101
|
+
return;
|
|
3102
|
+
}
|
|
3103
|
+
console.log(JSON.stringify(scaffold, null, 2));
|
|
3104
|
+
}
|
|
2499
3105
|
|
|
2500
3106
|
// src/commands/relay.ts
|
|
2501
3107
|
import pc8 from "picocolors";
|
|
@@ -2856,13 +3462,13 @@ one --agent <command>
|
|
|
2856
3462
|
|
|
2857
3463
|
All commands return JSON. If an \`error\` key is present, the command failed.
|
|
2858
3464
|
|
|
2859
|
-
## IMPORTANT:
|
|
3465
|
+
## IMPORTANT: Read the guide before you act
|
|
2860
3466
|
|
|
2861
|
-
Before using any feature,
|
|
3467
|
+
Before using any feature, read its guide section first: \`one guide actions\`, \`one guide flows\`, or \`one guide relay\`. The guide teaches you the correct workflow, required fields, and common mistakes. Never guess \u2014 read the guide, then act.
|
|
2862
3468
|
|
|
2863
3469
|
## Features
|
|
2864
3470
|
|
|
2865
|
-
### 1. Actions \u2014 Execute API calls on
|
|
3471
|
+
### 1. Actions \u2014 Execute API calls on 250+ platforms
|
|
2866
3472
|
Search for actions, read their docs, and execute them. This is the core workflow.
|
|
2867
3473
|
|
|
2868
3474
|
**Quick start:**
|
|
@@ -2996,91 +3602,7 @@ All errors return JSON: \`{"error": "message"}\`. Check the \`error\` key.
|
|
|
2996
3602
|
- If search returns no results, try broader queries
|
|
2997
3603
|
- Access control settings from \`one config\` may restrict execution
|
|
2998
3604
|
`;
|
|
2999
|
-
var GUIDE_FLOWS =
|
|
3000
|
-
|
|
3001
|
-
## Overview
|
|
3002
|
-
|
|
3003
|
-
Workflows are JSON files at \`.one/flows/<key>.flow.json\` that chain actions across platforms.
|
|
3004
|
-
|
|
3005
|
-
## Commands
|
|
3006
|
-
|
|
3007
|
-
\`\`\`bash
|
|
3008
|
-
one --agent flow create <key> --definition '<json>' # Create
|
|
3009
|
-
one --agent flow list # List
|
|
3010
|
-
one --agent flow validate <key> # Validate
|
|
3011
|
-
one --agent flow execute <key> -i name=value # Execute
|
|
3012
|
-
one --agent flow execute <key> --dry-run --mock # Test with mock data
|
|
3013
|
-
one --agent flow execute <key> --allow-bash # Enable bash steps
|
|
3014
|
-
one --agent flow runs [flowKey] # List past runs
|
|
3015
|
-
one --agent flow resume <runId> # Resume failed run
|
|
3016
|
-
\`\`\`
|
|
3017
|
-
|
|
3018
|
-
## Building a Workflow
|
|
3019
|
-
|
|
3020
|
-
1. **Design first** \u2014 clarify the end goal, map the full value chain, identify where AI analysis is needed
|
|
3021
|
-
2. **Discover connections** \u2014 \`one --agent connection list\`
|
|
3022
|
-
3. **Get knowledge** for every action \u2014 \`one --agent actions knowledge <platform> <actionId>\`
|
|
3023
|
-
4. **Construct JSON** \u2014 declare inputs, wire steps with selectors
|
|
3024
|
-
5. **Validate** \u2014 \`one --agent flow validate <key>\`
|
|
3025
|
-
6. **Execute** \u2014 \`one --agent flow execute <key> -i param=value\`
|
|
3026
|
-
|
|
3027
|
-
## Step Types
|
|
3028
|
-
|
|
3029
|
-
| Type | Purpose |
|
|
3030
|
-
|------|---------|
|
|
3031
|
-
| \`action\` | Execute a platform API action |
|
|
3032
|
-
| \`transform\` | Single JS expression (implicit return) |
|
|
3033
|
-
| \`code\` | Multi-line async JS (explicit return) |
|
|
3034
|
-
| \`condition\` | If/then/else branching |
|
|
3035
|
-
| \`loop\` | Iterate over array with optional concurrency |
|
|
3036
|
-
| \`parallel\` | Run steps concurrently |
|
|
3037
|
-
| \`file-read\` | Read file (optional JSON parse) |
|
|
3038
|
-
| \`file-write\` | Write/append to file |
|
|
3039
|
-
| \`while\` | Do-while loop with condition |
|
|
3040
|
-
| \`flow\` | Execute a sub-flow |
|
|
3041
|
-
| \`paginate\` | Auto-paginate API results |
|
|
3042
|
-
| \`bash\` | Shell command (requires \`--allow-bash\`) |
|
|
3043
|
-
|
|
3044
|
-
## Selectors
|
|
3045
|
-
|
|
3046
|
-
| Pattern | Resolves To |
|
|
3047
|
-
|---------|-------------|
|
|
3048
|
-
| \`$.input.paramName\` | Input value |
|
|
3049
|
-
| \`$.steps.stepId.response\` | Full API response |
|
|
3050
|
-
| \`$.steps.stepId.response.data[0].email\` | Nested field |
|
|
3051
|
-
| \`$.steps.stepId.response.data[*].id\` | Wildcard array map |
|
|
3052
|
-
| \`$.env.MY_VAR\` | Environment variable |
|
|
3053
|
-
| \`$.loop.item\` / \`$.loop.i\` | Loop iteration |
|
|
3054
|
-
| \`"Hello {{$.steps.getUser.response.name}}"\` | String interpolation |
|
|
3055
|
-
|
|
3056
|
-
## Error Handling
|
|
3057
|
-
|
|
3058
|
-
\`\`\`json
|
|
3059
|
-
{"onError": {"strategy": "retry", "retries": 3, "retryDelayMs": 1000}}
|
|
3060
|
-
\`\`\`
|
|
3061
|
-
|
|
3062
|
-
Strategies: \`fail\` (default), \`continue\`, \`retry\`, \`fallback\`
|
|
3063
|
-
|
|
3064
|
-
Conditional execution: \`"if": "$.steps.prev.response.data.length > 0"\`
|
|
3065
|
-
|
|
3066
|
-
## AI-Augmented Pattern
|
|
3067
|
-
|
|
3068
|
-
For workflows that need analysis/summarization, use the file-write \u2192 bash \u2192 code pattern:
|
|
3069
|
-
|
|
3070
|
-
1. \`file-write\` \u2014 save data to temp file
|
|
3071
|
-
2. \`bash\` \u2014 \`claude --print\` analyzes it (\`parseJson: true\`, \`timeout: 180000\`)
|
|
3072
|
-
3. \`code\` \u2014 parse and structure the output
|
|
3073
|
-
|
|
3074
|
-
Set timeout to at least 180000ms (3 min). Run Claude-heavy flows sequentially, not in parallel.
|
|
3075
|
-
|
|
3076
|
-
## Notes
|
|
3077
|
-
|
|
3078
|
-
- Connection keys are **inputs**, not hardcoded
|
|
3079
|
-
- Action IDs in examples are placeholders \u2014 always use \`actions search\`
|
|
3080
|
-
- Code steps allow \`crypto\`, \`buffer\`, \`url\`, \`path\` \u2014 \`fs\`, \`http\`, \`child_process\` are blocked
|
|
3081
|
-
- Bash steps require \`--allow-bash\` flag
|
|
3082
|
-
- State is persisted after every step \u2014 resume picks up where it left off
|
|
3083
|
-
`;
|
|
3605
|
+
var GUIDE_FLOWS = generateFlowGuide();
|
|
3084
3606
|
var GUIDE_RELAY = `# One Relay \u2014 Reference
|
|
3085
3607
|
|
|
3086
3608
|
## Overview
|
|
@@ -3307,7 +3829,7 @@ function buildStep1() {
|
|
|
3307
3829
|
nextCommand: "one onboard --step 2",
|
|
3308
3830
|
message: `# One CLI \u2014 Agent Onboarding
|
|
3309
3831
|
|
|
3310
|
-
You now have access to the One CLI. It connects you to
|
|
3832
|
+
You now have access to the One CLI. It connects you to 250+ platforms
|
|
3311
3833
|
(Gmail, Slack, Stripe, Notion, etc.) \u2014 you'll learn the details shortly.
|
|
3312
3834
|
|
|
3313
3835
|
IMPORTANT: Your only task right now is to send the message below to the
|
|
@@ -3317,7 +3839,7 @@ Do not offer demos yet. Just send this message and wait for their reply:
|
|
|
3317
3839
|
---
|
|
3318
3840
|
|
|
3319
3841
|
I just got access to the One CLI \u2014 it connects me to your platforms like
|
|
3320
|
-
Gmail, Slack, Stripe, and
|
|
3842
|
+
Gmail, Slack, Stripe, and 250+ others so I can work with them directly.
|
|
3321
3843
|
I'll show you what I can do in a moment.
|
|
3322
3844
|
|
|
3323
3845
|
First, one quick setup step: right now only this conversation knows about
|
|
@@ -3346,7 +3868,7 @@ If they declined, skip this section.
|
|
|
3346
3868
|
## One CLI \u2014 Platform Integrations
|
|
3347
3869
|
|
|
3348
3870
|
The One CLI (\`one\`) is installed and configured on this machine. It is a
|
|
3349
|
-
command-line tool that gives AI agents the ability to interact with
|
|
3871
|
+
command-line tool that gives AI agents the ability to interact with 250+
|
|
3350
3872
|
third-party platforms (Gmail, Slack, Shopify, HubSpot, Stripe, GitHub,
|
|
3351
3873
|
Notion, Salesforce, etc.) through a single, unified interface.
|
|
3352
3874
|
|
|
@@ -3441,7 +3963,7 @@ function buildStep3(connections) {
|
|
|
3441
3963
|
The user can connect any of these popular platforms:
|
|
3442
3964
|
Gmail, Google Calendar, Slack, Notion, Shopify, Stripe, HubSpot, GitHub,
|
|
3443
3965
|
Salesforce, QuickBooks, Asana, Jira, Linear, Intercom, Zendesk, Twilio,
|
|
3444
|
-
and
|
|
3966
|
+
and 250+ more. Run \`one platforms\` for the full list.`);
|
|
3445
3967
|
sections.push("## Onboarding complete!\n\nYou're all set. Use `one --agent guide` any time you need the full reference.");
|
|
3446
3968
|
return {
|
|
3447
3969
|
step: 3,
|
|
@@ -3472,7 +3994,7 @@ platform before you can start using actions. Suggest they run:
|
|
|
3472
3994
|
one add slack
|
|
3473
3995
|
one add <any-platform>
|
|
3474
3996
|
|
|
3475
|
-
Run \`one platforms\` to see all
|
|
3997
|
+
Run \`one platforms\` to see all 250+ available platforms.`;
|
|
3476
3998
|
}
|
|
3477
3999
|
const header = `## Current State
|
|
3478
4000
|
|
|
@@ -3532,20 +4054,23 @@ function buildWorkflowIdeas(connections) {
|
|
|
3532
4054
|
|
|
3533
4055
|
// src/commands/update.ts
|
|
3534
4056
|
import { createRequire } from "module";
|
|
3535
|
-
import { spawn
|
|
4057
|
+
import { spawn } from "child_process";
|
|
3536
4058
|
import { readFileSync, writeFileSync, mkdirSync } from "fs";
|
|
3537
4059
|
import { homedir } from "os";
|
|
3538
4060
|
import { join } from "path";
|
|
3539
4061
|
var require2 = createRequire(import.meta.url);
|
|
3540
4062
|
var { version: currentVersion } = require2("../package.json");
|
|
3541
4063
|
var CACHE_PATH = join(homedir(), ".one", "update-check.json");
|
|
3542
|
-
var CHECK_INTERVAL_MS =
|
|
3543
|
-
|
|
4064
|
+
var CHECK_INTERVAL_MS = 4 * 60 * 60 * 1e3;
|
|
4065
|
+
var AGE_GATE_MS = 30 * 60 * 1e3;
|
|
4066
|
+
async function fetchLatestVersionInfo() {
|
|
3544
4067
|
try {
|
|
3545
|
-
const res = await fetch("https://registry.npmjs.org/@withone/cli
|
|
4068
|
+
const res = await fetch("https://registry.npmjs.org/@withone/cli");
|
|
3546
4069
|
if (!res.ok) return null;
|
|
3547
4070
|
const data = await res.json();
|
|
3548
|
-
|
|
4071
|
+
const latest = data["dist-tags"]?.latest;
|
|
4072
|
+
if (!latest) return null;
|
|
4073
|
+
return { version: latest, publishedAt: data.time?.[latest] ?? null };
|
|
3549
4074
|
} catch {
|
|
3550
4075
|
return null;
|
|
3551
4076
|
}
|
|
@@ -3557,24 +4082,26 @@ function readCache() {
|
|
|
3557
4082
|
return null;
|
|
3558
4083
|
}
|
|
3559
4084
|
}
|
|
3560
|
-
function writeCache(latestVersion) {
|
|
4085
|
+
function writeCache(latestVersion, publishedAt) {
|
|
3561
4086
|
try {
|
|
3562
4087
|
mkdirSync(join(homedir(), ".one"), { recursive: true });
|
|
3563
|
-
writeFileSync(CACHE_PATH, JSON.stringify({ lastCheck: Date.now(), latestVersion }));
|
|
4088
|
+
writeFileSync(CACHE_PATH, JSON.stringify({ lastCheck: Date.now(), latestVersion, publishedAt }));
|
|
3564
4089
|
} catch {
|
|
3565
4090
|
}
|
|
3566
4091
|
}
|
|
3567
4092
|
async function checkLatestVersion() {
|
|
3568
|
-
const
|
|
3569
|
-
if (
|
|
3570
|
-
return
|
|
4093
|
+
const info = await fetchLatestVersionInfo();
|
|
4094
|
+
if (info) writeCache(info.version, info.publishedAt);
|
|
4095
|
+
return info?.version ?? null;
|
|
3571
4096
|
}
|
|
3572
4097
|
async function checkLatestVersionCached() {
|
|
3573
4098
|
const cache = readCache();
|
|
3574
4099
|
if (cache && Date.now() - cache.lastCheck < CHECK_INTERVAL_MS) {
|
|
3575
|
-
return cache.latestVersion;
|
|
4100
|
+
return { version: cache.latestVersion, publishedAt: cache.publishedAt ?? null };
|
|
3576
4101
|
}
|
|
3577
|
-
|
|
4102
|
+
const info = await fetchLatestVersionInfo();
|
|
4103
|
+
if (info) writeCache(info.version, info.publishedAt);
|
|
4104
|
+
return info;
|
|
3578
4105
|
}
|
|
3579
4106
|
function getCurrentVersion() {
|
|
3580
4107
|
return currentVersion;
|
|
@@ -3599,7 +4126,7 @@ async function updateCommand() {
|
|
|
3599
4126
|
s.stop(`Update available: v${currentVersion} \u2192 v${latestVersion}`);
|
|
3600
4127
|
console.log(`Updating @withone/cli: v${currentVersion} \u2192 v${latestVersion}...`);
|
|
3601
4128
|
const code = await new Promise((resolve) => {
|
|
3602
|
-
const child =
|
|
4129
|
+
const child = spawn("npm", ["install", "-g", "@withone/cli@latest", "--force"], {
|
|
3603
4130
|
stdio: isAgentMode() ? "pipe" : "inherit",
|
|
3604
4131
|
shell: true
|
|
3605
4132
|
});
|
|
@@ -3616,12 +4143,24 @@ async function updateCommand() {
|
|
|
3616
4143
|
error("Update failed \u2014 try running: npm install -g @withone/cli@latest");
|
|
3617
4144
|
}
|
|
3618
4145
|
}
|
|
4146
|
+
function autoUpdate(targetVersion, publishedAt) {
|
|
4147
|
+
if (publishedAt) {
|
|
4148
|
+
const age = Date.now() - new Date(publishedAt).getTime();
|
|
4149
|
+
if (age < AGE_GATE_MS) return;
|
|
4150
|
+
}
|
|
4151
|
+
const child = spawn("npm", ["install", "-g", `@withone/cli@${targetVersion}`], {
|
|
4152
|
+
detached: true,
|
|
4153
|
+
stdio: "ignore",
|
|
4154
|
+
shell: true
|
|
4155
|
+
});
|
|
4156
|
+
child.unref();
|
|
4157
|
+
}
|
|
3619
4158
|
|
|
3620
4159
|
// src/index.ts
|
|
3621
4160
|
var require3 = createRequire2(import.meta.url);
|
|
3622
4161
|
var { version } = require3("../package.json");
|
|
3623
4162
|
var program = new Command();
|
|
3624
|
-
program.name("one").option("--agent", "Machine-readable JSON output (no colors, spinners, or prompts)").description(`One CLI \u2014 Connect AI agents to
|
|
4163
|
+
program.name("one").option("--agent", "Machine-readable JSON output (no colors, spinners, or prompts)").description(`One CLI \u2014 Connect AI agents to 250+ platforms through one interface.
|
|
3625
4164
|
|
|
3626
4165
|
Setup:
|
|
3627
4166
|
one init Set up API key and install MCP server
|
|
@@ -3665,7 +4204,7 @@ program.name("one").option("--agent", "Machine-readable JSON output (no colors,
|
|
|
3665
4204
|
-d '{"to":"j@example.com","subject":"Hello","body":"Hi!","connectionKey":"live::gmail::default::abc123"}'
|
|
3666
4205
|
|
|
3667
4206
|
Platform names are always kebab-case (e.g. hub-spot, ship-station, google-calendar).
|
|
3668
|
-
Run 'one platforms' to browse all
|
|
4207
|
+
Run 'one platforms' to browse all 250+ available platforms.`).version(version);
|
|
3669
4208
|
var updateCheckPromise;
|
|
3670
4209
|
program.hook("preAction", (thisCommand) => {
|
|
3671
4210
|
const opts = program.opts();
|
|
@@ -3679,18 +4218,11 @@ program.hook("preAction", (thisCommand) => {
|
|
|
3679
4218
|
});
|
|
3680
4219
|
program.hook("postAction", async () => {
|
|
3681
4220
|
if (!updateCheckPromise) return;
|
|
3682
|
-
const
|
|
3683
|
-
if (!
|
|
4221
|
+
const info = await updateCheckPromise;
|
|
4222
|
+
if (!info) return;
|
|
3684
4223
|
const current = getCurrentVersion();
|
|
3685
|
-
if (current ===
|
|
3686
|
-
|
|
3687
|
-
process.stderr.write(`
|
|
3688
|
-
Update available: v${current} \u2192 v${latestVersion}. Run "one update" to upgrade.
|
|
3689
|
-
`);
|
|
3690
|
-
} else {
|
|
3691
|
-
console.log(`
|
|
3692
|
-
\x1B[33mUpdate available: v${current} \u2192 v${latestVersion}. Run \x1B[1mone update\x1B[22m to upgrade.\x1B[0m`);
|
|
3693
|
-
}
|
|
4224
|
+
if (current === info.version) return;
|
|
4225
|
+
autoUpdate(info.version, info.publishedAt);
|
|
3694
4226
|
});
|
|
3695
4227
|
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) => {
|
|
3696
4228
|
await initCommand(options);
|
|
@@ -3745,6 +4277,9 @@ flow.command("resume <runId>").description("Resume a paused or failed workflow r
|
|
|
3745
4277
|
flow.command("runs [flowKey]").description("List workflow runs (optionally filtered by flow key)").action(async (flowKey) => {
|
|
3746
4278
|
await flowRunsCommand(flowKey);
|
|
3747
4279
|
});
|
|
4280
|
+
flow.command("scaffold [template]").description("Generate a workflow scaffold (templates: basic, conditional, loop, ai)").action(async (template) => {
|
|
4281
|
+
await flowScaffoldCommand(template);
|
|
4282
|
+
});
|
|
3748
4283
|
var relay = program.command("relay").alias("r").description("Receive webhooks from platforms and relay them via passthrough actions");
|
|
3749
4284
|
relay.command("create").description("Create a new relay endpoint for a connection").requiredOption("--connection-key <key>", "Connection key for the source platform").option("--description <desc>", "Description of the relay endpoint").option("--event-filters <json>", `JSON array of event types to filter (e.g. '["customer.created"]')`).option("--tags <json>", "JSON array of tags").option("--create-webhook", "Automatically register the webhook with the source platform").action(async (options) => {
|
|
3750
4285
|
await relayCreateCommand(options);
|