@withone/cli 1.21.0 → 1.23.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -16,7 +16,7 @@ import {
16
16
  loadFlowWithMeta,
17
17
  resolveFlowPath,
18
18
  saveFlow
19
- } from "./chunk-KZOFPEHD.js";
19
+ } from "./chunk-Z45IBT5P.js";
20
20
 
21
21
  // src/index.ts
22
22
  import { createRequire as createRequire2 } from "module";
@@ -25,10 +25,10 @@ import { Command } from "commander";
25
25
  // src/commands/init.ts
26
26
  import * as p3 from "@clack/prompts";
27
27
  import pc2 from "picocolors";
28
- import fs3 from "fs";
29
- import path3 from "path";
30
- import os3 from "os";
31
- import { fileURLToPath } from "url";
28
+ import fs4 from "fs";
29
+ import path4 from "path";
30
+ import os4 from "os";
31
+ import { fileURLToPath as fileURLToPath2 } from "url";
32
32
 
33
33
  // src/lib/config.ts
34
34
  import fs from "fs";
@@ -53,11 +53,11 @@ function readConfig() {
53
53
  return null;
54
54
  }
55
55
  }
56
- function writeConfig(config) {
56
+ function writeConfig(config2) {
57
57
  if (!fs.existsSync(CONFIG_DIR)) {
58
58
  fs.mkdirSync(CONFIG_DIR, { mode: 448 });
59
59
  }
60
- fs.writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2), { mode: 384 });
60
+ fs.writeFileSync(CONFIG_FILE, JSON.stringify(config2, null, 2), { mode: 384 });
61
61
  }
