@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/index.js CHANGED
@@ -11,7 +11,7 @@ import {
11
11
  loadFlow,
12
12
  resolveFlowPath,
13
13
  saveFlow
14
- } from "./chunk-SX2Y4HDW.js";
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 pc3 from "picocolors";
22
+ import pc2 from "picocolors";
23
+ import fs3 from "fs";
23
24
  import path3 from "path";
24
- import { spawn } from "child_process";
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(` ${pc3.bold("Current Setup")}`);
589
- console.log(` ${pc3.dim("\u2500".repeat(42))}`);
590
- console.log(` ${pc3.dim("API Key:")} ${masked}`);
591
- console.log(` ${pc3.dim("Config:")} ${getConfigPath()}`);
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: "update-key",
622
- label: "Update API key"
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: "install-skills",
626
- label: "Install/update skills"
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 runSkillsInstall();
611
+ const success = await promptSkillInstall();
667
612
  if (success) {
668
- p3.outro("Skills installed successfully.");
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 "install-more":
678
- await handleInstallMore(apiKey, agentsMissingGlobal);
620
+ case "show-prompt":
621
+ printOnboardingPrompt();
622
+ p3.outro("Paste the prompt above to your AI agent.");
679
623
  break;
680
- case "install-project":
681
- await handleInstallProject(apiKey, agentsMissingProject);
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
- ${pc3.cyan(getApiKeyUrl())}`, "API Key");
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
- async function handleInstallMore(apiKey, missing) {
755
- const ac = getAccessControl();
756
- if (missing.length === 1) {
757
- const agent = missing[0].agent;
758
- const confirm3 = await p3.confirm({
759
- message: `Install One MCP to ${agent.name}?`,
760
- initialValue: true
761
- });
762
- if (p3.isCancel(confirm3) || !confirm3) {
763
- p3.outro("No changes made.");
764
- return;
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
- const selected = await p3.multiselect({
773
- message: "Select agents to install MCP:",
774
- options: missing.map((s) => ({
775
- value: s.agent.id,
776
- label: s.agent.name
777
- }))
778
- });
779
- if (p3.isCancel(selected)) {
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
- const agents = missing.filter((s) => selected.includes(s.agent.id));
784
- for (const s of agents) {
785
- installMcpConfig(s.agent, apiKey, "global", ac);
786
- updateConfigAgents(s.agent.id);
787
- p3.log.success(`${s.agent.name}: MCP installed`);
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
- p3.outro("Done.");
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 handleInstallProject(apiKey, missing) {
792
- const ac = getAccessControl();
793
- if (missing.length === 1) {
794
- const agent = missing[0].agent;
795
- const confirm3 = await p3.confirm({
796
- message: `Install project-level MCP for ${agent.name}?`,
797
- initialValue: true
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(confirm3) || !confirm3) {
800
- p3.outro("No changes made.");
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
- const selected = await p3.multiselect({
814
- message: "Select agents for project-level MCP:",
815
- options: missing.map((s) => ({
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 agents = missing.filter((s) => selected.includes(s.agent.id));
825
- for (const s of agents) {
826
- installMcpConfig(s.agent, apiKey, "project", ac);
827
- const configPath = getAgentConfigPath(s.agent, "project");
828
- p3.log.success(`${s.agent.name}: ${configPath} created`);
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
- p3.note(
831
- pc3.yellow("Project config files can be committed to share with your team.\n") + pc3.yellow("Team members will need their own API key."),
832
- "Tip"
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
- async function runSkillsInstall() {
837
- const __dirname2 = path3.dirname(fileURLToPath(import.meta.url));
838
- const skillsDir = path3.resolve(__dirname2, "..", "skills");
839
- return new Promise((resolve) => {
840
- const handler = () => {
841
- };
842
- process.on("SIGINT", handler);
843
- const child = spawn("npx", ["skills", "add", skillsDir], {
844
- stdio: "inherit",
845
- shell: true
846
- });
847
- child.on("close", (code) => {
848
- process.removeListener("SIGINT", handler);
849
- resolve(code === 0);
850
- });
851
- child.on("error", () => {
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
- pc3.yellow("Project config files can be committed to share with your team.\n") + pc3.yellow("Team members will need their own API key."),
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
- ${pc3.cyan(getApiKeyUrl())}`, "API Key");
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
- const installSkills = await p3.confirm({
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: ${pc3.dim(getConfigPath())}`,
1012
+ `Config saved to: ${pc2.dim(getConfigPath())}`,
1021
1013
  "Setup Complete"
1022
1014
  );
1023
- p3.note(
1024
- `Tell your agent to run ${pc3.cyan("one onboard")} to learn what it can do.`,
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(pc3.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"));
1032
- console.log(pc3.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"));
1033
- console.log(pc3.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 "));
1034
- console.log(pc3.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 "));
1035
- console.log(pc3.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 "));
1036
- console.log(pc3.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 "));
1037
- console.log(pc3.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 "));
1038
- console.log(pc3.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 "));
1039
- console.log(pc3.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"));
1040
- console.log(pc3.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"));
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(pc3.dim(" U N I V E R S A L I N T E G R A T I O N S F O R A I"));
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 ${pc3.cyan("one add <platform>")}`);
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 ${pc3.cyan(label)}...`);
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(`${pc3.green("\u2713")} ${label} is now available to your AI agents`);
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: ${pc3.cyan(`one add ${platform}`)}`);
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
- } catch (error2) {
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
- // src/commands/flow.ts
1715
- import pc7 from "picocolors";
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 (!/^[a-z0-9][a-z0-9-]*[a-z0-9]$/.test(f.key) && f.key.length > 1) {
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 || !VALID_INPUT_TYPES.includes(d.type)) {
1767
- errors.push({ path: `${prefix}.type`, message: `Input type must be one of: ${VALID_INPUT_TYPES.join(", ")}` });
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 || !VALID_STEP_TYPES.includes(s.type)) {
1804
- errors.push({ path: `${path4}.type`, message: `Step type must be one of: ${VALID_STEP_TYPES.join(", ")}` });
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 (!VALID_ERROR_STRATEGIES.includes(oe.strategy)) {
1809
- errors.push({ path: `${path4}.onError.strategy`, message: `Error strategy must be one of: ${VALID_ERROR_STRATEGIES.join(", ")}` });
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 type = s.type;
1813
- if (type === "action") {
1814
- if (!s.action || typeof s.action !== "object") {
1815
- errors.push({ path: `${path4}.action`, message: 'Action step must have an "action" config object' });
1816
- } else {
1817
- const a = s.action;
1818
- if (!a.platform) errors.push({ path: `${path4}.action.platform`, message: 'Action must have "platform"' });
1819
- if (!a.actionId) errors.push({ path: `${path4}.action.actionId`, message: 'Action must have "actionId"' });
1820
- if (!a.connectionKey) errors.push({ path: `${path4}.action.connectionKey`, message: 'Action must have "connectionKey"' });
1821
- }
1822
- } else if (type === "transform") {
1823
- if (!s.transform || typeof s.transform !== "object") {
1824
- errors.push({ path: `${path4}.transform`, message: 'Transform step must have a "transform" config object' });
1825
- } else {
1826
- const t = s.transform;
1827
- if (!t.expression || typeof t.expression !== "string") {
1828
- errors.push({ path: `${path4}.transform.expression`, message: 'Transform must have a string "expression"' });
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
- } else if (type === "file-write") {
1899
- if (!s.fileWrite || typeof s.fileWrite !== "object") {
1900
- errors.push({ path: `${path4}.fileWrite`, message: 'File-write step must have a "fileWrite" config object' });
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
- } else if (type === "while") {
1911
- if (!s.while || typeof s.while !== "object") {
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
- } else if (type === "flow") {
1928
- if (!s.flow || typeof s.flow !== "object") {
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
- } else if (type === "paginate") {
1940
- if (!s.paginate || typeof s.paginate !== "object") {
1941
- errors.push({ path: `${path4}.paginate`, message: 'Paginate step must have a "paginate" config object' });
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
- const a = p7.action;
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
- } else if (type === "bash") {
1966
- if (!s.bash || typeof s.bash !== "object") {
1967
- errors.push({ path: `${path4}.bash`, message: 'Bash step must have a "bash" config object' });
1968
- } else {
1969
- const b = s.bash;
1970
- if (!b.command || typeof b.command !== "string") {
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
- if (step.condition) {
1996
- if (step.condition.then) collectIds(step.condition.then, `${path4}.condition.then`);
1997
- if (step.condition.else) collectIds(step.condition.else, `${path4}.condition.else`);
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
- if (step.condition) {
2015
- for (const id of getAllStepIds(step.condition.then)) ids.add(id);
2016
- if (step.condition.else) {
2017
- for (const id of getAllStepIds(step.condition.else)) ids.add(id);
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
- if (step.action) {
2076
- checkSelectors(extractSelectors(step.action), `${pathPrefix}.action`);
2077
- }
2078
- if (step.transform) {
2079
- }
2080
- if (step.condition) {
2081
- checkStep({ id: "__cond_expr", name: "", type: "transform", transform: { expression: "" } }, pathPrefix);
2082
- step.condition.then.forEach((s, i) => checkStep(s, `${pathPrefix}.condition.then[${i}]`));
2083
- step.condition.else?.forEach((s, i) => checkStep(s, `${pathPrefix}.condition.else[${i}]`));
2084
- }
2085
- if (step.loop) {
2086
- checkSelectors(extractSelectors(step.loop.over), `${pathPrefix}.loop.over`);
2087
- step.loop.steps.forEach((s, i) => checkStep(s, `${pathPrefix}.loop.steps[${i}]`));
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
- if (step.bash.env) {
2117
- checkSelectors(extractSelectors(step.bash.env), `${pathPrefix}.bash.env`);
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 fs3 from "fs";
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(options.definition);
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 = fs3.readFileSync(flowPath, "utf-8");
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: Learn before you use
3465
+ ## IMPORTANT: Read the guide before you act
2860
3466
 
2861
- Before using any feature, you MUST read the corresponding skill documentation first. The skills are bundled with the CLI and teach you the correct workflow, required steps, template syntax, and common mistakes. Never guess \u2014 read the skill, then act.
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 200+ platforms
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 = `# One Flows \u2014 Reference
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 200+ platforms
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 200+ others so I can work with them directly.
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 200+
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 200+ more. Run \`one platforms\` for the full list.`);
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 200+ available platforms.`;
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 as spawn2 } from "child_process";
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 = 24 * 60 * 60 * 1e3;
3543
- async function fetchLatestVersion() {
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/latest");
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
- return data.version;
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 version2 = await fetchLatestVersion();
3569
- if (version2) writeCache(version2);
3570
- return version2;
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
- return checkLatestVersion();
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 = spawn2("npm", ["install", "-g", "@withone/cli@latest", "--force"], {
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 200+ platforms through one interface.
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 200+ available platforms.`).version(version);
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 latestVersion = await updateCheckPromise;
3683
- if (!latestVersion) return;
4221
+ const info = await updateCheckPromise;
4222
+ if (!info) return;
3684
4223
  const current = getCurrentVersion();
3685
- if (current === latestVersion) return;
3686
- if (isAgentMode()) {
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);