62
62
  function readOneRc() {
63
63
  const rcPath = path.join(process.cwd(), ".onerc");
@@ -108,32 +108,32 @@ function getAccessControl() {
108
108
  }
109
109
  var DEFAULT_API_BASE = "https://api.withone.ai/v1";
110
110
  function getApiBase() {
111
- const config = readConfig();
112
- if (config?.apiBase) return `${config.apiBase}/v1`;
111
+ const config2 = readConfig();
112
+ if (config2?.apiBase) return `${config2.apiBase}/v1`;
113
113
  return DEFAULT_API_BASE;
114
114
  }
115
115
  function updateApiBase(url) {
116
- const config = readConfig();
117
- if (!config) return;
116
+ const config2 = readConfig();
117
+ if (!config2) return;
118
118
  if (url) {
119
- config.apiBase = url;
119
+ config2.apiBase = url;
120
120
  } else {
121
- delete config.apiBase;
121
+ delete config2.apiBase;
122
122
  }
123
- writeConfig(config);
123
+ writeConfig(config2);
124
124
  }
125
125
  function getCacheTtl() {
126
126
  if (process.env.ONE_CACHE_TTL) {
127
127
  const val = parseInt(process.env.ONE_CACHE_TTL, 10);
128
128
  if (!isNaN(val) && val > 0) return val;
129
129
  }
130
- const config = readConfig();
131
- if (config?.cacheTtl && config.cacheTtl > 0) return config.cacheTtl;
130
+ const config2 = readConfig();
131
+ if (config2?.cacheTtl && config2.cacheTtl > 0) return config2.cacheTtl;
132
132
  return 3600;
133
133
  }
134
134
  function updateAccessControl(settings) {
135
- const config = readConfig();
136
- if (!config) return;
135
+ const config2 = readConfig();
136
+ if (!config2) return;
137
137
  const cleaned = {};
138
138
  if (settings.permissions && settings.permissions !== "admin") {
139
139
  cleaned.permissions = settings.permissions;
@@ -148,11 +148,11 @@ function updateAccessControl(settings) {
148
148
  cleaned.knowledgeAgent = true;
149
149
  }
150
150
  if (Object.keys(cleaned).length === 0) {
151
- delete config.accessControl;
151
+ delete config2.accessControl;
152
152
  } else {
153
- config.accessControl = cleaned;
153
+ config2.accessControl = cleaned;
154
154
  }
155
- writeConfig(config);
155
+ writeConfig(config2);
156
156
  }
157
157
 
158
158
  // src/lib/agents.ts
@@ -274,16 +274,16 @@ function readAgentConfig(agent, scope = "global") {
274
274
  return {};
275
275
  }
276
276
  }
277
- function writeAgentConfig(agent, config, scope = "global") {
277
+ function writeAgentConfig(agent, config2, scope = "global") {
278
278
  const configPath = getAgentConfigPath(agent, scope);
279
279
  const configDir = path2.dirname(configPath);
280
280
  if (!fs2.existsSync(configDir)) {
281
281
  fs2.mkdirSync(configDir, { recursive: true });
282
282
  }
283
283
  if (agent.configFormat === "toml") {
284
- fs2.writeFileSync(configPath, stringifyToml(config));
284
+ fs2.writeFileSync(configPath, stringifyToml(config2));
285
285
  } else {
286
- fs2.writeFileSync(configPath, JSON.stringify(config, null, 2));
286
+ fs2.writeFileSync(configPath, JSON.stringify(config2, null, 2));
287
287
  }
288
288
  }
289
289
  function getMcpServerConfig(apiKey, accessControl) {
@@ -311,17 +311,17 @@ function getMcpServerConfig(apiKey, accessControl) {
311
311
  };
312
312
  }
313
313
  function installMcpConfig(agent, apiKey, scope = "global", accessControl) {
314
- const config = readAgentConfig(agent, scope);
314
+ const config2 = readAgentConfig(agent, scope);
315
315
  const configKey = agent.configKey;
316
- const mcpServers = config[configKey] || {};
316
+ const mcpServers = config2[configKey] || {};
317
317
  mcpServers["one"] = getMcpServerConfig(apiKey, accessControl);
318
- config[configKey] = mcpServers;
319
- writeAgentConfig(agent, config, scope);
318
+ config2[configKey] = mcpServers;
319
+ writeAgentConfig(agent, config2, scope);
320
320
  }
321
321
  function isMcpInstalled(agent, scope = "global") {
322
- const config = readAgentConfig(agent, scope);
322
+ const config2 = readAgentConfig(agent, scope);
323
323
  const configKey = agent.configKey;
324
- const mcpServers = config[configKey];
324
+ const mcpServers = config2[configKey];
325
325
  return mcpServers?.["one"] !== void 0;
326
326
  }
327
327
  function getAgentStatuses() {
@@ -397,8 +397,8 @@ async function configCommand() {
397
397
  if (isAgentMode()) {
398
398
  error("This command requires interactive input. Run without --agent.");
399
399
  }
400
- const config = readConfig();
401
- if (!config) {
400
+ const config2 = readConfig();
401
+ if (!config2) {
402
402
  p2.log.error(`No One config found. Run ${pc.cyan("one init")} first.`);
403
403
  return;
404
404
  }
@@ -439,7 +439,7 @@ async function configCommand() {
439
439
  }
440
440
  let connectionKeys;
441
441
  if (connectionMode === "specific") {
442
- connectionKeys = await selectConnections(config.apiKey);
442
+ connectionKeys = await selectConnections(config2.apiKey);
443
443
  if (connectionKeys === void 0) {
444
444
  p2.outro("No changes made.");
445
445
  return;
@@ -500,7 +500,7 @@ async function configCommand() {
500
500
  p2.outro("No changes made.");
501
501
  return;
502
502
  }
503
- let newApiKey = config.apiKey;
503
+ let newApiKey = config2.apiKey;
504
504
  if (baseUrlMode === "custom") {
505
505
  const customUrl = await p2.text({
506
506
  message: "Enter API base URL:",
@@ -601,7 +601,7 @@ async function configCommand() {
601
601
  };
602
602
  updateAccessControl(settings);
603
603
  const updatedConfig = readConfig();
604
- if (updatedConfig && newApiKey !== config.apiKey) {
604
+ if (updatedConfig && newApiKey !== config2.apiKey) {
605
605
  updatedConfig.apiKey = newApiKey;
606
606
  writeConfig(updatedConfig);
607
607
  }
@@ -667,6 +667,214 @@ function formatList(list) {
667
667
 
668
668
  // src/commands/init.ts
669
669
  import open2 from "open";
670
+
671
+ // src/lib/skill-sync.ts
672
+ import fs3 from "fs";
673
+ import os3 from "os";
674
+ import path3 from "path";
675
+ import { fileURLToPath } from "url";
676
+
677
+ // src/commands/update.ts
678
+ import { createRequire } from "module";
679
+ import { spawn } from "child_process";
680
+ import { readFileSync, writeFileSync, mkdirSync } from "fs";
681
+ import { homedir } from "os";
682
+ import { join } from "path";
683
+ var require2 = createRequire(import.meta.url);
684
+ var { version: currentVersion } = require2("../package.json");
685
+ var CACHE_PATH = join(homedir(), ".one", "update-check.json");
686
+ var CHECK_INTERVAL_MS = 4 * 60 * 60 * 1e3;
687
+ var AGE_GATE_MS = 30 * 60 * 1e3;
688
+ async function fetchLatestVersionInfo() {
689
+ try {
690
+ const res = await fetch("https://registry.npmjs.org/@withone/cli");
691
+ if (!res.ok) return null;
692
+ const data = await res.json();
693
+ const latest = data["dist-tags"]?.latest;
694
+ if (!latest) return null;
695
+ return { version: latest, publishedAt: data.time?.[latest] ?? null };
696
+ } catch {
697
+ return null;
698
+ }
699
+ }
700
+ function readCache() {
701
+ try {
702
+ return JSON.parse(readFileSync(CACHE_PATH, "utf8"));
703
+ } catch {
704
+ return null;
705
+ }
706
+ }
707
+ function writeCache(latestVersion, publishedAt) {
708
+ try {
709
+ mkdirSync(join(homedir(), ".one"), { recursive: true });
710
+ writeFileSync(CACHE_PATH, JSON.stringify({ lastCheck: Date.now(), latestVersion, publishedAt }));
711
+ } catch {
712
+ }
713
+ }
714
+ async function checkLatestVersion() {
715
+ const info = await fetchLatestVersionInfo();
716
+ if (info) writeCache(info.version, info.publishedAt);
717
+ return info?.version ?? null;
718
+ }
719
+ async function checkLatestVersionCached() {
720
+ const cache2 = readCache();
721
+ if (cache2 && Date.now() - cache2.lastCheck < CHECK_INTERVAL_MS) {
722
+ return { version: cache2.latestVersion, publishedAt: cache2.publishedAt ?? null };
723
+ }
724
+ const info = await fetchLatestVersionInfo();
725
+ if (info) writeCache(info.version, info.publishedAt);
726
+ return info;
727
+ }
728
+ function getCurrentVersion() {
729
+ return currentVersion;
730
+ }
731
+ async function updateCommand() {
732
+ const s = createSpinner();
733
+ s.start("Checking for updates...");
734
+ const latestVersion = await checkLatestVersion();
735
+ if (!latestVersion) {
736
+ s.stop("");
737
+ error("Failed to check for updates \u2014 could not reach npm registry");
738
+ }
739
+ if (currentVersion === latestVersion) {
740
+ s.stop("Already up to date");
741
+ if (isAgentMode()) {
742
+ json({ current: currentVersion, latest: latestVersion, updated: false, message: "Already up to date" });
743
+ } else {
744
+ console.log(`Already up to date (v${currentVersion})`);
745
+ }
746
+ return;
747
+ }
748
+ s.stop(`Update available: v${currentVersion} \u2192 v${latestVersion}`);
749
+ console.log(`Updating @withone/cli: v${currentVersion} \u2192 v${latestVersion}...`);
750
+ const code = await new Promise((resolve) => {
751
+ const child = spawn("npm", ["install", "-g", "@withone/cli@latest", "--force"], {
752
+ stdio: isAgentMode() ? "pipe" : "inherit",
753
+ shell: true
754
+ });
755
+ child.on("close", resolve);
756
+ child.on("error", () => resolve(1));
757
+ });
758
+ if (code === 0) {
759
+ if (isAgentMode()) {
760
+ json({ current: currentVersion, latest: latestVersion, updated: true, message: "Updated successfully" });
761
+ } else {
762
+ console.log(`Successfully updated to v${latestVersion}`);
763
+ }
764
+ } else {
765
+ error("Update failed \u2014 try running: npm install -g @withone/cli@latest");
766
+ }
767
+ }
768
+ function isNewerVersion(latest, current) {
769
+ const parse = (v) => v.split(".").map(Number);
770
+ const [lMaj, lMin, lPat] = parse(latest);
771
+ const [cMaj, cMin, cPat] = parse(current);
772
+ if (lMaj !== cMaj) return lMaj > cMaj;
773
+ if (lMin !== cMin) return lMin > cMin;
774
+ return lPat > cPat;
775
+ }
776
+ function autoUpdate(targetVersion, publishedAt) {
777
+ if (publishedAt) {
778
+ const age = Date.now() - new Date(publishedAt).getTime();
779
+ if (age < AGE_GATE_MS) return;
780
+ }
781
+ const child = spawn("npm", ["install", "-g", `@withone/cli@${targetVersion}`], {
782
+ detached: true,
783
+ stdio: "ignore",
784
+ shell: true
785
+ });
786
+ child.unref();
787
+ }
788
+
789
+ // src/lib/skill-sync.ts
790
+ var CANONICAL_SKILL_DIR = ".agents/skills";
791
+ var VERSION_MARKER = ".one-cli-version";
792
+ function getPackagedSkillDir() {
793
+ const here = path3.dirname(fileURLToPath(import.meta.url));
794
+ return path3.resolve(here, "..", "skills", "one");
795
+ }
796
+ function getCanonicalSkillPath() {
797
+ return path3.join(os3.homedir(), CANONICAL_SKILL_DIR, "one");
798
+ }
799
+ function getVersionMarkerPath() {
800
+ return path3.join(getCanonicalSkillPath(), VERSION_MARKER);
801
+ }
802
+ function isSkillInstalled() {
803
+ return fs3.existsSync(path3.join(getCanonicalSkillPath(), "SKILL.md"));
804
+ }
805
+ function readInstalledSkillVersion() {
806
+ try {
807
+ return fs3.readFileSync(getVersionMarkerPath(), "utf-8").trim() || null;
808
+ } catch {
809
+ return null;
810
+ }
811
+ }
812
+ function writeInstalledSkillVersion(version2) {
813
+ try {
814
+ fs3.mkdirSync(getCanonicalSkillPath(), { recursive: true });
815
+ fs3.writeFileSync(getVersionMarkerPath(), `${version2}
816
+ `);
817
+ } catch {
818
+ }
819
+ }
820
+ function copyDirSync(src, dest) {
821
+ fs3.mkdirSync(dest, { recursive: true });
822
+ for (const entry of fs3.readdirSync(src, { withFileTypes: true })) {
823
+ const srcPath = path3.join(src, entry.name);
824
+ const destPath = path3.join(dest, entry.name);
825
+ if (entry.isDirectory()) {
826
+ copyDirSync(srcPath, destPath);
827
+ } else {
828
+ fs3.copyFileSync(srcPath, destPath);
829
+ }
830
+ }
831
+ }
832
+ function syncSkillsIfStale() {
833
+ if (!isSkillInstalled()) {
834
+ return { synced: false, reason: "not-installed" };
835
+ }
836
+ const current = getCurrentVersion();
837
+ const installed = readInstalledSkillVersion();
838
+ if (installed === current) {
839
+ return { synced: false, reason: "up-to-date", from: installed, to: current };
840
+ }
841
+ return performSync(current, installed === null ? "missing-marker" : "stale");
842
+ }
843
+ function forceSyncSkills() {
844
+ if (!isSkillInstalled()) {
845
+ return { synced: false, reason: "not-installed" };
846
+ }
847
+ return performSync(getCurrentVersion(), "forced");
848
+ }
849
+ function performSync(current, reason) {
850
+ const source = getPackagedSkillDir();
851
+ if (!fs3.existsSync(path3.join(source, "SKILL.md"))) {
852
+ return { synced: false, reason: "source-missing" };
853
+ }
854
+ const canonical = getCanonicalSkillPath();
855
+ try {
856
+ copyDirSync(source, canonical);
857
+ writeInstalledSkillVersion(current);
858
+ return { synced: true, reason, from: readInstalledSkillVersion() ?? null, to: current };
859
+ } catch (err) {
860
+ return { synced: false, reason: "error", error: err instanceof Error ? err.message : String(err) };
861
+ }
862
+ }
863
+ function getSkillStatus() {
864
+ const installed = isSkillInstalled();
865
+ const installedVersion = readInstalledSkillVersion();
866
+ const currentVersion2 = getCurrentVersion();
867
+ return {
868
+ installed,
869
+ canonicalPath: getCanonicalSkillPath(),
870
+ installedVersion,
871
+ currentVersion: currentVersion2,
872
+ upToDate: installed && installedVersion === currentVersion2,
873
+ markerExists: installedVersion !== null
874
+ };
875
+ }
876
+
877
+ // src/commands/init.ts
670
878
  async function initCommand(options) {
671
879
  if (isAgentMode()) {
672
880
  error("This command requires interactive input. Run without --agent.");
@@ -682,7 +890,7 @@ async function initCommand(options) {
682
890
  async function handleExistingConfig(apiKey, options) {
683
891
  const statuses = getAgentStatuses();
684
892
  const masked = maskApiKey(apiKey);
685
- const skillInstalled = isSkillInstalled();
893
+ const skillInstalled = isSkillInstalled2();
686
894
  console.log();
687
895
  console.log(` ${pc2.bold("Current Setup")}`);
688
896
  console.log(` ${pc2.dim("\u2500".repeat(42))}`);
@@ -818,12 +1026,12 @@ ${pc2.cyan(getApiKeyUrl())}`, "API Key");
818
1026
  reinstalled.push(`${s.agent.name} (project)`);
819
1027
  }
820
1028
  }
821
- const config = readConfig();
1029
+ const config2 = readConfig();
822
1030
  writeConfig({
823
1031
  apiKey: newKey,
824
- installedAgents: config?.installedAgents ?? [],
825
- createdAt: config?.createdAt ?? (/* @__PURE__ */ new Date()).toISOString(),
826
- accessControl: config?.accessControl
1032
+ installedAgents: config2?.installedAgents ?? [],
1033
+ createdAt: config2?.createdAt ?? (/* @__PURE__ */ new Date()).toISOString(),
1034
+ accessControl: config2?.accessControl
827
1035
  });
828
1036
  if (reinstalled.length > 0) {
829
1037
  p3.log.success(`Updated MCP configs: ${reinstalled.join(", ")}`);
@@ -842,48 +1050,49 @@ var SKILL_AGENTS = [
842
1050
  { id: "opencode", name: "OpenCode", skillDir: ".opencode/skills" },
843
1051
  { id: "roo", name: "Roo", skillDir: ".roo/skills" }
844
1052
  ];
845
- var CANONICAL_SKILL_DIR = ".agents/skills";
1053
+ var CANONICAL_SKILL_DIR2 = ".agents/skills";
846
1054
  function getSkillSourceDir() {
847
- const __dirname2 = path3.dirname(fileURLToPath(import.meta.url));
848
- return path3.resolve(__dirname2, "..", "skills", "one");
1055
+ const __dirname2 = path4.dirname(fileURLToPath2(import.meta.url));
1056
+ return path4.resolve(__dirname2, "..", "skills", "one");
849
1057
  }
850
- function getCanonicalSkillPath() {
851
- return path3.join(os3.homedir(), CANONICAL_SKILL_DIR, "one");
1058
+ function getCanonicalSkillPath2() {
1059
+ return path4.join(os4.homedir(), CANONICAL_SKILL_DIR2, "one");
852
1060
  }
853
1061
  function getAgentSkillPath(agent) {
854
- return path3.join(os3.homedir(), agent.skillDir, "one");
1062
+ return path4.join(os4.homedir(), agent.skillDir, "one");
855
1063
  }
856
- function isSkillInstalled() {
857
- return fs3.existsSync(path3.join(getCanonicalSkillPath(), "SKILL.md"));
1064
+ function isSkillInstalled2() {
1065
+ return fs4.existsSync(path4.join(getCanonicalSkillPath2(), "SKILL.md"));
858
1066
  }
859
1067
  function isSkillInstalledForAgent(agent) {
860
- return fs3.existsSync(path3.join(getAgentSkillPath(agent), "SKILL.md"));
1068
+ return fs4.existsSync(path4.join(getAgentSkillPath(agent), "SKILL.md"));
861
1069
  }
862
- function copyDirSync(src, dest) {
863
- fs3.mkdirSync(dest, { recursive: true });
864
- for (const entry of fs3.readdirSync(src, { withFileTypes: true })) {
865
- const srcPath = path3.join(src, entry.name);
866
- const destPath = path3.join(dest, entry.name);
1070
+ function copyDirSync2(src, dest) {
1071
+ fs4.mkdirSync(dest, { recursive: true });
1072
+ for (const entry of fs4.readdirSync(src, { withFileTypes: true })) {
1073
+ const srcPath = path4.join(src, entry.name);
1074
+ const destPath = path4.join(dest, entry.name);
867
1075
  if (entry.isDirectory()) {
868
- copyDirSync(srcPath, destPath);
1076
+ copyDirSync2(srcPath, destPath);
869
1077
  } else {
870
- fs3.copyFileSync(srcPath, destPath);
1078
+ fs4.copyFileSync(srcPath, destPath);
871
1079
  }
872
1080
  }
873
1081
  }
874
1082
  function installSkillForAgents(agentIds) {
875
1083
  const source = getSkillSourceDir();
876
- const canonical = getCanonicalSkillPath();
1084
+ const canonical = getCanonicalSkillPath2();
877
1085
  const installed = [];
878
1086
  const failed = [];
879
- if (!fs3.existsSync(path3.join(source, "SKILL.md"))) {
1087
+ if (!fs4.existsSync(path4.join(source, "SKILL.md"))) {
880
1088
  return { installed: [], failed: ["skill source not found"] };
881
1089
  }
882
1090
  try {
883
- if (fs3.existsSync(canonical)) {
884
- fs3.rmSync(canonical, { recursive: true });
1091
+ if (fs4.existsSync(canonical)) {
1092
+ fs4.rmSync(canonical, { recursive: true });
885
1093
  }
886
- copyDirSync(source, canonical);
1094
+ copyDirSync2(source, canonical);
1095
+ writeInstalledSkillVersion(getCurrentVersion());
887
1096
  } catch {
888
1097
  return { installed: [], failed: ["canonical copy"] };
889
1098
  }
@@ -897,15 +1106,15 @@ function installSkillForAgents(agentIds) {
897
1106
  continue;
898
1107
  }
899
1108
  try {
900
- const agentSkillsDir = path3.dirname(agentPath);
901
- fs3.mkdirSync(agentSkillsDir, { recursive: true });
1109
+ const agentSkillsDir = path4.dirname(agentPath);
1110
+ fs4.mkdirSync(agentSkillsDir, { recursive: true });
902
1111
  try {
903
- fs3.lstatSync(agentPath);
904
- fs3.rmSync(agentPath, { recursive: true });
1112
+ fs4.lstatSync(agentPath);
1113
+ fs4.rmSync(agentPath, { recursive: true });
905
1114
  } catch {
906
1115
  }
907
- const relative = path3.relative(agentSkillsDir, canonical);
908
- fs3.symlinkSync(relative, agentPath);
1116
+ const relative = path4.relative(agentSkillsDir, canonical);
1117
+ fs4.symlinkSync(relative, agentPath);
909
1118
  installed.push(agent.name);
910
1119
  seen.set(agentPath, true);
911
1120
  } catch {
@@ -1553,35 +1762,35 @@ import * as p6 from "@clack/prompts";
1553
1762
  import pc6 from "picocolors";
1554
1763
 
1555
1764
  // src/lib/cache.ts
1556
- import fs4 from "fs";
1557
- import path4 from "path";
1558
- import os4 from "os";
1559
- var CACHE_BASE = path4.join(os4.homedir(), ".one", "cache");
1560
- var KNOWLEDGE_DIR = path4.join(CACHE_BASE, "knowledge");
1561
- var SEARCH_DIR = path4.join(CACHE_BASE, "search");
1765
+ import fs5 from "fs";
1766
+ import path5 from "path";
1767
+ import os5 from "os";
1768
+ var CACHE_BASE = path5.join(os5.homedir(), ".one", "cache");
1769
+ var KNOWLEDGE_DIR = path5.join(CACHE_BASE, "knowledge");
1770
+ var SEARCH_DIR = path5.join(CACHE_BASE, "search");
1562
1771
  function sanitizeFilename(input) {
1563
1772
  return input.replace(/[^a-zA-Z0-9_\-\.]/g, "_");
1564
1773
  }
1565
1774
  function knowledgeCachePath(actionId) {
1566
- return path4.join(KNOWLEDGE_DIR, `${sanitizeFilename(actionId)}.json`);
1775
+ return path5.join(KNOWLEDGE_DIR, `${sanitizeFilename(actionId)}.json`);
1567
1776
  }
1568
1777
  function searchCachePath(platform, query, type) {
1569
1778
  const key = `${platform}_${sanitizeFilename(query)}_${type || "knowledge"}`;
1570
- return path4.join(SEARCH_DIR, `${key}.json`);
1779
+ return path5.join(SEARCH_DIR, `${key}.json`);
1571
1780
  }
1572
- function readCache(filePath) {
1781
+ function readCache2(filePath) {
1573
1782
  try {
1574
- const content = fs4.readFileSync(filePath, "utf-8");
1783
+ const content = fs5.readFileSync(filePath, "utf-8");
1575
1784
  return JSON.parse(content);
1576
1785
  } catch {
1577
1786
  return null;
1578
1787
  }
1579
1788
  }
1580
- function writeCache(filePath, entry) {
1789
+ function writeCache2(filePath, entry) {
1581
1790
  try {
1582
- const dir = path4.dirname(filePath);
1583
- fs4.mkdirSync(dir, { recursive: true });
1584
- fs4.writeFileSync(filePath, JSON.stringify(entry, null, 2));
1791
+ const dir = path5.dirname(filePath);
1792
+ fs5.mkdirSync(dir, { recursive: true });
1793
+ fs5.writeFileSync(filePath, JSON.stringify(entry, null, 2));
1585
1794
  } catch {
1586
1795
  }
1587
1796
  }
@@ -1619,11 +1828,11 @@ function listCacheEntries() {
1619
1828
  const entries = [];
1620
1829
  for (const [dir, type] of [[KNOWLEDGE_DIR, "knowledge"], [SEARCH_DIR, "search"]]) {
1621
1830
  try {
1622
- const files = fs4.readdirSync(dir);
1831
+ const files = fs5.readdirSync(dir);
1623
1832
  for (const file of files) {
1624
1833
  if (!file.endsWith(".json")) continue;
1625
- const filePath = path4.join(dir, file);
1626
- const entry = readCache(filePath);
1834
+ const filePath = path5.join(dir, file);
1835
+ const entry = readCache2(filePath);
1627
1836
  if (entry) {
1628
1837
  entries.push({ type, filePath, entry });
1629
1838
  }
@@ -1637,12 +1846,12 @@ function clearAll() {
1637
1846
  let count = 0;
1638
1847
  for (const dir of [KNOWLEDGE_DIR, SEARCH_DIR]) {
1639
1848
  try {
1640
- const files = fs4.readdirSync(dir);
1849
+ const files = fs5.readdirSync(dir);
1641
1850
  for (const file of files) {
1642
- fs4.unlinkSync(path4.join(dir, file));
1851
+ fs5.unlinkSync(path5.join(dir, file));
1643
1852
  count++;
1644
1853
  }
1645
- fs4.rmdirSync(dir);
1854
+ fs5.rmdirSync(dir);
1646
1855
  } catch {
1647
1856
  }
1648
1857
  }
@@ -1651,7 +1860,7 @@ function clearAll() {
1651
1860
  function clearEntry(actionId) {
1652
1861
  const filePath = knowledgeCachePath(actionId);
1653
1862
  try {
1654
- fs4.unlinkSync(filePath);
1863
+ fs5.unlinkSync(filePath);
1655
1864
  return true;
1656
1865
  } catch {
1657
1866
  return false;
@@ -1697,7 +1906,7 @@ async function actionsSearchCommand(platform, query, options) {
1697
1906
  const agentType = knowledgeAgent ? "knowledge" : options.type || "execute";
1698
1907
  const useCache = options.cache !== false;
1699
1908
  const cachePath = searchCachePath(platform, query, agentType || "knowledge");
1700
- const cached = useCache ? readCache(cachePath) : null;
1909
+ const cached = useCache ? readCache2(cachePath) : null;
1701
1910
  let cleanedActions;
1702
1911
  let cacheHit = false;
1703
1912
  if (cached && isFresh(cached)) {
@@ -1713,7 +1922,7 @@ async function actionsSearchCommand(platform, query, options) {
1713
1922
  );
1714
1923
  if (result.status === 304 && cached) {
1715
1924
  cached.cachedAt = (/* @__PURE__ */ new Date()).toISOString();
1716
- writeCache(cachePath, cached);
1925
+ writeCache2(cachePath, cached);
1717
1926
  cleanedActions = cached.data.actions;
1718
1927
  cacheHit = true;
1719
1928
  } else {
@@ -1726,7 +1935,7 @@ async function actionsSearchCommand(platform, query, options) {
1726
1935
  method: action.method,
1727
1936
  path: action.path
1728
1937
  }));
1729
- writeCache(cachePath, makeCacheEntry(
1938
+ writeCache2(cachePath, makeCacheEntry(
1730
1939
  `${platform}_${query}_${agentType || "knowledge"}`,
1731
1940
  { actions: cleanedActions },
1732
1941
  result.etag
@@ -1750,7 +1959,7 @@ async function actionsSearchCommand(platform, query, options) {
1750
1959
  if (cacheHit && cached) {
1751
1960
  response._cache = buildCacheMeta(cached, true);
1752
1961
  } else {
1753
- const freshEntry = readCache(cachePath);
1962
+ const freshEntry = readCache2(cachePath);
1754
1963
  response._cache = buildCacheMeta(freshEntry, false);
1755
1964
  }
1756
1965
  json(response);
@@ -1810,7 +2019,7 @@ Execute: ${pc6.cyan(`one actions execute ${platform} <actionId> <connectionK
1810
2019
  async function actionsKnowledgeCommand(platform, actionId, options) {
1811
2020
  const cachePath = knowledgeCachePath(actionId);
1812
2021
  if (options.cacheStatus) {
1813
- const entry = readCache(cachePath);
2022
+ const entry = readCache2(cachePath);
1814
2023
  if (!entry) {
1815
2024
  json({
1816
2025
  cached: false,
@@ -1858,7 +2067,7 @@ async function actionsKnowledgeCommand(platform, actionId, options) {
1858
2067
  spinner5.start(`Loading knowledge for action ${pc6.dim(actionId)}...`);
1859
2068
  try {
1860
2069
  const useCache = options.cache !== false;
1861
- const cached = useCache ? readCache(cachePath) : null;
2070
+ const cached = useCache ? readCache2(cachePath) : null;
1862
2071
  let knowledgeData;
1863
2072
  let cacheHit = false;
1864
2073
  let cacheEntry = cached;
@@ -1873,13 +2082,13 @@ async function actionsKnowledgeCommand(platform, actionId, options) {
1873
2082
  );
1874
2083
  if (result.status === 304 && cached) {
1875
2084
  cached.cachedAt = (/* @__PURE__ */ new Date()).toISOString();
1876
- writeCache(cachePath, cached);
2085
+ writeCache2(cachePath, cached);
1877
2086
  knowledgeData = cached.data;
1878
2087
  cacheHit = true;
1879
2088
  } else {
1880
2089
  knowledgeData = result.data;
1881
2090
  const newEntry = makeCacheEntry(actionId, knowledgeData, result.etag);
1882
- writeCache(cachePath, newEntry);
2091
+ writeCache2(cachePath, newEntry);
1883
2092
  cacheEntry = newEntry;
1884
2093
  }
1885
2094
  } catch (fetchError) {
@@ -2034,6 +2243,9 @@ function colorMethod(method) {
2034
2243
  import pc7 from "picocolors";
2035
2244
 
2036
2245
  // src/lib/flow-validator.ts
2246
+ import fs6 from "fs";
2247
+ import path6 from "path";
2248
+ import { spawnSync } from "child_process";
2037
2249
  function validateFlowSchema(flow2) {
2038
2250
  const errors = [];
2039
2251
  if (!flow2 || typeof flow2 !== "object") {
@@ -2092,26 +2304,26 @@ function validateStepsArray(steps, pathPrefix, errors) {
2092
2304
  const validTypes = FLOW_SCHEMA.stepTypes.map((st) => st.type);
2093
2305
  for (let i = 0; i < steps.length; i++) {
2094
2306
  const step = steps[i];
2095
- const path5 = `${pathPrefix}[${i}]`;
2307
+ const path8 = `${pathPrefix}[${i}]`;
2096
2308
  if (!step || typeof step !== "object" || Array.isArray(step)) {
2097
- errors.push({ path: path5, message: "Step must be an object" });
2309
+ errors.push({ path: path8, message: "Step must be an object" });
2098
2310
  continue;
2099
2311
  }
2100
2312
  const s = step;
2101
2313
  if (!s.id || typeof s.id !== "string") {
2102
- errors.push({ path: `${path5}.id`, message: 'Step must have a string "id"' });
2314
+ errors.push({ path: `${path8}.id`, message: 'Step must have a string "id"' });
2103
2315
  }
2104
2316
  if (!s.name || typeof s.name !== "string") {
2105
- errors.push({ path: `${path5}.name`, message: 'Step must have a string "name"' });
2317
+ errors.push({ path: `${path8}.name`, message: 'Step must have a string "name"' });
2106
2318
  }
2107
2319
  if (!s.type || !validTypes.includes(s.type)) {
2108
- errors.push({ path: `${path5}.type`, message: `Step type must be one of: ${validTypes.join(", ")}` });
2320
+ errors.push({ path: `${path8}.type`, message: `Step type must be one of: ${validTypes.join(", ")}` });
2109
2321
  continue;
2110
2322
  }
2111
2323
  if (s.onError && typeof s.onError === "object") {
2112
2324
  const oe = s.onError;
2113
2325
  if (!FLOW_SCHEMA.errorStrategies.includes(oe.strategy)) {
2114
- errors.push({ path: `${path5}.onError.strategy`, message: `Error strategy must be one of: ${FLOW_SCHEMA.errorStrategies.join(", ")}` });
2326
+ errors.push({ path: `${path8}.onError.strategy`, message: `Error strategy must be one of: ${FLOW_SCHEMA.errorStrategies.join(", ")}` });
2115
2327
  }
2116
2328
  }
2117
2329
  const descriptor = getStepTypeDescriptor(s.type);
@@ -2121,15 +2333,15 @@ function validateStepsArray(steps, pathPrefix, errors) {
2121
2333
  if (!configObj || typeof configObj !== "object") {
2122
2334
  const hint = detectFlatConfigHint(s, descriptor);
2123
2335
  errors.push({
2124
- path: `${path5}.${configKey}`,
2336
+ path: `${path8}.${configKey}`,
2125
2337
  message: `${capitalize(descriptor.type)} step must have a "${configKey}" config object${hint}`
2126
2338
  });
2127
2339
  continue;
2128
2340
  }
2129
- const config = configObj;
2341
+ const config2 = configObj;
2130
2342
  for (const [fieldName, fd] of Object.entries(descriptor.fields)) {
2131
- const fieldPath = `${path5}.${configKey}.${fieldName}`;
2132
- const value = config[fieldName];
2343
+ const fieldPath = `${path8}.${configKey}.${fieldName}`;
2344
+ const value = config2[fieldName];
2133
2345
  if (fd.required && (value === void 0 || value === null || value === "")) {
2134
2346
  errors.push({ path: fieldPath, message: `${capitalize(descriptor.type)} must have ${fd.type === "string" ? "a string" : fd.type === "array" ? "a" : "a"} "${fieldName}"` });
2135
2347
  continue;
@@ -2161,21 +2373,27 @@ function validateStepsArray(steps, pathPrefix, errors) {
2161
2373
  }
2162
2374
  }
2163
2375
  if (descriptor.type === "code") {
2164
- const hasSource = typeof config.source === "string" && config.source.length > 0;
2165
- const hasModule = typeof config.module === "string" && config.module.length > 0;
2376
+ const hasSource = typeof config2.source === "string" && config2.source.length > 0;
2377
+ const hasModule = typeof config2.module === "string" && config2.module.length > 0;
2166
2378
  if (!hasSource && !hasModule) {
2167
- errors.push({ path: `${path5}.${configKey}`, message: 'Code step must define either "source" (inline JS) or "module" (path to .mjs file)' });
2379
+ errors.push({ path: `${path8}.${configKey}`, message: 'Code step must define either "source" (inline JS) or "module" (path to .mjs file)' });
2168
2380
  } else if (hasSource && hasModule) {
2169
- errors.push({ path: `${path5}.${configKey}`, message: 'Code step cannot define both "source" and "module" \u2014 pick one' });
2381
+ errors.push({ path: `${path8}.${configKey}`, message: 'Code step cannot define both "source" and "module" \u2014 pick one' });
2170
2382
  }
2171
2383
  if (hasModule) {
2172
- const m = config.module;
2384
+ const m = config2.module;
2173
2385
  if (m.startsWith("/") || /^[a-zA-Z]:[\\/]/.test(m)) {
2174
- errors.push({ path: `${path5}.${configKey}.module`, message: "Code module path must be relative to the flow folder (no absolute paths)" });
2386
+ errors.push({ path: `${path8}.${configKey}.module`, message: "Code module path must be relative to the flow folder (no absolute paths)" });
2175
2387
  } else if (m.split(/[\\/]/).includes("..")) {
2176
- errors.push({ path: `${path5}.${configKey}.module`, message: 'Code module path must not escape the flow folder ("..")' });
2388
+ errors.push({ path: `${path8}.${configKey}.module`, message: 'Code module path must not escape the flow folder ("..")' });
2177
2389
  } else if (!m.endsWith(".mjs")) {
2178
- errors.push({ path: `${path5}.${configKey}.module`, message: "Code module must be a .mjs file" });
2390
+ errors.push({ path: `${path8}.${configKey}.module`, message: "Code module must be a .mjs file" });
2391
+ }
2392
+ }
2393
+ if (hasSource) {
2394
+ const syntaxError = checkCodeSourceSyntax(config2.source);
2395
+ if (syntaxError) {
2396
+ errors.push({ path: `${path8}.${configKey}.source`, message: `Syntax error in code step: ${syntaxError}` });
2179
2397
  }
2180
2398
  }
2181
2399
  }
@@ -2192,6 +2410,17 @@ function detectFlatConfigHint(step, descriptor) {
2192
2410
  function capitalize(s) {
2193
2411
  return s.charAt(0).toUpperCase() + s.slice(1);
2194
2412
  }
2413
+ var AsyncFunctionCtor = Object.getPrototypeOf(async function() {
2414
+ }).constructor;
2415
+ function checkCodeSourceSyntax(source) {
2416
+ try {
2417
+ new AsyncFunctionCtor("$", "require", source);
2418
+ return null;
2419
+ } catch (err) {
2420
+ if (err instanceof SyntaxError) return err.message;
2421
+ return err.message;
2422
+ }
2423
+ }
2195
2424
  function validateStepIds(flow2) {
2196
2425
  const errors = [];
2197
2426
  const seen = /* @__PURE__ */ new Set();
@@ -2199,16 +2428,16 @@ function validateStepIds(flow2) {
2199
2428
  function collectIds(steps, pathPrefix) {
2200
2429
  for (let i = 0; i < steps.length; i++) {
2201
2430
  const step = steps[i];
2202
- const path5 = `${pathPrefix}[${i}]`;
2431
+ const path8 = `${pathPrefix}[${i}]`;
2203
2432
  if (seen.has(step.id)) {
2204
- errors.push({ path: `${path5}.id`, message: `Duplicate step ID: "${step.id}"` });
2433
+ errors.push({ path: `${path8}.id`, message: `Duplicate step ID: "${step.id}"` });
2205
2434
  } else {
2206
2435
  seen.add(step.id);
2207
2436
  }
2208
2437
  for (const { configKey, fieldName } of nestedKeys) {
2209
- const config = step[configKey];
2210
- if (config && Array.isArray(config[fieldName])) {
2211
- collectIds(config[fieldName], `${path5}.${configKey}.${fieldName}`);
2438
+ const config2 = step[configKey];
2439
+ if (config2 && Array.isArray(config2[fieldName])) {
2440
+ collectIds(config2[fieldName], `${path8}.${configKey}.${fieldName}`);
2212
2441
  }
2213
2442
  }
2214
2443
  }
@@ -2225,9 +2454,9 @@ function validateSelectorReferences(flow2) {
2225
2454
  for (const step of steps) {
2226
2455
  ids.add(step.id);
2227
2456
  for (const { configKey, fieldName } of nestedKeys) {
2228
- const config = step[configKey];
2229
- if (config && Array.isArray(config[fieldName])) {
2230
- for (const id of getAllStepIds(config[fieldName])) ids.add(id);
2457
+ const config2 = step[configKey];
2458
+ if (config2 && Array.isArray(config2[fieldName])) {
2459
+ for (const id of getAllStepIds(config2[fieldName])) ids.add(id);
2231
2460
  }
2232
2461
  }
2233
2462
  }
@@ -2256,7 +2485,7 @@ function validateSelectorReferences(flow2) {
2256
2485
  }
2257
2486
  return selectors;
2258
2487
  }
2259
- function checkSelectors(selectors, path5) {
2488
+ function checkSelectors(selectors, path8, precedingStepIds) {
2260
2489
  for (const selector of selectors) {
2261
2490
  const parts = selector.split(".");
2262
2491
  if (parts.length < 3) continue;
@@ -2264,83 +2493,142 @@ function validateSelectorReferences(flow2) {
2264
2493
  if (root === "input") {
2265
2494
  const inputName = parts[2];
2266
2495
  if (!inputNames.has(inputName)) {
2267
- errors.push({ path: path5, message: `Selector "${selector}" references undefined input "${inputName}"` });
2496
+ errors.push({ path: path8, message: `Selector "${selector}" references undefined input "${inputName}"` });
2268
2497
  }
2269
2498
  } else if (root === "steps") {
2270
- const stepId = parts[2];
2499
+ const stepId = parts[2].replace(/[\[\]]/g, "").split(/[\[\]]/)[0];
2271
2500
  if (!allStepIds.has(stepId)) {
2272
- errors.push({ path: path5, message: `Selector "${selector}" references undefined step "${stepId}"` });
2501
+ errors.push({ path: path8, message: `Selector "${selector}" references undefined step "${stepId}"` });
2502
+ } else if (precedingStepIds && !precedingStepIds.has(stepId)) {
2503
+ errors.push({
2504
+ path: path8,
2505
+ message: `Selector "${selector}" references step "${stepId}" which is declared after the current step. Steps execute in declaration order, so this will always resolve to undefined at runtime \u2014 move the dependency earlier in the steps array.`
2506
+ });
2273
2507
  }
2274
2508
  }
2275
2509
  }
2276
2510
  }
2277
2511
  const EXPRESSION_FIELDS = /* @__PURE__ */ new Set(["condition.expression", "while.condition"]);
2278
- function checkOperatorsInSelectorField(value, path5) {
2512
+ function checkOperatorsInSelectorField(value, path8) {
2279
2513
  if (typeof value === "string" && value.startsWith("$.")) {
2280
2514
  if (value.includes("||")) {
2281
- errors.push({ path: path5, message: `Selector "${value}" contains unsupported operator "||". Selectors in data fields use dot-path resolution, not JS evaluation. Use the "default" field on the input definition instead, or use a "code" step for complex expressions.` });
2515
+ errors.push({ path: path8, message: `Selector "${value}" contains unsupported operator "||". Selectors in data fields use dot-path resolution, not JS evaluation. Use the "default" field on the input definition instead, or use a "code" step for complex expressions.` });
2282
2516
  } else if (value.includes("&&")) {
2283
- errors.push({ path: path5, message: `Selector "${value}" contains unsupported operator "&&". Selectors in data fields use dot-path resolution, not JS evaluation. Use a "condition" step or "code" step for complex expressions.` });
2517
+ errors.push({ path: path8, message: `Selector "${value}" contains unsupported operator "&&". Selectors in data fields use dot-path resolution, not JS evaluation. Use a "condition" step or "code" step for complex expressions.` });
2284
2518
  }
2285
2519
  } else if (value && typeof value === "object" && !Array.isArray(value)) {
2286
2520
  for (const [k, v] of Object.entries(value)) {
2287
- checkOperatorsInSelectorField(v, `${path5}.${k}`);
2521
+ checkOperatorsInSelectorField(v, `${path8}.${k}`);
2288
2522
  }
2289
2523
  } else if (Array.isArray(value)) {
2290
2524
  for (let i = 0; i < value.length; i++) {
2291
- checkOperatorsInSelectorField(value[i], `${path5}[${i}]`);
2525
+ checkOperatorsInSelectorField(value[i], `${path8}[${i}]`);
2292
2526
  }
2293
2527
  }
2294
2528
  }
2295
- function checkStep(step, pathPrefix) {
2296
- if (step.if) checkSelectors(extractSelectors(step.if), `${pathPrefix}.if`);
2297
- if (step.unless) checkSelectors(extractSelectors(step.unless), `${pathPrefix}.unless`);
2529
+ function checkStep(step, pathPrefix, preceding2) {
2530
+ if (step.if) checkSelectors(extractSelectors(step.if), `${pathPrefix}.if`, preceding2);
2531
+ if (step.unless) checkSelectors(extractSelectors(step.unless), `${pathPrefix}.unless`, preceding2);
2298
2532
  const descriptor = getStepTypeDescriptor(step.type);
2299
2533
  if (descriptor) {
2300
- const config = step[descriptor.configKey];
2301
- if (config && typeof config === "object") {
2534
+ const config2 = step[descriptor.configKey];
2535
+ if (config2 && typeof config2 === "object") {
2302
2536
  if (step.type !== "transform" && step.type !== "code") {
2303
2537
  for (const [fieldName, fd] of Object.entries(descriptor.fields)) {
2304
2538
  if (fd.stepsArray) continue;
2305
- const value = config[fieldName];
2539
+ const value = config2[fieldName];
2306
2540
  if (value !== void 0) {
2307
2541
  const fieldKey = `${descriptor.configKey}.${fieldName}`;
2308
2542
  const fieldPath = `${pathPrefix}.${fieldKey}`;
2309
- checkSelectors(extractSelectors(value), fieldPath);
2543
+ checkSelectors(extractSelectors(value), fieldPath, preceding2);
2310
2544
  if (!EXPRESSION_FIELDS.has(fieldKey)) {
2311
2545
  checkOperatorsInSelectorField(value, fieldPath);
2312
2546
  }
2313
2547
  }
2314
2548
  }
2549
+ } else {
2550
+ const c = config2;
2551
+ const codeSource = step.type === "code" ? c.source : void 0;
2552
+ const transformExpr = step.type === "transform" ? c.expression : void 0;
2553
+ const text4 = codeSource ?? transformExpr;
2554
+ if (typeof text4 === "string") {
2555
+ const fieldName = step.type === "code" ? "source" : "expression";
2556
+ checkSelectors(extractSelectors(text4), `${pathPrefix}.${descriptor.configKey}.${fieldName}`, preceding2);
2557
+ }
2315
2558
  }
2316
2559
  }
2317
2560
  for (const { configKey, fieldName } of nestedKeys) {
2318
2561
  if (configKey === descriptor.configKey) {
2319
2562
  const c = step[configKey];
2320
2563
  if (c && Array.isArray(c[fieldName])) {
2321
- c[fieldName].forEach(
2322
- (s, i) => checkStep(s, `${pathPrefix}.${configKey}.${fieldName}[${i}]`)
2323
- );
2564
+ const childPreceding = new Set(preceding2);
2565
+ c[fieldName].forEach((s, i) => {
2566
+ checkStep(s, `${pathPrefix}.${configKey}.${fieldName}[${i}]`, childPreceding);
2567
+ childPreceding.add(s.id);
2568
+ });
2324
2569
  }
2325
2570
  }
2326
2571
  }
2327
2572
  }
2328
2573
  }
2329
- flow2.steps.forEach((step, i) => checkStep(step, `steps[${i}]`));
2574
+ const preceding = /* @__PURE__ */ new Set();
2575
+ flow2.steps.forEach((step, i) => {
2576
+ checkStep(step, `steps[${i}]`, preceding);
2577
+ preceding.add(step.id);
2578
+ });
2330
2579
  return errors;
2331
2580
  }
2332
- function validateFlow(flow2) {
2581
+ function validateFlow(flow2, rootDir) {
2333
2582
  const schemaErrors = validateFlowSchema(flow2);
2334
2583
  if (schemaErrors.length > 0) return schemaErrors;
2335
2584
  const f = flow2;
2336
2585
  return [
2337
2586
  ...validateStepIds(f),
2338
- ...validateSelectorReferences(f)
2587
+ ...validateSelectorReferences(f),
2588
+ ...rootDir ? validateCodeModules(f, rootDir) : []
2339
2589
  ];
2340
2590
  }
2591
+ function validateCodeModules(flow2, rootDir) {
2592
+ const errors = [];
2593
+ const nestedKeys = getNestedStepsKeys();
2594
+ function walk(steps, pathPrefix) {
2595
+ for (let i = 0; i < steps.length; i++) {
2596
+ const step = steps[i];
2597
+ const stepPath = `${pathPrefix}[${i}]`;
2598
+ if (step.type === "code" && step.code?.module) {
2599
+ const m = step.code.module;
2600
+ const abs = path6.resolve(rootDir, m);
2601
+ if (!fs6.existsSync(abs)) {
2602
+ errors.push({
2603
+ path: `${stepPath}.code.module`,
2604
+ message: `Code module "${m}" not found at ${abs}`
2605
+ });
2606
+ } else {
2607
+ const res = spawnSync(process.execPath, ["--check", abs], { encoding: "utf-8" });
2608
+ if (res.status !== 0) {
2609
+ const msg = (res.stderr || "").split("\n").find((l) => l.includes("SyntaxError") || l.includes("Error")) || res.stderr || "Syntax check failed";
2610
+ errors.push({
2611
+ path: `${stepPath}.code.module`,
2612
+ message: `Syntax error in code module "${m}": ${msg.trim()}`
2613
+ });
2614
+ }
2615
+ }
2616
+ }
2617
+ for (const { configKey, fieldName } of nestedKeys) {
2618
+ const config2 = step[configKey];
2619
+ if (config2 && Array.isArray(config2[fieldName])) {
2620
+ walk(config2[fieldName], `${stepPath}.${configKey}.${fieldName}`);
2621
+ }
2622
+ }
2623
+ }
2624
+ }
2625
+ walk(flow2.steps, "steps");
2626
+ return errors;
2627
+ }
2341
2628
 
2342
2629
  // src/commands/flow.ts
2343
- import fs5 from "fs";
2630
+ import fs7 from "fs";
2631
+ import path7 from "path";
2344
2632
  function getConfig2() {
2345
2633
  const apiKey = getApiKey();
2346
2634
  if (!apiKey) {
@@ -2398,7 +2686,7 @@ async function flowCreateCommand(key, options) {
2398
2686
  if (raw.startsWith("@")) {
2399
2687
  const filePath = raw.slice(1);
2400
2688
  try {
2401
- raw = fs5.readFileSync(filePath, "utf-8");
2689
+ raw = fs7.readFileSync(filePath, "utf-8");
2402
2690
  } catch (err) {
2403
2691
  error(`Cannot read file "${filePath}": ${err.message}`);
2404
2692
  }
@@ -2463,6 +2751,15 @@ async function flowExecuteCommand(keyOrPath, options) {
2463
2751
  return;
2464
2752
  }
2465
2753
  spinner5.stop(`Workflow: ${flow2.name} (${flow2.steps.length} steps)`);
2754
+ const preflightErrors = validateFlow(flow2, rootDir);
2755
+ if (preflightErrors.length > 0) {
2756
+ if (isAgentMode()) {
2757
+ json({ error: "Validation failed", errors: preflightErrors });
2758
+ process.exit(1);
2759
+ }
2760
+ error(`Validation failed:
2761
+ ${preflightErrors.map((e) => ` ${e.path}: ${e.message}`).join("\n")}`);
2762
+ }
2466
2763
  if (flowFilePath.endsWith(".flow.json")) {
2467
2764
  const msg = `Workflow "${flow2.key}" uses the deprecated single-file layout. Migrate to .one/flows/${flow2.key}/flow.json (see: one guide flows).`;
2468
2765
  if (isAgentMode()) {
@@ -2603,15 +2900,23 @@ async function flowValidateCommand(keyOrPath) {
2603
2900
  const spinner5 = createSpinner();
2604
2901
  spinner5.start(`Validating "${keyOrPath}"...`);
2605
2902
  let flowData;
2903
+ let rootDir;
2606
2904
  try {
2607
- const flowPath = resolveFlowPath(keyOrPath);
2608
- const content = fs5.readFileSync(flowPath, "utf-8");
2609
- flowData = JSON.parse(content);
2905
+ try {
2906
+ const loaded = loadFlowWithMeta(keyOrPath);
2907
+ flowData = loaded.flow;
2908
+ rootDir = loaded.rootDir;
2909
+ } catch {
2910
+ const flowPath = resolveFlowPath(keyOrPath);
2911
+ const content = fs7.readFileSync(flowPath, "utf-8");
2912
+ flowData = JSON.parse(content);
2913
+ rootDir = path7.dirname(flowPath);
2914
+ }
2610
2915
  } catch (err) {
2611
2916
  spinner5.stop("Validation failed");
2612
2917
  error(`Could not read workflow: ${err instanceof Error ? err.message : String(err)}`);
2613
2918
  }
2614
- const errors = validateFlow(flowData);
2919
+ const errors = validateFlow(flowData, rootDir);
2615
2920
  if (errors.length > 0) {
2616
2921
  spinner5.stop("Validation failed");
2617
2922
  if (isAgentMode()) {
@@ -3363,11 +3668,11 @@ async function cacheUpdateAllCommand() {
3363
3668
  if (e.type === "knowledge") {
3364
3669
  const result = await api.getActionKnowledgeWithMeta(e.entry.key);
3365
3670
  const newEntry = makeCacheEntry(e.entry.key, result.data, result.etag);
3366
- writeCache(e.filePath, newEntry);
3671
+ writeCache2(e.filePath, newEntry);
3367
3672
  updated++;
3368
3673
  } else {
3369
3674
  const refreshed = { ...e.entry, cachedAt: (/* @__PURE__ */ new Date()).toISOString() };
3370
- writeCache(e.filePath, refreshed);
3675
+ writeCache2(e.filePath, refreshed);
3371
3676
  updated++;
3372
3677
  }
3373
3678
  } catch (err) {
@@ -3495,6 +3800,7 @@ Request specific sections:
3495
3800
  - Always use the **exact action ID** from search results \u2014 don't guess
3496
3801
  - Always read **knowledge** before executing any action
3497
3802
  - Connection keys come from \`one connection list\` \u2014 don't hardcode them
3803
+ - Skills stay in lockstep with the CLI version automatically \u2014 every command checks a \`.one-cli-version\` marker in the canonical skill dir and refreshes the files if the CLI has been upgraded. Check manually with \`one config skills status\`; force a resync with \`one config skills sync\`
3498
3804
  `;
3499
3805
  var GUIDE_ACTIONS = `# One Actions \u2014 Reference
3500
3806
 
@@ -4098,118 +4404,6 @@ function buildWorkflowIdeas(connections) {
4098
4404
  return lines.join("\n");
4099
4405
  }
4100
4406
 
4101
- // src/commands/update.ts
4102
- import { createRequire } from "module";
4103
- import { spawn } from "child_process";
4104
- import { readFileSync, writeFileSync, mkdirSync } from "fs";
4105
- import { homedir } from "os";
4106
- import { join } from "path";
4107
- var require2 = createRequire(import.meta.url);
4108
- var { version: currentVersion } = require2("../package.json");
4109
- var CACHE_PATH = join(homedir(), ".one", "update-check.json");
4110
- var CHECK_INTERVAL_MS = 4 * 60 * 60 * 1e3;
4111
- var AGE_GATE_MS = 30 * 60 * 1e3;
4112
- async function fetchLatestVersionInfo() {
4113
- try {
4114
- const res = await fetch("https://registry.npmjs.org/@withone/cli");
4115
- if (!res.ok) return null;
4116
- const data = await res.json();
4117
- const latest = data["dist-tags"]?.latest;
4118
- if (!latest) return null;
4119
- return { version: latest, publishedAt: data.time?.[latest] ?? null };
4120
- } catch {
4121
- return null;
4122
- }
4123
- }
4124
- function readCache3() {
4125
- try {
4126
- return JSON.parse(readFileSync(CACHE_PATH, "utf8"));
4127
- } catch {
4128
- return null;
4129
- }
4130
- }
4131
- function writeCache2(latestVersion, publishedAt) {
4132
- try {
4133
- mkdirSync(join(homedir(), ".one"), { recursive: true });
4134
- writeFileSync(CACHE_PATH, JSON.stringify({ lastCheck: Date.now(), latestVersion, publishedAt }));
4135
- } catch {
4136
- }
4137
- }
4138
- async function checkLatestVersion() {
4139
- const info = await fetchLatestVersionInfo();
4140
- if (info) writeCache2(info.version, info.publishedAt);
4141
- return info?.version ?? null;
4142
- }
4143
- async function checkLatestVersionCached() {
4144
- const cache2 = readCache3();
4145
- if (cache2 && Date.now() - cache2.lastCheck < CHECK_INTERVAL_MS) {
4146
- return { version: cache2.latestVersion, publishedAt: cache2.publishedAt ?? null };
4147
- }
4148
- const info = await fetchLatestVersionInfo();
4149
- if (info) writeCache2(info.version, info.publishedAt);
4150
- return info;
4151
- }
4152
- function getCurrentVersion() {
4153
- return currentVersion;
4154
- }
4155
- async function updateCommand() {
4156
- const s = createSpinner();
4157
- s.start("Checking for updates...");
4158
- const latestVersion = await checkLatestVersion();
4159
- if (!latestVersion) {
4160
- s.stop("");
4161
- error("Failed to check for updates \u2014 could not reach npm registry");
4162
- }
4163
- if (currentVersion === latestVersion) {
4164
- s.stop("Already up to date");
4165
- if (isAgentMode()) {
4166
- json({ current: currentVersion, latest: latestVersion, updated: false, message: "Already up to date" });
4167
- } else {
4168
- console.log(`Already up to date (v${currentVersion})`);
4169
- }
4170
- return;
4171
- }
4172
- s.stop(`Update available: v${currentVersion} \u2192 v${latestVersion}`);
4173
- console.log(`Updating @withone/cli: v${currentVersion} \u2192 v${latestVersion}...`);
4174
- const code = await new Promise((resolve) => {
4175
- const child = spawn("npm", ["install", "-g", "@withone/cli@latest", "--force"], {
4176
- stdio: isAgentMode() ? "pipe" : "inherit",
4177
- shell: true
4178
- });
4179
- child.on("close", resolve);
4180
- child.on("error", () => resolve(1));
4181
- });
4182
- if (code === 0) {
4183
- if (isAgentMode()) {
4184
- json({ current: currentVersion, latest: latestVersion, updated: true, message: "Updated successfully" });
4185
- } else {
4186
- console.log(`Successfully updated to v${latestVersion}`);
4187
- }
4188
- } else {
4189
- error("Update failed \u2014 try running: npm install -g @withone/cli@latest");
4190
- }
4191
- }
4192
- function isNewerVersion(latest, current) {
4193
- const parse = (v) => v.split(".").map(Number);
4194
- const [lMaj, lMin, lPat] = parse(latest);
4195
- const [cMaj, cMin, cPat] = parse(current);
4196
- if (lMaj !== cMaj) return lMaj > cMaj;
4197
- if (lMin !== cMin) return lMin > cMin;
4198
- return lPat > cPat;
4199
- }
4200
- function autoUpdate(targetVersion, publishedAt) {
4201
- if (publishedAt) {
4202
- const age = Date.now() - new Date(publishedAt).getTime();
4203
- if (age < AGE_GATE_MS) return;
4204
- }
4205
- const child = spawn("npm", ["install", "-g", `@withone/cli@${targetVersion}`], {
4206
- detached: true,
4207
- stdio: "ignore",
4208
- shell: true
4209
- });
4210
- child.unref();
4211
- }
4212
-
4213
4407
  // src/index.ts
4214
4408
  var require3 = createRequire2(import.meta.url);
4215
4409
  var { version } = require3("../package.json");
@@ -4275,6 +4469,12 @@ program.hook("preAction", (thisCommand) => {
4275
4469
  if (commandName !== "update") {
4276
4470
  updateCheckPromise = checkLatestVersionCached();
4277
4471
  }
4472
+ if (commandName !== "init" && commandName !== "update") {
4473
+ try {
4474
+ syncSkillsIfStale();
4475
+ } catch {
4476
+ }
4477
+ }
4278
4478
  });
4279
4479
  program.hook("postAction", async () => {
4280
4480
  if (!updateCheckPromise) return;
@@ -4287,9 +4487,48 @@ program.hook("postAction", async () => {
4287
4487
  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) => {
4288
4488
  await initCommand(options);
4289
4489
  });
4290
- program.command("config").description("Configure MCP access control (permissions, connections, actions)").action(async () => {
4490
+ var config = program.command("config").description("Configure the CLI (access control, skills, ...)").action(async () => {
4291
4491
  await configCommand();
4292
4492
  });
4493
+ var configSkills = config.command("skills").description("Manage locally-installed skill files");
4494
+ configSkills.command("sync").description("Re-copy packaged skill files over the local install (runs automatically after CLI upgrades)").action(async () => {
4495
+ const result = forceSyncSkills();
4496
+ if (isAgentMode()) {
4497
+ json({ command: "config skills sync", ...result });
4498
+ return;
4499
+ }
4500
+ if (result.reason === "not-installed") {
4501
+ console.log("No skill is installed yet. Run 'one init' first and opt in to skill installation.");
4502
+ return;
4503
+ }
4504
+ if (result.synced) {
4505
+ console.log(`\u2713 Skills synced to v${result.to}`);
4506
+ return;
4507
+ }
4508
+ if (result.reason === "source-missing") {
4509
+ console.log("\u2717 Packaged skill source not found in this CLI build");
4510
+ return;
4511
+ }
4512
+ console.log(`\u2717 Sync failed${result.error ? ": " + result.error : ""}`);
4513
+ });
4514
+ configSkills.command("status").description("Show installed skill version and whether it matches the current CLI").action(async () => {
4515
+ const status = getSkillStatus();
4516
+ if (isAgentMode()) {
4517
+ json({ command: "config skills status", ...status });
4518
+ return;
4519
+ }
4520
+ if (!status.installed) {
4521
+ console.log("Skill is not installed. Run 'one init' to install it.");
4522
+ console.log(`Canonical path (empty): ${status.canonicalPath}`);
4523
+ return;
4524
+ }
4525
+ const marker = status.installedVersion ?? "(no marker \u2014 pre-sync install)";
4526
+ const state = status.upToDate ? "\u2713 up to date" : "\u26A0 stale \u2014 will sync on next command";
4527
+ console.log(`Skill: ${state}`);
4528
+ console.log(` installed: ${marker}`);
4529
+ console.log(` current: ${status.currentVersion}`);
4530
+ console.log(` path: ${status.canonicalPath}`);
4531
+ });
4293
4532
  var connection = program.command("connection").description("Manage connections");
4294
4533
  connection.command("add [platform]").alias("a").description("Add a new connection").action(async (platform) => {
4295
4534
  await connectionAddCommand(platform);