@withone/cli 1.33.0 → 1.35.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
@@ -19,7 +19,7 @@ import {
19
19
  resolveFlowPath,
20
20
  saveFlow,
21
21
  validateActionInput
22
- } from "./chunk-T7LTS2IE.js";
22
+ } from "./chunk-HZP7NT4K.js";
23
23
 
24
24
  // src/index.ts
25
25
  import { createRequire as createRequire2 } from "module";
@@ -27,7 +27,7 @@ import path16 from "path";
27
27
  import { Command } from "commander";
28
28
 
29
29
  // src/commands/init.ts
30
- import * as p3 from "@clack/prompts";
30
+ import * as p4 from "@clack/prompts";
31
31
  import pc2 from "picocolors";
32
32
  import fs4 from "fs";
33
33
  import path4 from "path";
@@ -265,11 +265,11 @@ import fs2 from "fs";
265
265
  import path2 from "path";
266
266
  import os2 from "os";
267
267
  import { parse as parseToml, stringify as stringifyToml } from "smol-toml";
268
- function expandPath(p8) {
269
- if (p8.startsWith("~/")) {
270
- return path2.join(os2.homedir(), p8.slice(2));
268
+ function expandPath(p10) {
269
+ if (p10.startsWith("~/")) {
270
+ return path2.join(os2.homedir(), p10.slice(2));
271
271
  }
272
- return p8;
272
+ return p10;
273
273
  }
274
274
  function getClaudeDesktopConfigPath() {
275
275
  switch (process.platform) {
@@ -459,6 +459,13 @@ async function openConnectionPage(platform, params) {
459
459
  async function openApiKeyPage() {
460
460
  await open(getApiKeyUrl());
461
461
  }
462
+ function getCliAuthUrl(port, state) {
463
+ return `${ONE_APP_URL}/cli/auth?port=${port}&state=${encodeURIComponent(state)}`;
464
+ }
465
+ async function openCliAuthPage(port, state) {
466
+ const url = getCliAuthUrl(port, state);
467
+ await open(url);
468
+ }
462
469
 
463
470
  // src/commands/config.ts
464
471
  import * as p2 from "@clack/prompts";
@@ -649,24 +656,24 @@ async function configCommand() {
649
656
  p2.outro("No changes made.");
650
657
  return;
651
658
  }
652
- const spinner5 = p2.spinner();
653
- spinner5.start("Validating API key...");
659
+ const spinner6 = p2.spinner();
660
+ spinner6.start("Validating API key...");
654
661
  let isValid = false;
655
662
  try {
656
663
  const api = new OneApi(apiKey, `${normalized}/v1`);
657
664
  isValid = await api.validateApiKey();
658
665
  } catch (err) {
659
- spinner5.stop("Connection failed");
666
+ spinner6.stop("Connection failed");
660
667
  const msg = err instanceof Error ? err.message : String(err);
661
668
  p2.log.error(`Could not reach ${pc.cyan(normalized)}: ${msg}`);
662
669
  return;
663
670
  }
664
671
  if (!isValid) {
665
- spinner5.stop("Invalid API key");
672
+ spinner6.stop("Invalid API key");
666
673
  p2.log.error(`Invalid API key for ${pc.cyan(normalized)}.`);
667
674
  return;
668
675
  }
669
- spinner5.stop("API key validated");
676
+ spinner6.stop("API key validated");
670
677
  updateApiBase(normalized);
671
678
  newApiKey = apiKey;
672
679
  } else if (isCustomBase) {
@@ -685,24 +692,24 @@ async function configCommand() {
685
692
  p2.outro("No changes made.");
686
693
  return;
687
694
  }
688
- const spinner5 = p2.spinner();
689
- spinner5.start("Validating API key...");
695
+ const spinner6 = p2.spinner();
696
+ spinner6.start("Validating API key...");
690
697
  let isValid = false;
691
698
  try {
692
699
  const api = new OneApi(apiKey, "https://api.withone.ai/v1");
693
700
  isValid = await api.validateApiKey();
694
701
  } catch (err) {
695
- spinner5.stop("Connection failed");
702
+ spinner6.stop("Connection failed");
696
703
  const msg = err instanceof Error ? err.message : String(err);
697
704
  p2.log.error(`Could not reach ${pc.cyan("https://api.withone.ai")}: ${msg}`);
698
705
  return;
699
706
  }
700
707
  if (!isValid) {
701
- spinner5.stop("Invalid API key");
708
+ spinner6.stop("Invalid API key");
702
709
  p2.log.error(`Invalid API key. Get a valid key at ${getApiKeyUrl()}`);
703
710
  return;
704
711
  }
705
- spinner5.stop("API key validated");
712
+ spinner6.stop("API key validated");
706
713
  updateApiBase(null);
707
714
  newApiKey = apiKey;
708
715
  }
@@ -738,16 +745,16 @@ async function configCommand() {
738
745
  p2.outro("Configuration updated.");
739
746
  }
740
747
  async function selectConnections(apiKey) {
741
- const spinner5 = p2.spinner();
742
- spinner5.start("Fetching connections...");
748
+ const spinner6 = p2.spinner();
749
+ spinner6.start("Fetching connections...");
743
750
  let connections;
744
751
  try {
745
752
  const api = new OneApi(apiKey, getApiBase());
746
753
  const rawConnections = await api.listConnections();
747
754
  connections = rawConnections.map((c) => ({ platform: c.platform, key: c.key }));
748
- spinner5.stop(`Found ${connections.length} connection(s)`);
755
+ spinner6.stop(`Found ${connections.length} connection(s)`);
749
756
  } catch {
750
- spinner5.stop("Could not fetch connections");
757
+ spinner6.stop("Could not fetch connections");
751
758
  const manual = await p2.text({
752
759
  message: "Enter connection keys manually (comma-separated):",
753
760
  placeholder: "conn_key_1, conn_key_2",
@@ -996,6 +1003,224 @@ function getSkillStatus() {
996
1003
  };
997
1004
  }
998
1005
 
1006
+ // src/commands/login.ts
1007
+ import http from "http";
1008
+ import crypto from "crypto";
1009
+ import * as p3 from "@clack/prompts";
1010
+ var LOGIN_TIMEOUT_MS = 5 * 60 * 1e3;
1011
+ var PORT_RANGE_START = 49152;
1012
+ var PORT_RANGE_END = 65535;
1013
+ var MAX_PORT_ATTEMPTS = 5;
1014
+ var SUCCESS_HTML = `<!DOCTYPE html>
1015
+ <html><head><title>One CLI</title></head>
1016
+ <body style="font-family:system-ui;display:flex;justify-content:center;align-items:center;height:100vh;margin:0;background:#0a0a0a;color:#fafafa">
1017
+ <div style="text-align:center">
1018
+ <div style="width:48px;height:48px;border-radius:50%;background:rgba(34,197,94,0.1);display:flex;align-items:center;justify-content:center;margin:0 auto 16px">
1019
+ <svg width="24" height="24" fill="none" stroke="#22c55e" stroke-width="2" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M5 13l4 4L19 7"/></svg>
1020
+ </div>
1021
+ <h1 style="font-size:20px;margin:0 0 8px">You're all set!</h1>
1022
+ <p style="color:#a1a1aa;font-size:14px">Return to your terminal. You can close this tab.</p>
1023
+ </div></body></html>`;
1024
+ function randomPort() {
1025
+ return PORT_RANGE_START + Math.floor(Math.random() * (PORT_RANGE_END - PORT_RANGE_START));
1026
+ }
1027
+ function startCallbackServer(expectedState) {
1028
+ return new Promise((resolveSetup, rejectSetup) => {
1029
+ let attempts = 0;
1030
+ function tryListen() {
1031
+ const port = randomPort();
1032
+ attempts++;
1033
+ let resolveResult;
1034
+ const result = new Promise((res) => {
1035
+ resolveResult = res;
1036
+ });
1037
+ const server = http.createServer((req, res) => {
1038
+ const url = new URL(req.url || "/", `http://localhost:${port}`);
1039
+ if (url.pathname !== "/callback") {
1040
+ res.writeHead(404, { "Content-Type": "text/plain" });
1041
+ res.end("Not found");
1042
+ return;
1043
+ }
1044
+ const encodedKey = url.searchParams.get("s");
1045
+ const state = url.searchParams.get("state");
1046
+ const apiKey = encodedKey ? Buffer.from(encodedKey, "base64").toString("utf-8") : null;
1047
+ if (!apiKey || !state) {
1048
+ res.writeHead(400, { "Content-Type": "text/plain" });
1049
+ res.end("Missing required parameters");
1050
+ return;
1051
+ }
1052
+ if (state !== expectedState) {
1053
+ res.writeHead(403, { "Content-Type": "text/plain" });
1054
+ res.end("State mismatch");
1055
+ return;
1056
+ }
1057
+ res.writeHead(200, { "Content-Type": "text/html" });
1058
+ res.end(SUCCESS_HTML);
1059
+ resolveResult({ apiKey, state });
1060
+ });
1061
+ server.on("error", (err) => {
1062
+ if (err.code === "EADDRINUSE" && attempts < MAX_PORT_ATTEMPTS) {
1063
+ tryListen();
1064
+ return;
1065
+ }
1066
+ rejectSetup(err);
1067
+ });
1068
+ server.listen(port, "127.0.0.1", () => {
1069
+ resolveSetup({ server, port, result });
1070
+ });
1071
+ }
1072
+ tryListen();
1073
+ });
1074
+ }
1075
+ async function browserLogin() {
1076
+ const state = crypto.randomUUID();
1077
+ const spin = p3.spinner();
1078
+ let server;
1079
+ let port;
1080
+ let resultPromise;
1081
+ try {
1082
+ ({ server, port, result: resultPromise } = await startCallbackServer(state));
1083
+ } catch {
1084
+ error("Could not start local server. Try: one init");
1085
+ return null;
1086
+ }
1087
+ const authUrl = getCliAuthUrl(port, state);
1088
+ p3.note(
1089
+ `If the browser doesn't open, visit:
1090
+ ${authUrl}`,
1091
+ "Opening browser for authentication..."
1092
+ );
1093
+ try {
1094
+ await openCliAuthPage(port, state);
1095
+ } catch {
1096
+ }
1097
+ spin.start("Waiting for authentication... (timeout: 5 min)");
1098
+ const timeout = new Promise((_, reject) => {
1099
+ const timer = setTimeout(() => {
1100
+ reject(new Error("timeout"));
1101
+ }, LOGIN_TIMEOUT_MS);
1102
+ timer.unref();
1103
+ });
1104
+ try {
1105
+ const payload = await Promise.race([resultPromise, timeout]);
1106
+ spin.stop("Authentication received!");
1107
+ const apiBase = getApiBase();
1108
+ const api = new OneApi(payload.apiKey, apiBase);
1109
+ const whoami = await api.whoami();
1110
+ return { apiKey: payload.apiKey, whoami };
1111
+ } catch (err) {
1112
+ spin.stop("Authentication failed.");
1113
+ if (err instanceof Error && err.message === "timeout") {
1114
+ error("Authentication timed out (5 min). Try again with: one login");
1115
+ } else if (err instanceof ApiError) {
1116
+ error(`Authentication failed: ${err.message}`);
1117
+ } else {
1118
+ error("Authentication failed. Try: one init");
1119
+ }
1120
+ return null;
1121
+ } finally {
1122
+ server.closeAllConnections();
1123
+ server.close();
1124
+ }
1125
+ }
1126
+ function saveCredentials(apiKey, scope) {
1127
+ const existing = scope === "project" ? readProjectConfig() : readGlobalConfig();
1128
+ writeConfig({
1129
+ apiKey,
1130
+ installedAgents: existing?.installedAgents ?? [],
1131
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
1132
+ accessControl: existing?.accessControl,
1133
+ cacheTtl: existing?.cacheTtl,
1134
+ apiBase: existing?.apiBase
1135
+ }, scope);
1136
+ }
1137
+ async function loginCommand() {
1138
+ if (isAgentMode()) {
1139
+ json({ error: "Browser login not available in agent mode. Use: one init" });
1140
+ return;
1141
+ }
1142
+ let targetScope = "global";
1143
+ const existingKey = getApiKey();
1144
+ if (existingKey) {
1145
+ const pc13 = (await import("picocolors")).default;
1146
+ const resolved2 = resolveConfig();
1147
+ const whoami2 = resolved2.config?.whoami;
1148
+ const env2 = getEnvFromApiKey(existingKey);
1149
+ const envLabel2 = env2 === "test" ? pc13.yellow("test") : pc13.green("live");
1150
+ const currentScope = resolved2.scope === "project" ? pc13.cyan("local config") : pc13.magenta("global config");
1151
+ const lines = ["You are already logged in.", ""];
1152
+ if (whoami2) {
1153
+ const contextParts2 = [];
1154
+ if (whoami2.organization) contextParts2.push(whoami2.organization.name);
1155
+ if (whoami2.project) contextParts2.push(whoami2.project.name);
1156
+ const scopeDisplay2 = contextParts2.length > 0 ? contextParts2.join(" / ") : "Personal";
1157
+ lines.push(`${pc13.bold(scopeDisplay2)} ${pc13.dim("\xB7")} ${envLabel2}`);
1158
+ lines.push(`${whoami2.user.name} ${pc13.dim(`(${whoami2.user.email})`)}`);
1159
+ if (whoami2.organization) lines.push(`${pc13.dim("Org:")} ${whoami2.organization.name}`);
1160
+ if (whoami2.project) lines.push(`${pc13.dim("Project:")} ${whoami2.project.name}`);
1161
+ }
1162
+ lines.push("");
1163
+ lines.push(`${pc13.dim("Stored in")} ${currentScope}`);
1164
+ p3.note(lines.join("\n"));
1165
+ const scopeChoice = await p3.select({
1166
+ message: "Where would you like to log in?",
1167
+ options: [
1168
+ { value: "global", label: "Globally", hint: "applies everywhere" },
1169
+ { value: "project", label: "This directory", hint: "only this project" }
1170
+ ]
1171
+ });
1172
+ if (p3.isCancel(scopeChoice)) {
1173
+ p3.cancel("Login cancelled.");
1174
+ return;
1175
+ }
1176
+ targetScope = scopeChoice;
1177
+ }
1178
+ const result = await browserLogin();
1179
+ if (!result) return;
1180
+ const { apiKey, whoami } = result;
1181
+ saveCredentials(apiKey, targetScope);
1182
+ const resolved = resolveConfig();
1183
+ if (resolved.config) {
1184
+ writeConfig({ ...resolved.config, whoami }, targetScope);
1185
+ }
1186
+ const pc12 = (await import("picocolors")).default;
1187
+ const env = getEnvFromApiKey(apiKey);
1188
+ const contextParts = [];
1189
+ if (whoami.organization) contextParts.push(whoami.organization.name);
1190
+ if (whoami.project) contextParts.push(whoami.project.name);
1191
+ const scopeDisplay = contextParts.length > 0 ? contextParts.join(" / ") : "Personal";
1192
+ const envLabel = env === "test" ? pc12.yellow("test") : pc12.green("live");
1193
+ const configLabel = targetScope === "project" ? pc12.cyan("local config") : pc12.magenta("global config");
1194
+ const infoLines = [
1195
+ `${pc12.bold(scopeDisplay)} ${pc12.dim("\xB7")} ${envLabel}`,
1196
+ `${whoami.user.name} ${pc12.dim(`(${whoami.user.email})`)}`
1197
+ ];
1198
+ if (whoami.organization) infoLines.push(`${pc12.dim("Org:")} ${whoami.organization.name}`);
1199
+ if (whoami.project) infoLines.push(`${pc12.dim("Project:")} ${whoami.project.name}`);
1200
+ infoLines.push("");
1201
+ infoLines.push(`${pc12.dim("Stored in")} ${configLabel}`);
1202
+ p3.note(infoLines.join("\n"), "Logged in");
1203
+ console.log();
1204
+ console.log(` ${pc12.dim("Next steps:")}`);
1205
+ console.log(` ${pc12.cyan("one add <platform>")} ${pc12.dim("\u2014")} Connect a platform (e.g. gmail, slack, stripe)`);
1206
+ console.log();
1207
+ console.log(pc12.bold(" Copy-paste this to your AI agent:"));
1208
+ console.log();
1209
+ console.log(pc12.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"));
1210
+ console.log(pc12.cyan(" \u2502"));
1211
+ console.log(pc12.cyan(" \u2502 The One CLI (`one`) is now installed on this machine. It"));
1212
+ console.log(pc12.cyan(" \u2502 lets you interact with 250+ third-party platforms (Gmail,"));
1213
+ console.log(pc12.cyan(" \u2502 Slack, Shopify, HubSpot, Stripe, GitHub, etc.) through"));
1214
+ console.log(pc12.cyan(" \u2502 their APIs. Whenever you need to interact with any"));
1215
+ console.log(pc12.cyan(" \u2502 third-party platform or external service, use the `one`"));
1216
+ console.log(pc12.cyan(" \u2502 skill. Please save this to your project instructions or"));
1217
+ console.log(pc12.cyan(" \u2502 memory so you remember it in future conversations."));
1218
+ console.log(pc12.cyan(" \u2502"));
1219
+ console.log(pc12.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"));
1220
+ console.log();
1221
+ p3.outro("Happy building!");
1222
+ }
1223
+
999
1224
  // src/commands/init.ts
1000
1225
  async function initCommand(options) {
1001
1226
  if (isAgentMode()) {
@@ -1004,7 +1229,7 @@ async function initCommand(options) {
1004
1229
  printBanner();
1005
1230
  const scope = await chooseConfigScope(options);
1006
1231
  if (scope === null) {
1007
- p3.cancel("Setup cancelled.");
1232
+ p4.cancel("Setup cancelled.");
1008
1233
  return;
1009
1234
  }
1010
1235
  const existingConfig = scope === "project" ? readProjectConfig() : readGlobalConfig();
@@ -1030,7 +1255,7 @@ async function chooseConfigScope(options) {
1030
1255
  console.log(` ${pc2.bold("Active config:")} ${pc2.cyan("project")} ${pc2.dim("\xB7 " + homeProject)}`);
1031
1256
  console.log();
1032
1257
  if (hasGlobal) {
1033
- const which2 = await p3.select({
1258
+ const which2 = await p4.select({
1034
1259
  message: "Which config do you want to edit?",
1035
1260
  options: [
1036
1261
  { value: "project", label: `This project (${projectName})`, hint: homeProject },
@@ -1038,7 +1263,7 @@ async function chooseConfigScope(options) {
1038
1263
  ],
1039
1264
  initialValue: "project"
1040
1265
  });
1041
- if (p3.isCancel(which2)) return null;
1266
+ if (p4.isCancel(which2)) return null;
1042
1267
  return which2;
1043
1268
  }
1044
1269
  return "project";
@@ -1052,8 +1277,8 @@ async function chooseConfigScope(options) {
1052
1277
  console.log();
1053
1278
  const defaultScope = hasGlobal ? "project" : "global";
1054
1279
  const hint = hasGlobal ? "Your global config stays as-is. This folder gets its own setup." : "No global config yet \u2014 this becomes your default for every folder.";
1055
- p3.note(hint, defaultScope === "project" ? "Recommended: project" : "Recommended: global");
1056
- const which = await p3.select({
1280
+ p4.note(hint, defaultScope === "project" ? "Recommended: project" : "Recommended: global");
1281
+ const which = await p4.select({
1057
1282
  message: "Where should this setup live?",
1058
1283
  options: [
1059
1284
  {
@@ -1069,7 +1294,7 @@ async function chooseConfigScope(options) {
1069
1294
  ],
1070
1295
  initialValue: defaultScope
1071
1296
  });
1072
- if (p3.isCancel(which)) return null;
1297
+ if (p4.isCancel(which)) return null;
1073
1298
  return which;
1074
1299
  }
1075
1300
  function tildify(filePath) {
@@ -1133,12 +1358,12 @@ async function handleExistingConfig(apiKey, scope, options) {
1133
1358
  value: "start-fresh",
1134
1359
  label: "Start fresh (reconfigure everything)"
1135
1360
  });
1136
- const action = await p3.select({
1361
+ const action = await p4.select({
1137
1362
  message: scopedMessage(scope, "What would you like to do?"),
1138
1363
  options: actionOptions
1139
1364
  });
1140
- if (p3.isCancel(action)) {
1141
- p3.outro("No changes made.");
1365
+ if (p4.isCancel(action)) {
1366
+ p4.outro("No changes made.");
1142
1367
  return;
1143
1368
  }
1144
1369
  switch (action) {
@@ -1146,15 +1371,15 @@ async function handleExistingConfig(apiKey, scope, options) {
1146
1371
  const success = await promptSkillInstall();
1147
1372
  if (success) {
1148
1373
  printOnboardingPrompt();
1149
- p3.outro("Skill installed. Paste the prompt above to your AI agent.");
1374
+ p4.outro("Skill installed. Paste the prompt above to your AI agent.");
1150
1375
  } else {
1151
- p3.outro("Done.");
1376
+ p4.outro("Done.");
1152
1377
  }
1153
1378
  break;
1154
1379
  }
1155
1380
  case "show-prompt":
1156
1381
  printOnboardingPrompt();
1157
- p3.outro("Paste the prompt above to your AI agent.");
1382
+ p4.outro("Paste the prompt above to your AI agent.");
1158
1383
  break;
1159
1384
  case "add-connection": {
1160
1385
  const whoamiCached = getWhoAmI();
@@ -1164,7 +1389,7 @@ async function handleExistingConfig(apiKey, scope, options) {
1164
1389
  ...whoamiCached?.project && { projectId: whoamiCached.project.id }
1165
1390
  };
1166
1391
  await promptConnectIntegrations(apiKey, addConnParams);
1167
- p3.outro("Done.");
1392
+ p4.outro("Done.");
1168
1393
  break;
1169
1394
  }
1170
1395
  case "update-key":
@@ -1179,51 +1404,76 @@ async function handleExistingConfig(apiKey, scope, options) {
1179
1404
  }
1180
1405
  }
1181
1406
  async function handleUpdateKey(statuses, scope) {
1182
- p3.note(`Get your API key at:
1183
- ${pc2.cyan(getApiKeyUrl())}`, `API Key ${scopeLabel(scope)}`);
1184
- const openBrowser = await p3.confirm({
1185
- message: scopedMessage(scope, "Open browser to get API key?"),
1186
- initialValue: true
1407
+ const authMethod = await p4.select({
1408
+ message: scopedMessage(scope, "How would you like to authenticate?"),
1409
+ options: [
1410
+ { value: "browser", label: "Browser login", hint: "recommended \u2014 opens app.withone.ai" },
1411
+ { value: "manual", label: "Paste API key manually" }
1412
+ ]
1187
1413
  });
1188
- if (p3.isCancel(openBrowser)) {
1189
- p3.cancel("Cancelled.");
1414
+ if (p4.isCancel(authMethod)) {
1415
+ p4.cancel("Cancelled.");
1190
1416
  process.exit(0);
1191
1417
  }
1192
- if (openBrowser) {
1193
- await openApiKeyPage();
1194
- }
1195
- const newKey = await p3.text({
1196
- message: scopedMessage(scope, "Enter your new One API key:"),
1197
- placeholder: "sk_live_...",
1198
- validate: (value) => {
1199
- if (!value) return "API key is required";
1200
- if (!value.startsWith("sk_live_") && !value.startsWith("sk_test_")) {
1201
- return "API key should start with sk_live_ or sk_test_";
1418
+ let newKey;
1419
+ let whoamiResult;
1420
+ if (authMethod === "browser") {
1421
+ const result = await browserLogin();
1422
+ if (!result) {
1423
+ p4.cancel("Browser login did not complete.");
1424
+ process.exit(1);
1425
+ }
1426
+ newKey = result.apiKey;
1427
+ whoamiResult = result.whoami;
1428
+ } else {
1429
+ p4.note(`Get your API key at:
1430
+ ${pc2.cyan(getApiKeyUrl())}`, `API Key ${scopeLabel(scope)}`);
1431
+ const openBrowser = await p4.confirm({
1432
+ message: scopedMessage(scope, "Open browser to get API key?"),
1433
+ initialValue: true
1434
+ });
1435
+ if (p4.isCancel(openBrowser)) {
1436
+ p4.cancel("Cancelled.");
1437
+ process.exit(0);
1438
+ }
1439
+ if (openBrowser) {
1440
+ await openApiKeyPage();
1441
+ }
1442
+ const inputKey = await p4.text({
1443
+ message: scopedMessage(scope, "Enter your new One API key:"),
1444
+ placeholder: "sk_live_...",
1445
+ validate: (value) => {
1446
+ if (!value) return "API key is required";
1447
+ if (!value.startsWith("sk_live_") && !value.startsWith("sk_test_")) {
1448
+ return "API key should start with sk_live_ or sk_test_";
1449
+ }
1450
+ return void 0;
1202
1451
  }
1203
- return void 0;
1452
+ });
1453
+ if (p4.isCancel(inputKey)) {
1454
+ p4.cancel("Cancelled.");
1455
+ process.exit(0);
1204
1456
  }
1205
- });
1206
- if (p3.isCancel(newKey)) {
1207
- p3.cancel("Cancelled.");
1208
- process.exit(0);
1209
- }
1210
- const spinner5 = p3.spinner();
1211
- spinner5.start("Validating API key...");
1212
- const api = new OneApi(newKey, getApiBase());
1213
- const whoamiResult = await api.validateApiKey();
1214
- if (!whoamiResult) {
1215
- spinner5.stop("Invalid API key");
1216
- p3.cancel(`Invalid API key. Get a valid key at ${getApiKeyUrl()}`);
1217
- process.exit(1);
1457
+ newKey = inputKey;
1458
+ const spinner6 = p4.spinner();
1459
+ spinner6.start("Validating API key...");
1460
+ const api = new OneApi(newKey, getApiBase());
1461
+ const validated = await api.validateApiKey();
1462
+ if (!validated) {
1463
+ spinner6.stop("Invalid API key");
1464
+ p4.cancel(`Invalid API key. Get a valid key at ${getApiKeyUrl()}`);
1465
+ process.exit(1);
1466
+ }
1467
+ spinner6.stop("API key validated");
1468
+ whoamiResult = validated;
1218
1469
  }
1219
- spinner5.stop("API key validated");
1220
1470
  const env = getEnvFromApiKey(newKey);
1221
1471
  const contextParts = [];
1222
1472
  if (whoamiResult.organization) contextParts.push(whoamiResult.organization.name);
1223
1473
  if (whoamiResult.project) contextParts.push(whoamiResult.project.name);
1224
1474
  const scopeDisplay = contextParts.length > 0 ? contextParts.join(" / ") : "Personal";
1225
1475
  const envLabel = env === "test" ? pc2.yellow("test") : pc2.green("live");
1226
- p3.note(
1476
+ p4.note(
1227
1477
  `${scopeDisplay} ${pc2.dim("\xB7")} ${envLabel}
1228
1478
  ${whoamiResult.user.name} ${pc2.dim(`(${whoamiResult.user.email})`)}`,
1229
1479
  "Account"
@@ -1254,9 +1504,9 @@ ${whoamiResult.user.name} ${pc2.dim(`(${whoamiResult.user.email})`)}`,
1254
1504
  scope
1255
1505
  );
1256
1506
  if (reinstalled.length > 0) {
1257
- p3.log.success(`Updated MCP configs: ${reinstalled.join(", ")}`);
1507
+ p4.log.success(`Updated MCP configs: ${reinstalled.join(", ")}`);
1258
1508
  }
1259
- p3.outro("API key updated.");
1509
+ p4.outro("API key updated.");
1260
1510
  }
1261
1511
  var SKILL_AGENTS = [
1262
1512
  { id: "claude-code", name: "Claude Code", skillDir: ".claude/skills", primary: true },
@@ -1359,18 +1609,18 @@ async function promptSkillInstall() {
1359
1609
  hint: otherAgents.map((a) => a.name).join(", ")
1360
1610
  }
1361
1611
  ];
1362
- const choice = await p3.multiselect({
1612
+ const choice = await p4.multiselect({
1363
1613
  message: "Install the One skill to:",
1364
1614
  options,
1365
1615
  initialValues: primaryAgents.map((a) => a.id)
1366
1616
  });
1367
- if (p3.isCancel(choice)) {
1617
+ if (p4.isCancel(choice)) {
1368
1618
  return false;
1369
1619
  }
1370
1620
  let selectedIds = choice;
1371
1621
  if (selectedIds.includes("_other")) {
1372
1622
  selectedIds = selectedIds.filter((id) => id !== "_other");
1373
- const otherChoice = await p3.multiselect({
1623
+ const otherChoice = await p4.multiselect({
1374
1624
  message: "Select additional agents:",
1375
1625
  options: otherAgents.map((a) => ({
1376
1626
  value: a.id,
@@ -1378,23 +1628,23 @@ async function promptSkillInstall() {
1378
1628
  hint: isSkillInstalledForAgent(a) ? pc2.green("installed") : void 0
1379
1629
  }))
1380
1630
  });
1381
- if (!p3.isCancel(otherChoice)) {
1631
+ if (!p4.isCancel(otherChoice)) {
1382
1632
  selectedIds.push(...otherChoice);
1383
1633
  }
1384
1634
  }
1385
1635
  if (selectedIds.length === 0) {
1386
- p3.log.info("No agents selected.");
1636
+ p4.log.info("No agents selected.");
1387
1637
  return false;
1388
1638
  }
1389
- const spinner5 = p3.spinner();
1390
- spinner5.start("Installing skill...");
1639
+ const spinner6 = p4.spinner();
1640
+ spinner6.start("Installing skill...");
1391
1641
  const { installed, failed } = installSkillForAgents(selectedIds);
1392
- spinner5.stop(installed.length > 0 ? "Skill installed" : "Installation failed");
1642
+ spinner6.stop(installed.length > 0 ? "Skill installed" : "Installation failed");
1393
1643
  for (const name of installed) {
1394
- p3.log.success(`${name}: ${pc2.green("\u2713")} skill installed`);
1644
+ p4.log.success(`${name}: ${pc2.green("\u2713")} skill installed`);
1395
1645
  }
1396
1646
  for (const name of failed) {
1397
- p3.log.warn(`${name}: failed to install`);
1647
+ p4.log.warn(`${name}: failed to install`);
1398
1648
  }
1399
1649
  return installed.length > 0;
1400
1650
  }
@@ -1416,75 +1666,119 @@ function printOnboardingPrompt() {
1416
1666
  console.log();
1417
1667
  }
1418
1668
  async function freshSetup(scope, options) {
1419
- p3.note(`Get your API key at:
1420
- ${pc2.cyan(getApiKeyUrl())}`, `API Key ${scopeLabel(scope)}`);
1421
- const openBrowser = await p3.confirm({
1422
- message: scopedMessage(scope, "Open browser to get API key?"),
1423
- initialValue: true
1669
+ const authMethod = await p4.select({
1670
+ message: scopedMessage(scope, "How would you like to authenticate?"),
1671
+ options: [
1672
+ { value: "browser", label: "Browser login", hint: "recommended \u2014 opens app.withone.ai" },
1673
+ { value: "manual", label: "Paste API key manually" }
1674
+ ]
1424
1675
  });
1425
- if (p3.isCancel(openBrowser)) {
1426
- p3.cancel("Setup cancelled.");
1676
+ if (p4.isCancel(authMethod)) {
1677
+ p4.cancel("Setup cancelled.");
1427
1678
  process.exit(0);
1428
1679
  }
1429
- if (openBrowser) {
1430
- await openApiKeyPage();
1431
- }
1432
- const apiKey = await p3.text({
1433
- message: scopedMessage(scope, "Enter your One API key:"),
1434
- placeholder: "sk_live_...",
1435
- validate: (value) => {
1436
- if (!value) return "API key is required";
1437
- if (!value.startsWith("sk_live_") && !value.startsWith("sk_test_")) {
1438
- return "API key should start with sk_live_ or sk_test_";
1680
+ let apiKey;
1681
+ if (authMethod === "browser") {
1682
+ const result = await browserLogin();
1683
+ if (!result) {
1684
+ p4.cancel("Browser login did not complete. Try: one init");
1685
+ process.exit(1);
1686
+ }
1687
+ apiKey = result.apiKey;
1688
+ const env2 = getEnvFromApiKey(apiKey);
1689
+ const contextParts = [];
1690
+ if (result.whoami.organization) contextParts.push(result.whoami.organization.name);
1691
+ if (result.whoami.project) contextParts.push(result.whoami.project.name);
1692
+ const scopeDisplay = contextParts.length > 0 ? contextParts.join(" / ") : "Personal";
1693
+ const envLabel = env2 === "test" ? pc2.yellow("test") : pc2.green("live");
1694
+ p4.note(
1695
+ `${scopeDisplay} ${pc2.dim("\xB7")} ${envLabel}
1696
+ ${result.whoami.user.name} ${pc2.dim(`(${result.whoami.user.email})`)}`,
1697
+ "Account"
1698
+ );
1699
+ writeConfig(
1700
+ {
1701
+ apiKey,
1702
+ installedAgents: [],
1703
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
1704
+ whoami: result.whoami
1705
+ },
1706
+ scope
1707
+ );
1708
+ } else {
1709
+ p4.note(`Get your API key at:
1710
+ ${pc2.cyan(getApiKeyUrl())}`, `API Key ${scopeLabel(scope)}`);
1711
+ const openBrowser = await p4.confirm({
1712
+ message: scopedMessage(scope, "Open browser to get API key?"),
1713
+ initialValue: true
1714
+ });
1715
+ if (p4.isCancel(openBrowser)) {
1716
+ p4.cancel("Setup cancelled.");
1717
+ process.exit(0);
1718
+ }
1719
+ if (openBrowser) {
1720
+ await openApiKeyPage();
1721
+ }
1722
+ const inputKey = await p4.text({
1723
+ message: scopedMessage(scope, "Enter your One API key:"),
1724
+ placeholder: "sk_live_...",
1725
+ validate: (value) => {
1726
+ if (!value) return "API key is required";
1727
+ if (!value.startsWith("sk_live_") && !value.startsWith("sk_test_")) {
1728
+ return "API key should start with sk_live_ or sk_test_";
1729
+ }
1730
+ return void 0;
1439
1731
  }
1440
- return void 0;
1732
+ });
1733
+ if (p4.isCancel(inputKey)) {
1734
+ p4.cancel("Setup cancelled.");
1735
+ process.exit(0);
1441
1736
  }
1442
- });
1443
- if (p3.isCancel(apiKey)) {
1444
- p3.cancel("Setup cancelled.");
1445
- process.exit(0);
1446
- }
1447
- const spinner5 = p3.spinner();
1448
- spinner5.start("Validating API key...");
1449
- const api = new OneApi(apiKey, getApiBase());
1450
- const whoami = await api.validateApiKey();
1451
- if (!whoami) {
1452
- spinner5.stop("Invalid API key");
1453
- p3.cancel(`Invalid API key. Get a valid key at ${getApiKeyUrl()}`);
1454
- process.exit(1);
1455
- }
1456
- spinner5.stop("API key validated");
1457
- const env = getEnvFromApiKey(apiKey);
1458
- const contextParts = [];
1459
- if (whoami.organization) contextParts.push(whoami.organization.name);
1460
- if (whoami.project) contextParts.push(whoami.project.name);
1461
- const scope_label = contextParts.length > 0 ? contextParts.join(" / ") : "Personal";
1462
- const envLabel = env === "test" ? pc2.yellow("test") : pc2.green("live");
1463
- p3.note(
1464
- `${scope_label} ${pc2.dim("\xB7")} ${envLabel}
1737
+ apiKey = inputKey;
1738
+ const spinner6 = p4.spinner();
1739
+ spinner6.start("Validating API key...");
1740
+ const api = new OneApi(apiKey, getApiBase());
1741
+ const whoami = await api.validateApiKey();
1742
+ if (!whoami) {
1743
+ spinner6.stop("Invalid API key");
1744
+ p4.cancel(`Invalid API key. Get a valid key at ${getApiKeyUrl()}`);
1745
+ process.exit(1);
1746
+ }
1747
+ spinner6.stop("API key validated");
1748
+ const env2 = getEnvFromApiKey(apiKey);
1749
+ const contextParts = [];
1750
+ if (whoami.organization) contextParts.push(whoami.organization.name);
1751
+ if (whoami.project) contextParts.push(whoami.project.name);
1752
+ const scopeDisplay = contextParts.length > 0 ? contextParts.join(" / ") : "Personal";
1753
+ const envLabel = env2 === "test" ? pc2.yellow("test") : pc2.green("live");
1754
+ p4.note(
1755
+ `${scopeDisplay} ${pc2.dim("\xB7")} ${envLabel}
1465
1756
  ${whoami.user.name} ${pc2.dim(`(${whoami.user.email})`)}`,
1466
- "Account"
1467
- );
1468
- writeConfig(
1469
- {
1470
- apiKey,
1471
- installedAgents: [],
1472
- createdAt: (/* @__PURE__ */ new Date()).toISOString(),
1473
- whoami
1474
- },
1475
- scope
1476
- );
1757
+ "Account"
1758
+ );
1759
+ writeConfig(
1760
+ {
1761
+ apiKey,
1762
+ installedAgents: [],
1763
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
1764
+ whoami
1765
+ },
1766
+ scope
1767
+ );
1768
+ }
1477
1769
  await promptSkillInstall();
1770
+ const env = getEnvFromApiKey(apiKey);
1771
+ const savedConfig = readConfig();
1478
1772
  const connParams = {
1479
1773
  env,
1480
- ...whoami.organization && { orgId: whoami.organization.id },
1481
- ...whoami.project && { projectId: whoami.project.id }
1774
+ ...savedConfig?.whoami?.organization && { orgId: savedConfig.whoami.organization.id },
1775
+ ...savedConfig?.whoami?.project && { projectId: savedConfig.whoami.project.id }
1482
1776
  };
1483
1777
  await promptConnectIntegrations(apiKey, connParams);
1484
1778
  const savedPath = scope === "project" ? getProjectConfigPath() : getGlobalConfigPath();
1485
1779
  const resolutionHint = scope === "project" ? `When you run ${pc2.cyan("one")} from ${pc2.bold(path4.basename(getProjectRoot()))}, it uses this project config.
1486
1780
  From anywhere else, it falls back to your global config.` : `This config applies to every folder unless a project config is set.`;
1487
- p3.note(
1781
+ p4.note(
1488
1782
  `${scopeLabel(scope)} Config saved to:
1489
1783
  ${pc2.dim(tildify(savedPath))}
1490
1784
 
@@ -1492,7 +1786,7 @@ ${resolutionHint}`,
1492
1786
  "Setup Complete"
1493
1787
  );
1494
1788
  printOnboardingPrompt();
1495
- p3.outro("Done! Paste the prompt above to your AI agent.");
1789
+ p4.outro("Done! Paste the prompt above to your AI agent.");
1496
1790
  }
1497
1791
  function printBanner() {
1498
1792
  console.log();
@@ -1541,48 +1835,48 @@ async function promptConnectIntegrations(apiKey, connParams) {
1541
1835
  { value: "skip", label: "Skip for now", hint: "you can always run one add later" }
1542
1836
  ];
1543
1837
  const message = first ? "Connect your first integration?" : "Connect another?";
1544
- const choice = await p3.select({ message, options });
1545
- if (p3.isCancel(choice) || choice === "skip") {
1838
+ const choice = await p4.select({ message, options });
1839
+ if (p4.isCancel(choice) || choice === "skip") {
1546
1840
  break;
1547
1841
  }
1548
1842
  if (choice === "more") {
1549
1843
  try {
1550
1844
  await open2("https://app.withone.ai/connections");
1551
- p3.log.info("Opened One dashboard in browser.");
1845
+ p4.log.info("Opened One dashboard in browser.");
1552
1846
  } catch {
1553
- p3.note("https://app.withone.ai/connections", "Open in browser");
1847
+ p4.note("https://app.withone.ai/connections", "Open in browser");
1554
1848
  }
1555
- p3.log.info(`Connect from the dashboard, or use ${pc2.cyan("one add <platform>")}`);
1849
+ p4.log.info(`Connect from the dashboard, or use ${pc2.cyan("one add <platform>")}`);
1556
1850
  break;
1557
1851
  }
1558
1852
  const platform = choice;
1559
1853
  const integration = TOP_INTEGRATIONS.find((i) => i.value === platform);
1560
1854
  const label = integration?.label ?? platform;
1561
- p3.log.info(`Opening browser to connect ${pc2.cyan(label)}...`);
1855
+ p4.log.info(`Opening browser to connect ${pc2.cyan(label)}...`);
1562
1856
  try {
1563
1857
  await openConnectionPage(platform, connParams);
1564
1858
  } catch {
1565
1859
  const url = getConnectionUrl(platform, connParams);
1566
- p3.log.warn("Could not open browser automatically.");
1567
- p3.note(url, "Open manually");
1860
+ p4.log.warn("Could not open browser automatically.");
1861
+ p4.note(url, "Open manually");
1568
1862
  }
1569
- const spinner5 = p3.spinner();
1570
- spinner5.start("Waiting for connection... (complete auth in browser)");
1863
+ const spinner6 = p4.spinner();
1864
+ spinner6.start("Waiting for connection... (complete auth in browser)");
1571
1865
  try {
1572
1866
  await api.waitForConnection(platform, 5 * 60 * 1e3, 5e3);
1573
- spinner5.stop(`${label} connected!`);
1574
- p3.log.success(`${pc2.green("\u2713")} ${label} is now available to your AI agents`);
1867
+ spinner6.stop(`${label} connected!`);
1868
+ p4.log.success(`${pc2.green("\u2713")} ${label} is now available to your AI agents`);
1575
1869
  connected.push(platform);
1576
1870
  first = false;
1577
1871
  } catch (error2) {
1578
- spinner5.stop("Connection timed out");
1872
+ spinner6.stop("Connection timed out");
1579
1873
  if (error2 instanceof TimeoutError) {
1580
- p3.log.warn(`No worries. Connect later with: ${pc2.cyan(`one add ${platform}`)}`);
1874
+ p4.log.warn(`No worries. Connect later with: ${pc2.cyan(`one add ${platform}`)}`);
1581
1875
  }
1582
1876
  first = false;
1583
1877
  }
1584
1878
  if (TOP_INTEGRATIONS.every((i) => connected.includes(i.value))) {
1585
- p3.log.success("All top integrations connected!");
1879
+ p4.log.success("All top integrations connected!");
1586
1880
  break;
1587
1881
  }
1588
1882
  }
@@ -1593,23 +1887,23 @@ function maskApiKey(key) {
1593
1887
  }
1594
1888
 
1595
1889
  // src/commands/connection.ts
1596
- import * as p4 from "@clack/prompts";
1890
+ import * as p5 from "@clack/prompts";
1597
1891
  import pc4 from "picocolors";
1598
1892
 
1599
1893
  // src/lib/platforms.ts
1600
1894
  function findPlatform(platforms, query) {
1601
1895
  const normalizedQuery = query.toLowerCase().trim();
1602
1896
  const exact = platforms.find(
1603
- (p8) => p8.platform.toLowerCase() === normalizedQuery || p8.name.toLowerCase() === normalizedQuery
1897
+ (p10) => p10.platform.toLowerCase() === normalizedQuery || p10.name.toLowerCase() === normalizedQuery
1604
1898
  );
1605
1899
  if (exact) return exact;
1606
1900
  return null;
1607
1901
  }
1608
1902
  function findSimilarPlatforms(platforms, query, limit = 3) {
1609
1903
  const normalizedQuery = query.toLowerCase().trim();
1610
- const scored = platforms.map((p8) => {
1611
- const name = p8.name.toLowerCase();
1612
- const slug = p8.platform.toLowerCase();
1904
+ const scored = platforms.map((p10) => {
1905
+ const name = p10.name.toLowerCase();
1906
+ const slug = p10.platform.toLowerCase();
1613
1907
  let score = 0;
1614
1908
  if (name.includes(normalizedQuery) || slug.includes(normalizedQuery)) {
1615
1909
  score = 10;
@@ -1618,7 +1912,7 @@ function findSimilarPlatforms(platforms, query, limit = 3) {
1618
1912
  } else {
1619
1913
  score = countMatchingChars(normalizedQuery, slug);
1620
1914
  }
1621
- return { platform: p8, score };
1915
+ return { platform: p10, score };
1622
1916
  }).filter((item) => item.score > 0).sort((a, b) => b.score - a.score).slice(0, limit);
1623
1917
  return scored.map((item) => item.platform);
1624
1918
  }
@@ -1672,22 +1966,22 @@ async function connectionAddCommand(platformArg) {
1672
1966
  if (isAgentMode()) {
1673
1967
  error("This command requires interactive input. Run without --agent.");
1674
1968
  }
1675
- p4.intro(pc4.bgCyan(pc4.black(" One ")));
1969
+ p5.intro(pc4.bgCyan(pc4.black(" One ")));
1676
1970
  const apiKey = getApiKey();
1677
1971
  if (!apiKey) {
1678
- p4.cancel("Not configured. Run `one init` first.");
1972
+ p5.cancel("Not configured. Run `one init` first.");
1679
1973
  process.exit(1);
1680
1974
  }
1681
1975
  const api = new OneApi(apiKey, getApiBase());
1682
- const spinner5 = p4.spinner();
1683
- spinner5.start("Loading platforms...");
1976
+ const spinner6 = p5.spinner();
1977
+ spinner6.start("Loading platforms...");
1684
1978
  let platforms;
1685
1979
  try {
1686
1980
  platforms = await api.listPlatforms();
1687
- spinner5.stop(`${platforms.length} platforms available`);
1981
+ spinner6.stop(`${platforms.length} platforms available`);
1688
1982
  } catch (error2) {
1689
- spinner5.stop("Failed to load platforms");
1690
- p4.cancel(`Error: ${error2 instanceof Error ? error2.message : "Unknown error"}`);
1983
+ spinner6.stop("Failed to load platforms");
1984
+ p5.cancel(`Error: ${error2 instanceof Error ? error2.message : "Unknown error"}`);
1691
1985
  process.exit(1);
1692
1986
  }
1693
1987
  let platform;
@@ -1698,29 +1992,29 @@ async function connectionAddCommand(platformArg) {
1698
1992
  } else {
1699
1993
  const similar = findSimilarPlatforms(platforms, platformArg);
1700
1994
  if (similar.length > 0) {
1701
- p4.log.warn(`Unknown platform: ${platformArg}`);
1702
- const suggestion = await p4.select({
1995
+ p5.log.warn(`Unknown platform: ${platformArg}`);
1996
+ const suggestion = await p5.select({
1703
1997
  message: "Did you mean:",
1704
1998
  options: [
1705
1999
  ...similar.map((s) => ({ value: s.platform, label: `${s.name} (${s.platform})` })),
1706
2000
  { value: "__other__", label: "None of these" }
1707
2001
  ]
1708
2002
  });
1709
- if (p4.isCancel(suggestion) || suggestion === "__other__") {
1710
- p4.note(`Run ${pc4.cyan("one platforms")} to see all available platforms.`);
1711
- p4.cancel("Connection cancelled.");
2003
+ if (p5.isCancel(suggestion) || suggestion === "__other__") {
2004
+ p5.note(`Run ${pc4.cyan("one platforms")} to see all available platforms.`);
2005
+ p5.cancel("Connection cancelled.");
1712
2006
  process.exit(0);
1713
2007
  }
1714
2008
  platform = suggestion;
1715
2009
  } else {
1716
- p4.cancel(`Unknown platform: ${platformArg}
2010
+ p5.cancel(`Unknown platform: ${platformArg}
1717
2011
 
1718
2012
  Run ${pc4.cyan("one platforms")} to see available platforms.`);
1719
2013
  process.exit(1);
1720
2014
  }
1721
2015
  }
1722
2016
  } else {
1723
- const platformInput = await p4.text({
2017
+ const platformInput = await p5.text({
1724
2018
  message: "Which platform do you want to connect?",
1725
2019
  placeholder: "gmail, slack, hubspot...",
1726
2020
  validate: (value) => {
@@ -1728,15 +2022,15 @@ Run ${pc4.cyan("one platforms")} to see available platforms.`);
1728
2022
  return void 0;
1729
2023
  }
1730
2024
  });
1731
- if (p4.isCancel(platformInput)) {
1732
- p4.cancel("Connection cancelled.");
2025
+ if (p5.isCancel(platformInput)) {
2026
+ p5.cancel("Connection cancelled.");
1733
2027
  process.exit(0);
1734
2028
  }
1735
2029
  const found = findPlatform(platforms, platformInput);
1736
2030
  if (found) {
1737
2031
  platform = found.platform;
1738
2032
  } else {
1739
- p4.cancel(`Unknown platform: ${platformInput}
2033
+ p5.cancel(`Unknown platform: ${platformInput}
1740
2034
 
1741
2035
  Run ${pc4.cyan("one platforms")} to see available platforms.`);
1742
2036
  process.exit(1);
@@ -1749,26 +2043,26 @@ Run ${pc4.cyan("one platforms")} to see available platforms.`);
1749
2043
  ...whoami?.project && { projectId: whoami.project.id }
1750
2044
  };
1751
2045
  const url = getConnectionUrl(platform, connParams);
1752
- p4.log.info(`Opening browser to connect ${pc4.cyan(platform)}...`);
1753
- p4.note(pc4.dim(url), "URL");
2046
+ p5.log.info(`Opening browser to connect ${pc4.cyan(platform)}...`);
2047
+ p5.note(pc4.dim(url), "URL");
1754
2048
  try {
1755
2049
  await openConnectionPage(platform, connParams);
1756
2050
  } catch {
1757
- p4.log.warn("Could not open browser automatically.");
1758
- p4.note(`Open this URL manually:
2051
+ p5.log.warn("Could not open browser automatically.");
2052
+ p5.note(`Open this URL manually:
1759
2053
  ${url}`);
1760
2054
  }
1761
- const pollSpinner = p4.spinner();
2055
+ const pollSpinner = p5.spinner();
1762
2056
  pollSpinner.start("Waiting for connection... (complete auth in browser)");
1763
2057
  try {
1764
2058
  const connection2 = await api.waitForConnection(platform, 5 * 60 * 1e3, 5e3);
1765
2059
  pollSpinner.stop(`${platform} connected!`);
1766
- p4.log.success(`${pc4.green("\u2713")} ${connection2.platform} is now available to your AI agents.`);
1767
- p4.outro("Connection complete!");
2060
+ p5.log.success(`${pc4.green("\u2713")} ${connection2.platform} is now available to your AI agents.`);
2061
+ p5.outro("Connection complete!");
1768
2062
  } catch (error2) {
1769
2063
  pollSpinner.stop("Connection timed out");
1770
2064
  if (error2 instanceof TimeoutError) {
1771
- p4.note(
2065
+ p5.note(
1772
2066
  `Possible issues:
1773
2067
  - OAuth flow was not completed in the browser
1774
2068
  - Browser popup was blocked
@@ -1778,7 +2072,7 @@ Try again with: ${pc4.cyan(`one connection add ${platform}`)}`,
1778
2072
  "Timed Out"
1779
2073
  );
1780
2074
  } else {
1781
- p4.log.error(`Error: ${error2 instanceof Error ? error2.message : "Unknown error"}`);
2075
+ p5.log.error(`Error: ${error2 instanceof Error ? error2.message : "Unknown error"}`);
1782
2076
  }
1783
2077
  process.exit(1);
1784
2078
  }
@@ -1789,8 +2083,8 @@ async function connectionListCommand(options) {
1789
2083
  error("Not configured. Run `one init` first.");
1790
2084
  }
1791
2085
  const api = new OneApi(apiKey, getApiBase());
1792
- const spinner5 = createSpinner();
1793
- spinner5.start("Loading connections...");
2086
+ const spinner6 = createSpinner();
2087
+ spinner6.start("Loading connections...");
1794
2088
  try {
1795
2089
  const allConnections = await api.listConnections();
1796
2090
  const ac = getAccessControlFromAllSources();
@@ -1818,17 +2112,17 @@ async function connectionListCommand(options) {
1818
2112
  });
1819
2113
  return;
1820
2114
  }
1821
- spinner5.stop(`${filtered.length} connection${filtered.length === 1 ? "" : "s"} found`);
2115
+ spinner6.stop(`${filtered.length} connection${filtered.length === 1 ? "" : "s"} found`);
1822
2116
  if (filtered.length === 0) {
1823
2117
  if (searchQuery) {
1824
- p4.note(
2118
+ p5.note(
1825
2119
  `No connections matching "${searchQuery}".
1826
2120
 
1827
2121
  Try: ${pc4.cyan("one connection list")} to see all connections.`,
1828
2122
  "No Results"
1829
2123
  );
1830
2124
  } else {
1831
- p4.note(
2125
+ p5.note(
1832
2126
  `No connections yet.
1833
2127
 
1834
2128
  Add one with: ${pc4.cyan("one connection add gmail")}`,
@@ -1857,9 +2151,9 @@ Add one with: ${pc4.cyan("one connection add gmail")}`,
1857
2151
  rows
1858
2152
  );
1859
2153
  console.log();
1860
- p4.note(`Add more with: ${pc4.cyan("one connection add <platform>")}`, "Tip");
2154
+ p5.note(`Add more with: ${pc4.cyan("one connection add <platform>")}`, "Tip");
1861
2155
  } catch (error2) {
1862
- spinner5.stop("Failed to load connections");
2156
+ spinner6.stop("Failed to load connections");
1863
2157
  error(`Error: ${error2 instanceof Error ? error2.message : "Unknown error"}`);
1864
2158
  }
1865
2159
  }
@@ -1869,13 +2163,13 @@ async function connectionDeleteCommand(connectionKey, options) {
1869
2163
  error("Not configured. Run `one init` first.");
1870
2164
  }
1871
2165
  const api = new OneApi(apiKey, getApiBase());
1872
- const spinner5 = createSpinner();
1873
- spinner5.start("Finding connection...");
2166
+ const spinner6 = createSpinner();
2167
+ spinner6.start("Finding connection...");
1874
2168
  let allConnections;
1875
2169
  try {
1876
2170
  allConnections = await api.listConnections();
1877
2171
  } catch (error2) {
1878
- spinner5.stop("Failed to load connections");
2172
+ spinner6.stop("Failed to load connections");
1879
2173
  error(`Error: ${error2 instanceof Error ? error2.message : "Unknown error"}`);
1880
2174
  return;
1881
2175
  }
@@ -1884,22 +2178,22 @@ async function connectionDeleteCommand(connectionKey, options) {
1884
2178
  const connections = allowedKeys.includes("*") ? allConnections : allConnections.filter((conn) => allowedKeys.includes(conn.key));
1885
2179
  const match = connections.find((conn) => conn.key === connectionKey);
1886
2180
  if (!match) {
1887
- spinner5.stop("Connection not found");
2181
+ spinner6.stop("Connection not found");
1888
2182
  error(`No connection found with key: ${connectionKey}`);
1889
2183
  return;
1890
2184
  }
1891
2185
  const connection2 = match;
1892
- spinner5.stop(`Found ${connection2.platform} (${connection2.state})`);
2186
+ spinner6.stop(`Found ${connection2.platform} (${connection2.state})`);
1893
2187
  if (!isAgentMode() && !options?.force) {
1894
2188
  console.log();
1895
2189
  console.log(` ${getStatusIndicator(connection2.state)} ${connection2.platform} ${pc4.dim(connection2.key)}`);
1896
2190
  console.log();
1897
- const confirmed = await p4.confirm({
2191
+ const confirmed = await p5.confirm({
1898
2192
  message: "Are you sure you want to delete this connection?",
1899
2193
  initialValue: false
1900
2194
  });
1901
- if (p4.isCancel(confirmed) || !confirmed) {
1902
- p4.cancel("Deletion cancelled.");
2195
+ if (p5.isCancel(confirmed) || !confirmed) {
2196
+ p5.cancel("Deletion cancelled.");
1903
2197
  process.exit(0);
1904
2198
  }
1905
2199
  }
@@ -1916,7 +2210,7 @@ async function connectionDeleteCommand(connectionKey, options) {
1916
2210
  });
1917
2211
  return;
1918
2212
  }
1919
- p4.log.success(`${pc4.green("\u2713")} ${connection2.platform} connection removed.`);
2213
+ p5.log.success(`${pc4.green("\u2713")} ${connection2.platform} connection removed.`);
1920
2214
  } catch (error2) {
1921
2215
  deleteSpinner.stop("Failed to delete connection");
1922
2216
  error(`Error: ${error2 instanceof Error ? error2.message : "Unknown error"}`);
@@ -1936,7 +2230,7 @@ function getStatusIndicator(state) {
1936
2230
  }
1937
2231
 
1938
2232
  // src/commands/platforms.ts
1939
- import * as p5 from "@clack/prompts";
2233
+ import * as p6 from "@clack/prompts";
1940
2234
  import pc5 from "picocolors";
1941
2235
  async function platformsCommand(options) {
1942
2236
  const apiKey = getApiKey();
@@ -1947,11 +2241,11 @@ async function platformsCommand(options) {
1947
2241
  options.json = true;
1948
2242
  }
1949
2243
  const api = new OneApi(apiKey, getApiBase());
1950
- const spinner5 = createSpinner();
1951
- spinner5.start("Loading platforms...");
2244
+ const spinner6 = createSpinner();
2245
+ spinner6.start("Loading platforms...");
1952
2246
  try {
1953
2247
  const platforms = await api.listPlatforms();
1954
- spinner5.stop(`${platforms.length} platforms available`);
2248
+ spinner6.stop(`${platforms.length} platforms available`);
1955
2249
  let filtered = platforms;
1956
2250
  if (options.category) {
1957
2251
  filtered = platforms.filter((plat) => (plat.category || "Other") === options.category);
@@ -1961,7 +2255,7 @@ async function platformsCommand(options) {
1961
2255
  json({ error: `Unknown category "${options.category}"`, availableCategories: categories });
1962
2256
  process.exit(1);
1963
2257
  }
1964
- p5.note(`Available categories:
2258
+ p6.note(`Available categories:
1965
2259
  ${categories.join(", ")}`, "Unknown Category");
1966
2260
  process.exit(1);
1967
2261
  }
@@ -2003,15 +2297,15 @@ async function platformsCommand(options) {
2003
2297
  );
2004
2298
  }
2005
2299
  console.log();
2006
- p5.note(`Connect with: ${pc5.cyan("one connection add <platform>")}`, "Tip");
2300
+ p6.note(`Connect with: ${pc5.cyan("one connection add <platform>")}`, "Tip");
2007
2301
  } catch (error2) {
2008
- spinner5.stop("Failed to load platforms");
2302
+ spinner6.stop("Failed to load platforms");
2009
2303
  error(`Error: ${error2 instanceof Error ? error2.message : "Unknown error"}`);
2010
2304
  }
2011
2305
  }
2012
2306
 
2013
2307
  // src/commands/actions.ts
2014
- import * as p6 from "@clack/prompts";
2308
+ import * as p7 from "@clack/prompts";
2015
2309
  import pc6 from "picocolors";
2016
2310
 
2017
2311
  // src/lib/cache.ts
@@ -2153,8 +2447,8 @@ async function actionsSearchCommand(platform, query, options) {
2153
2447
  intro2(pc6.bgCyan(pc6.black(" One ")));
2154
2448
  const { apiKey, permissions, actionIds, knowledgeAgent } = getConfig();
2155
2449
  const api = new OneApi(apiKey, getApiBase());
2156
- const spinner5 = createSpinner();
2157
- spinner5.start(`Searching actions on ${pc6.cyan(platform)} for "${query}"...`);
2450
+ const spinner6 = createSpinner();
2451
+ spinner6.start(`Searching actions on ${pc6.cyan(platform)} for "${query}"...`);
2158
2452
  try {
2159
2453
  const agentType = knowledgeAgent ? "knowledge" : options.type || "execute";
2160
2454
  const useCache = options.cache !== false;
@@ -2219,8 +2513,8 @@ async function actionsSearchCommand(platform, query, options) {
2219
2513
  return;
2220
2514
  }
2221
2515
  if (cleanedActions.length === 0) {
2222
- spinner5.stop("No actions found");
2223
- p6.note(
2516
+ spinner6.stop("No actions found");
2517
+ p7.note(
2224
2518
  `No actions found for platform '${platform}' matching query '${query}'.
2225
2519
 
2226
2520
  Suggestions:
@@ -2237,7 +2531,7 @@ Examples of good queries:
2237
2531
  );
2238
2532
  return;
2239
2533
  }
2240
- spinner5.stop(
2534
+ spinner6.stop(
2241
2535
  `Found ${cleanedActions.length} action(s) for '${platform}' matching '${query}'`
2242
2536
  );
2243
2537
  console.log();
@@ -2257,13 +2551,13 @@ Examples of good queries:
2257
2551
  rows
2258
2552
  );
2259
2553
  console.log();
2260
- p6.note(
2554
+ p7.note(
2261
2555
  `Get details: ${pc6.cyan(`one actions knowledge ${platform} <actionId>`)}
2262
2556
  Execute: ${pc6.cyan(`one actions execute ${platform} <actionId> <connectionKey>`)}`,
2263
2557
  "Next Steps"
2264
2558
  );
2265
2559
  } catch (error2) {
2266
- spinner5.stop("Search failed");
2560
+ spinner6.stop("Search failed");
2267
2561
  error(
2268
2562
  `Error: ${error2 instanceof Error ? error2.message : "Unknown error"}`
2269
2563
  );
@@ -2299,25 +2593,25 @@ async function actionsKnowledgeCommand(platform, actionId, options) {
2299
2593
  error(`Action "${actionId}" is not in the allowed action list.`);
2300
2594
  }
2301
2595
  if (!connectionKeys.includes("*")) {
2302
- const spinner6 = createSpinner();
2303
- spinner6.start("Checking connections...");
2596
+ const spinner7 = createSpinner();
2597
+ spinner7.start("Checking connections...");
2304
2598
  try {
2305
2599
  const connections = await api.listConnections();
2306
2600
  const connectedPlatforms = connections.map((c) => c.platform);
2307
2601
  if (!connectedPlatforms.includes(platform)) {
2308
- spinner6.stop("Platform not connected");
2602
+ spinner7.stop("Platform not connected");
2309
2603
  error(`Platform "${platform}" has no allowed connections.`);
2310
2604
  }
2311
- spinner6.stop("Connection verified");
2605
+ spinner7.stop("Connection verified");
2312
2606
  } catch (error2) {
2313
- spinner6.stop("Failed to check connections");
2607
+ spinner7.stop("Failed to check connections");
2314
2608
  error(
2315
2609
  `Error: ${error2 instanceof Error ? error2.message : "Unknown error"}`
2316
2610
  );
2317
2611
  }
2318
2612
  }
2319
- const spinner5 = createSpinner();
2320
- spinner5.start(`Loading knowledge for action ${pc6.dim(actionId)}...`);
2613
+ const spinner6 = createSpinner();
2614
+ spinner6.start(`Loading knowledge for action ${pc6.dim(actionId)}...`);
2321
2615
  try {
2322
2616
  const useCache = options.cache !== false;
2323
2617
  const cached2 = useCache ? readCache2(cachePath) : null;
@@ -2372,16 +2666,16 @@ async function actionsKnowledgeCommand(platform, actionId, options) {
2372
2666
  json(response);
2373
2667
  return;
2374
2668
  }
2375
- spinner5.stop("Knowledge loaded");
2669
+ spinner6.stop("Knowledge loaded");
2376
2670
  console.log();
2377
2671
  console.log(knowledgeWithGuidance);
2378
2672
  console.log();
2379
- p6.note(
2673
+ p7.note(
2380
2674
  `Execute: ${pc6.cyan(`one actions execute ${platform} ${actionId} <connectionKey>`)}`,
2381
2675
  "Next Step"
2382
2676
  );
2383
2677
  } catch (error2) {
2384
- spinner5.stop("Failed to load knowledge");
2678
+ spinner6.stop("Failed to load knowledge");
2385
2679
  error(
2386
2680
  `Error: ${error2 instanceof Error ? error2.message : "Unknown error"}`
2387
2681
  );
@@ -2402,17 +2696,17 @@ async function actionsExecuteCommand(platform, actionId, connectionKey, options)
2402
2696
  error(`Connection key "${connectionKey}" is not allowed.`);
2403
2697
  }
2404
2698
  const api = new OneApi(apiKey, getApiBase());
2405
- const spinner5 = createSpinner();
2406
- spinner5.start("Loading action details...");
2699
+ const spinner6 = createSpinner();
2700
+ spinner6.start("Loading action details...");
2407
2701
  try {
2408
2702
  const actionDetails = await api.getActionDetails(actionId);
2409
2703
  if (!isMethodAllowed(actionDetails.method, permissions)) {
2410
- spinner5.stop("Permission denied");
2704
+ spinner6.stop("Permission denied");
2411
2705
  error(
2412
2706
  `Method "${actionDetails.method}" is not allowed under "${permissions}" permission level.`
2413
2707
  );
2414
2708
  }
2415
- spinner5.stop(`Action: ${actionDetails.title} [${actionDetails.method}]`);
2709
+ spinner6.stop(`Action: ${actionDetails.title} [${actionDetails.method}]`);
2416
2710
  const data = options.data ? parseJsonArg(options.data, "--data") : void 0;
2417
2711
  const pathVariables = options.pathVars ? parseJsonArg(options.pathVars, "--path-vars") : void 0;
2418
2712
  const queryParams = options.queryParams ? parseJsonArg(options.queryParams, "--query-params") : void 0;
@@ -2420,7 +2714,7 @@ async function actionsExecuteCommand(platform, actionId, connectionKey, options)
2420
2714
  if (!options.skipValidation) {
2421
2715
  const validation = validateActionInput(actionDetails, { data, pathVariables, queryParams });
2422
2716
  if (!validation.valid) {
2423
- spinner5.stop("Validation failed");
2717
+ spinner6.stop("Validation failed");
2424
2718
  if (isAgentMode()) {
2425
2719
  json({
2426
2720
  error: "Validation failed: missing required parameters",
@@ -2441,7 +2735,7 @@ async function actionsExecuteCommand(platform, actionId, connectionKey, options)
2441
2735
  }
2442
2736
  }
2443
2737
  if (options.mock) {
2444
- spinner5.stop("Mock \u2014 returning example response");
2738
+ spinner6.stop("Mock \u2014 returning example response");
2445
2739
  const mockResponse = actionDetails.ioSchema?.ioExample?.output ?? null;
2446
2740
  if (isAgentMode()) {
2447
2741
  json({
@@ -2516,7 +2810,7 @@ async function actionsExecuteCommand(platform, actionId, connectionKey, options)
2516
2810
  console.log(JSON.stringify(result.responseData, null, 2));
2517
2811
  }
2518
2812
  } catch (error2) {
2519
- spinner5.stop("Execution failed");
2813
+ spinner6.stop("Execution failed");
2520
2814
  error(
2521
2815
  `Error: ${error2 instanceof Error ? error2.message : "Unknown error"}`
2522
2816
  );
@@ -3463,8 +3757,15 @@ async function flowCreateCommand(key, options) {
3463
3757
  } else {
3464
3758
  error("Interactive workflow creation not yet supported. Use --definition <json> or pipe JSON via stdin.");
3465
3759
  }
3760
+ let group;
3466
3761
  if (key) {
3467
- flow2.key = key;
3762
+ if (key.includes("/")) {
3763
+ const parts = key.split("/");
3764
+ flow2.key = parts.pop();
3765
+ group = parts.join("/");
3766
+ } else {
3767
+ flow2.key = key;
3768
+ }
3468
3769
  }
3469
3770
  const errors = validateFlow(flow2);
3470
3771
  if (errors.length > 0) {
@@ -3475,7 +3776,7 @@ async function flowCreateCommand(key, options) {
3475
3776
  error(`Validation failed:
3476
3777
  ${errors.map((e) => ` ${e.path}: ${e.message}`).join("\n")}`);
3477
3778
  }
3478
- const flowPath = saveFlow(flow2, options.output);
3779
+ const flowPath = saveFlow(flow2, options.output, group);
3479
3780
  if (isAgentMode()) {
3480
3781
  json({ created: true, key: flow2.key, path: flowPath });
3481
3782
  return;
@@ -3488,8 +3789,8 @@ async function flowExecuteCommand(keyOrPath, options) {
3488
3789
  intro2(pc7.bgCyan(pc7.black(" One Workflow ")));
3489
3790
  const { apiKey, permissions, actionIds } = getConfig2();
3490
3791
  const api = new OneApi(apiKey, getApiBase());
3491
- const spinner5 = createSpinner();
3492
- spinner5.start(`Loading workflow "${keyOrPath}"...`);
3792
+ const spinner6 = createSpinner();
3793
+ spinner6.start(`Loading workflow "${keyOrPath}"...`);
3493
3794
  let flow2;
3494
3795
  let rootDir;
3495
3796
  let flowFilePath;
@@ -3499,11 +3800,11 @@ async function flowExecuteCommand(keyOrPath, options) {
3499
3800
  rootDir = loaded.rootDir;
3500
3801
  flowFilePath = loaded.filePath;
3501
3802
  } catch (err) {
3502
- spinner5.stop("Workflow not found");
3803
+ spinner6.stop("Workflow not found");
3503
3804
  error(err instanceof Error ? err.message : String(err));
3504
3805
  return;
3505
3806
  }
3506
- spinner5.stop(`Workflow: ${flow2.name} (${flow2.steps.length} steps)`);
3807
+ spinner6.stop(`Workflow: ${flow2.name} (${flow2.steps.length} steps)`);
3507
3808
  const preflightErrors = validateFlow(flow2, rootDir);
3508
3809
  if (preflightErrors.length > 0) {
3509
3810
  if (isAgentMode()) {
@@ -3639,7 +3940,7 @@ async function flowListCommand() {
3639
3940
  { key: "flags", label: "Requires" }
3640
3941
  ],
3641
3942
  flows.map((f) => ({
3642
- key: f.key,
3943
+ key: f.group ? `${f.group}/${f.key}` : f.key,
3643
3944
  name: f.name,
3644
3945
  layout: f.layout,
3645
3946
  inputCount: String(f.inputCount),
@@ -3651,8 +3952,8 @@ async function flowListCommand() {
3651
3952
  }
3652
3953
  async function flowValidateCommand(keyOrPath) {
3653
3954
  intro2(pc7.bgCyan(pc7.black(" One Workflow ")));
3654
- const spinner5 = createSpinner();
3655
- spinner5.start(`Validating "${keyOrPath}"...`);
3955
+ const spinner6 = createSpinner();
3956
+ spinner6.start(`Validating "${keyOrPath}"...`);
3656
3957
  let flowData;
3657
3958
  let rootDir;
3658
3959
  try {
@@ -3667,12 +3968,12 @@ async function flowValidateCommand(keyOrPath) {
3667
3968
  rootDir = path7.dirname(flowPath);
3668
3969
  }
3669
3970
  } catch (err) {
3670
- spinner5.stop("Validation failed");
3971
+ spinner6.stop("Validation failed");
3671
3972
  error(`Could not read workflow: ${err instanceof Error ? err.message : String(err)}`);
3672
3973
  }
3673
3974
  const errors = validateFlow(flowData, rootDir);
3674
3975
  if (errors.length > 0) {
3675
- spinner5.stop("Validation failed");
3976
+ spinner6.stop("Validation failed");
3676
3977
  if (isAgentMode()) {
3677
3978
  json({ valid: false, errors });
3678
3979
  process.exit(1);
@@ -3684,7 +3985,7 @@ async function flowValidateCommand(keyOrPath) {
3684
3985
  console.log();
3685
3986
  error(`${errors.length} validation error(s) found`);
3686
3987
  }
3687
- spinner5.stop("Workflow is valid");
3988
+ spinner6.stop("Workflow is valid");
3688
3989
  if (isAgentMode()) {
3689
3990
  json({ valid: true, key: flowData.key });
3690
3991
  return;
@@ -3718,11 +4019,11 @@ async function flowResumeCommand(runId) {
3718
4019
  json(event);
3719
4020
  }
3720
4021
  };
3721
- const spinner5 = createSpinner();
3722
- spinner5.start(`Resuming run ${runId} (${state.completedSteps.length} steps already completed)...`);
4022
+ const spinner6 = createSpinner();
4023
+ spinner6.start(`Resuming run ${runId} (${state.completedSteps.length} steps already completed)...`);
3723
4024
  try {
3724
4025
  const context = await runner.resume(flow2, api, permissions, actionIds, { onEvent, rootDir });
3725
- spinner5.stop("Workflow completed");
4026
+ spinner6.stop("Workflow completed");
3726
4027
  if (isAgentMode()) {
3727
4028
  json({
3728
4029
  event: "workflow:result",
@@ -3736,7 +4037,7 @@ async function flowResumeCommand(runId) {
3736
4037
  console.log(` ${pc7.green("\u2713")} Resumed and completed successfully`);
3737
4038
  console.log(` ${pc7.dim(`Log: ${runner.getLogPath()}`)}`);
3738
4039
  } catch (error2) {
3739
- spinner5.stop("Resume failed");
4040
+ spinner6.stop("Resume failed");
3740
4041
  const errorMsg = error2 instanceof Error ? error2.message : String(error2);
3741
4042
  if (isAgentMode()) {
3742
4043
  json({ event: "workflow:result", runId, status: "failed", error: errorMsg });
@@ -4023,8 +4324,8 @@ async function relayCreateCommand(options) {
4023
4324
  error(`Connection key "${options.connectionKey}" is not allowed.`);
4024
4325
  }
4025
4326
  const api = new OneApi(apiKey, getApiBase());
4026
- const spinner5 = createSpinner();
4027
- spinner5.start("Creating relay endpoint...");
4327
+ const spinner6 = createSpinner();
4328
+ spinner6.start("Creating relay endpoint...");
4028
4329
  try {
4029
4330
  const body = {
4030
4331
  connectionKey: options.connectionKey
@@ -4038,7 +4339,7 @@ async function relayCreateCommand(options) {
4038
4339
  json(result);
4039
4340
  return;
4040
4341
  }
4041
- spinner5.stop("Relay endpoint created");
4342
+ spinner6.stop("Relay endpoint created");
4042
4343
  console.log();
4043
4344
  console.log(` ${pc8.dim("ID:")} ${result.id}`);
4044
4345
  console.log(` ${pc8.dim("URL:")} ${result.url}`);
@@ -4048,15 +4349,15 @@ async function relayCreateCommand(options) {
4048
4349
  if (result.webhookPayload?.id) console.log(` ${pc8.dim("Webhook ID:")} ${result.webhookPayload.id}`);
4049
4350
  console.log();
4050
4351
  } catch (error2) {
4051
- spinner5.stop("Failed to create relay endpoint");
4352
+ spinner6.stop("Failed to create relay endpoint");
4052
4353
  error(`Error: ${error2 instanceof Error ? error2.message : "Unknown error"}`);
4053
4354
  }
4054
4355
  }
4055
4356
  async function relayListCommand(options) {
4056
4357
  const { apiKey } = getConfig3();
4057
4358
  const api = new OneApi(apiKey, getApiBase());
4058
- const spinner5 = createSpinner();
4059
- spinner5.start("Loading relay endpoints...");
4359
+ const spinner6 = createSpinner();
4360
+ spinner6.start("Loading relay endpoints...");
4060
4361
  try {
4061
4362
  const query = {};
4062
4363
  if (options.limit) query.limit = options.limit;
@@ -4079,7 +4380,7 @@ async function relayListCommand(options) {
4079
4380
  });
4080
4381
  return;
4081
4382
  }
4082
- spinner5.stop(`${endpoints.length} relay endpoint${endpoints.length === 1 ? "" : "s"} found`);
4383
+ spinner6.stop(`${endpoints.length} relay endpoint${endpoints.length === 1 ? "" : "s"} found`);
4083
4384
  if (endpoints.length === 0) {
4084
4385
  console.log("\n No relay endpoints yet.\n");
4085
4386
  return;
@@ -4101,22 +4402,22 @@ async function relayListCommand(options) {
4101
4402
  }))
4102
4403
  );
4103
4404
  } catch (error2) {
4104
- spinner5.stop("Failed to list relay endpoints");
4405
+ spinner6.stop("Failed to list relay endpoints");
4105
4406
  error(`Error: ${error2 instanceof Error ? error2.message : "Unknown error"}`);
4106
4407
  }
4107
4408
  }
4108
4409
  async function relayGetCommand(id) {
4109
4410
  const { apiKey } = getConfig3();
4110
4411
  const api = new OneApi(apiKey, getApiBase());
4111
- const spinner5 = createSpinner();
4112
- spinner5.start("Loading relay endpoint...");
4412
+ const spinner6 = createSpinner();
4413
+ spinner6.start("Loading relay endpoint...");
4113
4414
  try {
4114
4415
  const result = await api.getRelayEndpoint(id);
4115
4416
  if (isAgentMode()) {
4116
4417
  json(result);
4117
4418
  return;
4118
4419
  }
4119
- spinner5.stop("Relay endpoint loaded");
4420
+ spinner6.stop("Relay endpoint loaded");
4120
4421
  console.log();
4121
4422
  console.log(` ${pc8.dim("ID:")} ${result.id}`);
4122
4423
  console.log(` ${pc8.dim("URL:")} ${result.url}`);
@@ -4132,15 +4433,15 @@ async function relayGetCommand(id) {
4132
4433
  console.log(` ${pc8.dim("Created:")} ${result.createdAt}`);
4133
4434
  console.log();
4134
4435
  } catch (error2) {
4135
- spinner5.stop("Failed to load relay endpoint");
4436
+ spinner6.stop("Failed to load relay endpoint");
4136
4437
  error(`Error: ${error2 instanceof Error ? error2.message : "Unknown error"}`);
4137
4438
  }
4138
4439
  }
4139
4440
  async function relayUpdateCommand(id, options) {
4140
4441
  const { apiKey } = getConfig3();
4141
4442
  const api = new OneApi(apiKey, getApiBase());
4142
- const spinner5 = createSpinner();
4143
- spinner5.start("Updating relay endpoint...");
4443
+ const spinner6 = createSpinner();
4444
+ spinner6.start("Updating relay endpoint...");
4144
4445
  try {
4145
4446
  const body = {};
4146
4447
  if (options.description !== void 0) body.description = options.description;
@@ -4153,40 +4454,40 @@ async function relayUpdateCommand(id, options) {
4153
4454
  json(result);
4154
4455
  return;
4155
4456
  }
4156
- spinner5.stop("Relay endpoint updated");
4457
+ spinner6.stop("Relay endpoint updated");
4157
4458
  console.log(` ${pc8.dim("ID:")} ${result.id}`);
4158
4459
  console.log(` ${pc8.dim("Active:")} ${result.active}`);
4159
4460
  console.log(` ${pc8.dim("Actions:")} ${result.actions?.length || 0}`);
4160
4461
  console.log();
4161
4462
  } catch (error2) {
4162
- spinner5.stop("Failed to update relay endpoint");
4463
+ spinner6.stop("Failed to update relay endpoint");
4163
4464
  error(`Error: ${error2 instanceof Error ? error2.message : "Unknown error"}`);
4164
4465
  }
4165
4466
  }
4166
4467
  async function relayDeleteCommand(id) {
4167
4468
  const { apiKey } = getConfig3();
4168
4469
  const api = new OneApi(apiKey, getApiBase());
4169
- const spinner5 = createSpinner();
4170
- spinner5.start("Deleting relay endpoint...");
4470
+ const spinner6 = createSpinner();
4471
+ spinner6.start("Deleting relay endpoint...");
4171
4472
  try {
4172
4473
  const result = await api.deleteRelayEndpoint(id);
4173
4474
  if (isAgentMode()) {
4174
4475
  json({ deleted: true, id: result.id });
4175
4476
  return;
4176
4477
  }
4177
- spinner5.stop("Relay endpoint deleted");
4478
+ spinner6.stop("Relay endpoint deleted");
4178
4479
  console.log(` Deleted: ${result.id}`);
4179
4480
  console.log();
4180
4481
  } catch (error2) {
4181
- spinner5.stop("Failed to delete relay endpoint");
4482
+ spinner6.stop("Failed to delete relay endpoint");
4182
4483
  error(`Error: ${error2 instanceof Error ? error2.message : "Unknown error"}`);
4183
4484
  }
4184
4485
  }
4185
4486
  async function relayActivateCommand(id, options) {
4186
4487
  const { apiKey } = getConfig3();
4187
4488
  const api = new OneApi(apiKey, getApiBase());
4188
- const spinner5 = createSpinner();
4189
- spinner5.start("Activating relay endpoint...");
4489
+ const spinner6 = createSpinner();
4490
+ spinner6.start("Activating relay endpoint...");
4190
4491
  try {
4191
4492
  const actions2 = parseJsonArg2(options.actions, "--actions");
4192
4493
  const body = { actions: actions2 };
@@ -4196,21 +4497,21 @@ async function relayActivateCommand(id, options) {
4196
4497
  json(result);
4197
4498
  return;
4198
4499
  }
4199
- spinner5.stop("Relay endpoint activated");
4500
+ spinner6.stop("Relay endpoint activated");
4200
4501
  console.log(` ${pc8.dim("ID:")} ${result.id}`);
4201
4502
  console.log(` ${pc8.dim("Active:")} ${result.active}`);
4202
4503
  console.log(` ${pc8.dim("Actions:")} ${result.actions?.length || 0}`);
4203
4504
  console.log();
4204
4505
  } catch (error2) {
4205
- spinner5.stop("Failed to activate relay endpoint");
4506
+ spinner6.stop("Failed to activate relay endpoint");
4206
4507
  error(`Error: ${error2 instanceof Error ? error2.message : "Unknown error"}`);
4207
4508
  }
4208
4509
  }
4209
4510
  async function relayEventsCommand(options) {
4210
4511
  const { apiKey } = getConfig3();
4211
4512
  const api = new OneApi(apiKey, getApiBase());
4212
- const spinner5 = createSpinner();
4213
- spinner5.start("Loading relay events...");
4513
+ const spinner6 = createSpinner();
4514
+ spinner6.start("Loading relay events...");
4214
4515
  try {
4215
4516
  const query = {};
4216
4517
  if (options.limit) query.limit = options.limit;
@@ -4234,7 +4535,7 @@ async function relayEventsCommand(options) {
4234
4535
  });
4235
4536
  return;
4236
4537
  }
4237
- spinner5.stop(`${events.length} event${events.length === 1 ? "" : "s"} found`);
4538
+ spinner6.stop(`${events.length} event${events.length === 1 ? "" : "s"} found`);
4238
4539
  if (events.length === 0) {
4239
4540
  console.log("\n No events found.\n");
4240
4541
  return;
@@ -4254,22 +4555,22 @@ async function relayEventsCommand(options) {
4254
4555
  }))
4255
4556
  );
4256
4557
  } catch (error2) {
4257
- spinner5.stop("Failed to list relay events");
4558
+ spinner6.stop("Failed to list relay events");
4258
4559
  error(`Error: ${error2 instanceof Error ? error2.message : "Unknown error"}`);
4259
4560
  }
4260
4561
  }
4261
4562
  async function relayEventGetCommand(id) {
4262
4563
  const { apiKey } = getConfig3();
4263
4564
  const api = new OneApi(apiKey, getApiBase());
4264
- const spinner5 = createSpinner();
4265
- spinner5.start("Loading relay event...");
4565
+ const spinner6 = createSpinner();
4566
+ spinner6.start("Loading relay event...");
4266
4567
  try {
4267
4568
  const result = await api.getRelayEvent(id);
4268
4569
  if (isAgentMode()) {
4269
4570
  json(result);
4270
4571
  return;
4271
4572
  }
4272
- spinner5.stop("Relay event loaded");
4573
+ spinner6.stop("Relay event loaded");
4273
4574
  console.log();
4274
4575
  console.log(` ${pc8.dim("ID:")} ${result.id}`);
4275
4576
  console.log(` ${pc8.dim("Platform:")} ${result.platform}`);
@@ -4279,7 +4580,7 @@ async function relayEventGetCommand(id) {
4279
4580
  console.log(JSON.stringify(result.payload, null, 2));
4280
4581
  console.log();
4281
4582
  } catch (error2) {
4282
- spinner5.stop("Failed to load relay event");
4583
+ spinner6.stop("Failed to load relay event");
4283
4584
  error(`Error: ${error2 instanceof Error ? error2.message : "Unknown error"}`);
4284
4585
  }
4285
4586
  }
@@ -4289,8 +4590,8 @@ async function relayDeliveriesCommand(options) {
4289
4590
  }
4290
4591
  const { apiKey } = getConfig3();
4291
4592
  const api = new OneApi(apiKey, getApiBase());
4292
- const spinner5 = createSpinner();
4293
- spinner5.start("Loading deliveries...");
4593
+ const spinner6 = createSpinner();
4594
+ spinner6.start("Loading deliveries...");
4294
4595
  try {
4295
4596
  const deliveries = options.endpointId ? await api.listRelayEndpointDeliveries(options.endpointId) : await api.listRelayEventDeliveries(options.eventId);
4296
4597
  const items = Array.isArray(deliveries) ? deliveries : deliveries.rows || [];
@@ -4298,7 +4599,7 @@ async function relayDeliveriesCommand(options) {
4298
4599
  json({ deliveries: items });
4299
4600
  return;
4300
4601
  }
4301
- spinner5.stop(`${items.length} deliver${items.length === 1 ? "y" : "ies"} found`);
4602
+ spinner6.stop(`${items.length} deliver${items.length === 1 ? "y" : "ies"} found`);
4302
4603
  if (items.length === 0) {
4303
4604
  console.log("\n No deliveries found.\n");
4304
4605
  return;
@@ -4320,22 +4621,22 @@ async function relayDeliveriesCommand(options) {
4320
4621
  }))
4321
4622
  );
4322
4623
  } catch (error2) {
4323
- spinner5.stop("Failed to load deliveries");
4624
+ spinner6.stop("Failed to load deliveries");
4324
4625
  error(`Error: ${error2 instanceof Error ? error2.message : "Unknown error"}`);
4325
4626
  }
4326
4627
  }
4327
4628
  async function relayEventTypesCommand(platform) {
4328
4629
  const { apiKey } = getConfig3();
4329
4630
  const api = new OneApi(apiKey, getApiBase());
4330
- const spinner5 = createSpinner();
4331
- spinner5.start(`Loading event types for ${pc8.cyan(platform)}...`);
4631
+ const spinner6 = createSpinner();
4632
+ spinner6.start(`Loading event types for ${pc8.cyan(platform)}...`);
4332
4633
  try {
4333
4634
  const eventTypes = await api.listRelayEventTypes(platform);
4334
4635
  if (isAgentMode()) {
4335
4636
  json({ platform, eventTypes });
4336
4637
  return;
4337
4638
  }
4338
- spinner5.stop(`${eventTypes.length} event type${eventTypes.length === 1 ? "" : "s"} found`);
4639
+ spinner6.stop(`${eventTypes.length} event type${eventTypes.length === 1 ? "" : "s"} found`);
4339
4640
  if (eventTypes.length === 0) {
4340
4641
  console.log(`
4341
4642
  No event types found for ${platform}.
@@ -4348,7 +4649,7 @@ async function relayEventTypesCommand(platform) {
4348
4649
  }
4349
4650
  console.log();
4350
4651
  } catch (error2) {
4351
- spinner5.stop("Failed to load event types");
4652
+ spinner6.stop("Failed to load event types");
4352
4653
  error(`Error: ${error2 instanceof Error ? error2.message : "Unknown error"}`);
4353
4654
  }
4354
4655
  }
@@ -6741,7 +7042,7 @@ async function searchSyncedData(query, options) {
6741
7042
 
6742
7043
  // src/lib/sync/index.ts
6743
7044
  import { spawn as spawn4 } from "child_process";
6744
- import * as p7 from "@clack/prompts";
7045
+ import * as p8 from "@clack/prompts";
6745
7046
  import pc9 from "picocolors";
6746
7047
  async function syncInstallCommand() {
6747
7048
  if (await isSqliteAvailable()) {
@@ -6828,12 +7129,12 @@ async function syncProfilesCommand(platform) {
6828
7129
  const profiles = listBuiltinProfiles(platform);
6829
7130
  if (isAgentMode()) {
6830
7131
  json({
6831
- profiles: profiles.map((p8) => ({
6832
- platform: p8.platform,
6833
- model: p8.model,
6834
- description: p8.description,
6835
- hasEnrich: !!p8.enrich,
6836
- hasIdentityKey: !!p8.identityKey
7132
+ profiles: profiles.map((p10) => ({
7133
+ platform: p10.platform,
7134
+ model: p10.model,
7135
+ description: p10.description,
7136
+ hasEnrich: !!p10.enrich,
7137
+ hasIdentityKey: !!p10.identityKey
6837
7138
  })),
6838
7139
  total: profiles.length,
6839
7140
  _hint: profiles.length > 0 ? "Use a built-in profile: one --agent sync init <platform> <model>" : "No built-in profiles found. Use sync init to auto-infer from action knowledge."
@@ -6847,24 +7148,24 @@ async function syncProfilesCommand(platform) {
6847
7148
  );
6848
7149
  return;
6849
7150
  }
6850
- for (const p8 of profiles) {
7151
+ for (const p10 of profiles) {
6851
7152
  const extras = [];
6852
- if (p8.enrich) extras.push("enrich");
6853
- if (p8.identityKey) extras.push("identity");
6854
- if (p8.dateFilter) extras.push("incremental");
7153
+ if (p10.enrich) extras.push("enrich");
7154
+ if (p10.identityKey) extras.push("identity");
7155
+ if (p10.dateFilter) extras.push("incremental");
6855
7156
  const tags = extras.length > 0 ? ` ${pc9.dim(`[${extras.join(", ")}]`)}` : "";
6856
- console.log(` ${pc9.bold(`${p8.platform}/${p8.model}`.padEnd(35))} ${p8.description}${tags}`);
7157
+ console.log(` ${pc9.bold(`${p10.platform}/${p10.model}`.padEnd(35))} ${p10.description}${tags}`);
6857
7158
  }
6858
7159
  console.log(`
6859
7160
  ${profiles.length} built-in profile(s). Run ${pc9.bold("one sync init <platform> <model>")} to use one.`);
6860
7161
  }
6861
7162
  async function syncModelsCommand(platform) {
6862
7163
  const api = getApi();
6863
- const spinner5 = createSpinner();
6864
- spinner5.start(`Discovering models for ${platform}...`);
7164
+ const spinner6 = createSpinner();
7165
+ spinner6.start(`Discovering models for ${platform}...`);
6865
7166
  try {
6866
7167
  const models = await discoverModels(api, platform);
6867
- spinner5.stop(`Found ${models.length} models`);
7168
+ spinner6.stop(`Found ${models.length} models`);
6868
7169
  if (isAgentMode()) {
6869
7170
  json({ platform, models, total: models.length });
6870
7171
  return;
@@ -6878,15 +7179,15 @@ async function syncModelsCommand(platform) {
6878
7179
  );
6879
7180
  note2(lines.join("\n"), `${platform} \u2014 ${models.length} models`);
6880
7181
  } catch (err) {
6881
- spinner5.stop("Failed");
7182
+ spinner6.stop("Failed");
6882
7183
  error(`Error discovering models: ${err instanceof Error ? err.message : String(err)}`);
6883
7184
  }
6884
7185
  }
6885
7186
  async function syncInitCommand(platform, model, options) {
6886
7187
  if (!options.config) {
6887
7188
  const api = getApi();
6888
- const spinner5 = createSpinner();
6889
- spinner5.start(`Looking up ${platform}/${model}...`);
7189
+ const spinner6 = createSpinner();
7190
+ spinner6.start(`Looking up ${platform}/${model}...`);
6890
7191
  try {
6891
7192
  const models = await discoverModels(api, platform);
6892
7193
  const match = models.find((m) => m.name === model || m.name.toLowerCase() === model.toLowerCase());
@@ -6896,9 +7197,9 @@ async function syncInitCommand(platform, model, options) {
6896
7197
  if (actionId && !actionId.startsWith("conn_mod_def::")) {
6897
7198
  actionId = void 0;
6898
7199
  }
6899
- spinner5.stop(actionId ? "Found model + action ID" : "Found model (action ID not resolved)");
7200
+ spinner6.stop(actionId ? "Found model + action ID" : "Found model (action ID not resolved)");
6900
7201
  } else {
6901
- spinner5.stop("Model not found in available actions");
7202
+ spinner6.stop("Model not found in available actions");
6902
7203
  }
6903
7204
  const builtin = loadBuiltinProfile(platform, model);
6904
7205
  let template;
@@ -6998,7 +7299,7 @@ Run with --config to save:
6998
7299
  }
6999
7300
  }
7000
7301
  } catch (err) {
7001
- spinner5.stop("Failed");
7302
+ spinner6.stop("Failed");
7002
7303
  error(`Error: ${err instanceof Error ? err.message : String(err)}`);
7003
7304
  }
7004
7305
  return;
@@ -7084,7 +7385,7 @@ async function syncRunCommand(platform, options) {
7084
7385
  const api = getApi();
7085
7386
  const profiles = listProfiles(platform);
7086
7387
  const targetModels = options.models;
7087
- const toSync = targetModels ? profiles.filter((p8) => targetModels.includes(p8.model)) : profiles;
7388
+ const toSync = targetModels ? profiles.filter((p10) => targetModels.includes(p10.model)) : profiles;
7088
7389
  if (toSync.length === 0) {
7089
7390
  error(
7090
7391
  `No sync profiles found for ${platform}` + (targetModels ? ` with models: ${targetModels.join(", ")}` : "") + `. Run 'one sync init ${platform} <model> --config ...' first.`
@@ -7239,8 +7540,8 @@ async function syncDeleteCommand(platformModel, options) {
7239
7540
  return;
7240
7541
  }
7241
7542
  if (!options.yes && !isAgentMode()) {
7242
- const confirmed = await p7.confirm({ message: `Delete ${preview.count} record(s) from ${platform}/${model}?` });
7243
- if (p7.isCancel(confirmed) || !confirmed) {
7543
+ const confirmed = await p8.confirm({ message: `Delete ${preview.count} record(s) from ${platform}/${model}?` });
7544
+ if (p8.isCancel(confirmed) || !confirmed) {
7244
7545
  db.close();
7245
7546
  cancel2("Cancelled.");
7246
7547
  return;
@@ -7262,15 +7563,15 @@ async function syncDeleteCommand(platformModel, options) {
7262
7563
  async function syncListCommand(platform) {
7263
7564
  const profiles = listProfiles(platform);
7264
7565
  const state = readSyncState();
7265
- const syncs = profiles.map((p8) => {
7266
- const modelState = state[p8.platform]?.[p8.model];
7566
+ const syncs = profiles.map((p10) => {
7567
+ const modelState = state[p10.platform]?.[p10.model];
7267
7568
  return {
7268
- platform: p8.platform,
7269
- model: p8.model,
7569
+ platform: p10.platform,
7570
+ model: p10.model,
7270
7571
  lastSync: modelState?.lastSync ?? null,
7271
7572
  totalRecords: modelState?.totalRecords ?? 0,
7272
7573
  pagesProcessed: modelState?.pagesProcessed ?? 0,
7273
- dbSize: getDatabaseSize(p8.platform),
7574
+ dbSize: getDatabaseSize(p10.platform),
7274
7575
  status: modelState?.status ?? "idle"
7275
7576
  };
7276
7577
  });
@@ -7293,7 +7594,7 @@ async function syncRemoveCommand(platform, options) {
7293
7594
  const modelList = options.models?.split(",").map((m) => m.trim());
7294
7595
  const preview = [];
7295
7596
  const profiles = listProfiles(platform);
7296
- const targetModels = modelList ?? profiles.map((p8) => p8.model);
7597
+ const targetModels = modelList ?? profiles.map((p10) => p10.model);
7297
7598
  try {
7298
7599
  if (targetModels.length > 0) {
7299
7600
  const db = await openDatabase(platform);
@@ -7305,14 +7606,14 @@ async function syncRemoveCommand(platform, options) {
7305
7606
  } catch {
7306
7607
  for (const model of targetModels) preview.push({ model, records: 0 });
7307
7608
  }
7308
- const totalRecords = preview.reduce((sum, p8) => sum + p8.records, 0);
7609
+ const totalRecords = preview.reduce((sum, p10) => sum + p10.records, 0);
7309
7610
  const dbSize = getDatabaseSize(platform);
7310
7611
  if (options.dryRun) {
7311
7612
  if (isAgentMode()) {
7312
7613
  json({ dryRun: true, platform, models: preview, totalRecords, dbSize });
7313
7614
  } else {
7314
7615
  note2(
7315
- preview.map((p8) => ` ${p8.model.padEnd(30)} ${String(p8.records).padStart(8)} records`).join("\n") + `
7616
+ preview.map((p10) => ` ${p10.model.padEnd(30)} ${String(p10.records).padStart(8)} records`).join("\n") + `
7316
7617
 
7317
7618
  Total: ${totalRecords} records across ${preview.length} model(s), ${dbSize} on disk`,
7318
7619
  `Would remove from ${platform}`
@@ -7322,10 +7623,10 @@ async function syncRemoveCommand(platform, options) {
7322
7623
  }
7323
7624
  if (!options.yes && !isAgentMode()) {
7324
7625
  const target = modelList ? `${platform}/${modelList.join(", ")}` : `all synced data for ${platform}`;
7325
- const confirmed = await p7.confirm({
7626
+ const confirmed = await p8.confirm({
7326
7627
  message: `Remove ${target}? (${totalRecords} records, ${dbSize} on disk)`
7327
7628
  });
7328
- if (p7.isCancel(confirmed) || !confirmed) {
7629
+ if (p8.isCancel(confirmed) || !confirmed) {
7329
7630
  cancel2("Cancelled.");
7330
7631
  return;
7331
7632
  }
@@ -7617,8 +7918,8 @@ async function cacheUpdateAllCommand() {
7617
7918
  console.log("No cached entries to update");
7618
7919
  return;
7619
7920
  }
7620
- const spinner5 = createSpinner();
7621
- spinner5.start(`Updating ${entries.length} cached ${entries.length === 1 ? "entry" : "entries"}...`);
7921
+ const spinner6 = createSpinner();
7922
+ spinner6.start(`Updating ${entries.length} cached ${entries.length === 1 ? "entry" : "entries"}...`);
7622
7923
  let updated = 0;
7623
7924
  let failed = 0;
7624
7925
  const errors = [];
@@ -7642,7 +7943,7 @@ async function cacheUpdateAllCommand() {
7642
7943
  });
7643
7944
  }
7644
7945
  }
7645
- spinner5.stop(`Updated ${updated} ${updated === 1 ? "entry" : "entries"}${failed > 0 ? `, ${failed} failed` : ""}`);
7946
+ spinner6.stop(`Updated ${updated} ${updated === 1 ? "entry" : "entries"}${failed > 0 ? `, ${failed} failed` : ""}`);
7646
7947
  if (isAgentMode()) {
7647
7948
  json({ updated, failed, errors: errors.length > 0 ? errors : void 0 });
7648
7949
  return;
@@ -7663,10 +7964,12 @@ var GUIDE_OVERVIEW = `# One CLI \u2014 Agent Guide
7663
7964
 
7664
7965
  ## Setup
7665
7966
 
7666
- 1. Run \`one init\` to configure your API key (interactive \u2014 can be global or per-project)
7967
+ 1. Run \`one init\` for full interactive setup (authentication, skill installation, and platform connections)
7667
7968
  2. Run \`one add <platform>\` to connect platforms via OAuth
7668
7969
  3. Run \`one --agent connection list\` to verify connections
7669
7970
 
7971
+ You can also use \`one login\` / \`one logout\` to manage authentication separately (global or per-directory).
7972
+
7670
7973
  ## The --agent Flag
7671
7974
 
7672
7975
  Always use \`--agent\` for machine-readable JSON output. It disables colors, spinners, and interactive prompts.
@@ -7720,7 +8023,7 @@ one --agent flow list # List all workflows
7720
8023
  \`\`\`
7721
8024
 
7722
8025
  **Key concepts:**
7723
- - Workflows live at \`.one/flows/<key>/flow.json\` (folder layout \u2014 REQUIRED for new flows). The legacy \`.one/flows/<key>.flow.json\` single-file layout is DEPRECATED but still loads for backward compatibility
8026
+ - Workflows live at \`.one/flows/<key>/flow.json\` (folder layout \u2014 REQUIRED for new flows). Flows can be organized into subdirectory groups: \`.one/flows/<group>/<key>/flow.json\`. Reference them as \`group/key\` or just the bare key. The legacy \`.one/flows/<key>.flow.json\` single-file layout is DEPRECATED but still loads for backward compatibility
7724
8027
  - Code steps can reference an external \`.mjs\` module under the flow's \`lib/\` folder (stdin JSON in, stdout JSON out) \u2014 keeps JS out of JSON strings and makes flows shareable
7725
8028
  - 12 step types: action, transform, code, condition, loop, parallel, file-read, file-write, while, flow, paginate, bash
7726
8029
  - Data wiring via selectors: \`$.input.param\`, \`$.steps.stepId.response\`, \`$.loop.item\`
@@ -8392,7 +8695,7 @@ function pairKey(a, b) {
8392
8695
  }
8393
8696
  function getWorkflowExamples(connectedPlatforms) {
8394
8697
  const results = [];
8395
- const platforms = connectedPlatforms.map((p8) => p8.toLowerCase());
8698
+ const platforms = connectedPlatforms.map((p10) => p10.toLowerCase());
8396
8699
  for (let i = 0; i < platforms.length; i++) {
8397
8700
  for (let j = i + 1; j < platforms.length; j++) {
8398
8701
  const key = pairKey(platforms[i], platforms[j]);
@@ -8636,13 +8939,13 @@ function buildDemoActions(connections) {
8636
8939
  const connectedPlatforms = connections.map((c) => c.platform.toLowerCase());
8637
8940
  const popularPlatforms = ["gmail", "google-calendar", "slack", "shopify", "hub-spot", "github"];
8638
8941
  const platformsToShow = [
8639
- ...connectedPlatforms.filter((p8) => PLATFORM_DEMO_ACTIONS[p8]),
8640
- ...popularPlatforms.filter((p8) => !connectedPlatforms.includes(p8))
8942
+ ...connectedPlatforms.filter((p10) => PLATFORM_DEMO_ACTIONS[p10]),
8943
+ ...popularPlatforms.filter((p10) => !connectedPlatforms.includes(p10))
8641
8944
  ];
8642
8945
  const seen = /* @__PURE__ */ new Set();
8643
- const unique = platformsToShow.filter((p8) => {
8644
- if (seen.has(p8)) return false;
8645
- seen.add(p8);
8946
+ const unique = platformsToShow.filter((p10) => {
8947
+ if (seen.has(p10)) return false;
8948
+ seen.add(p10);
8646
8949
  return true;
8647
8950
  }).slice(0, 6);
8648
8951
  for (const platform of unique) {
@@ -8673,6 +8976,125 @@ function buildWorkflowIdeas(connections) {
8673
8976
  return lines.join("\n");
8674
8977
  }
8675
8978
 
8979
+ // src/commands/logout.ts
8980
+ import fs16 from "fs";
8981
+ import * as p9 from "@clack/prompts";
8982
+ function formatWhoami(config2, apiKey, pc12) {
8983
+ const whoami = config2.whoami;
8984
+ const env = getEnvFromApiKey(apiKey);
8985
+ const envLabel = env === "test" ? pc12.yellow("test") : pc12.green("live");
8986
+ const lines = [];
8987
+ if (whoami) {
8988
+ const contextParts = [];
8989
+ if (whoami.organization) contextParts.push(whoami.organization.name);
8990
+ if (whoami.project) contextParts.push(whoami.project.name);
8991
+ const scopeDisplay = contextParts.length > 0 ? contextParts.join(" / ") : "Personal";
8992
+ lines.push(`${pc12.bold(scopeDisplay)} ${pc12.dim("\xB7")} ${envLabel}`);
8993
+ lines.push(`${whoami.user.name} ${pc12.dim(`(${whoami.user.email})`)}`);
8994
+ } else {
8995
+ lines.push(`${pc12.dim("Key:")} ${apiKey.slice(0, 8)}... ${pc12.dim("\xB7")} ${envLabel}`);
8996
+ }
8997
+ return lines;
8998
+ }
8999
+ async function logoutCommand() {
9000
+ const apiKey = getApiKey();
9001
+ if (!apiKey) {
9002
+ if (isAgentMode()) {
9003
+ json({ error: "Not logged in." });
9004
+ process.exit(1);
9005
+ }
9006
+ error("Not logged in. Run: one login");
9007
+ return;
9008
+ }
9009
+ if (isAgentMode()) {
9010
+ const globalPath = getGlobalConfigPath();
9011
+ const projectPath = getProjectConfigPath();
9012
+ let cleared = false;
9013
+ if (fs16.existsSync(projectPath)) {
9014
+ fs16.unlinkSync(projectPath);
9015
+ cleared = true;
9016
+ }
9017
+ if (fs16.existsSync(globalPath)) {
9018
+ fs16.unlinkSync(globalPath);
9019
+ cleared = true;
9020
+ }
9021
+ json({ status: cleared ? "logged_out" : "not_logged_in", message: cleared ? "Credentials cleared." : "No config found." });
9022
+ return;
9023
+ }
9024
+ const pc12 = (await import("picocolors")).default;
9025
+ const globalConfig = readGlobalConfig();
9026
+ const projectConfig = readProjectConfig();
9027
+ const hasGlobal = globalConfig?.apiKey != null;
9028
+ const hasProject = projectConfig?.apiKey != null;
9029
+ let targetScope;
9030
+ if (hasGlobal && hasProject) {
9031
+ const infoLines = ["You are logged in with multiple configs.", ""];
9032
+ if (projectConfig) {
9033
+ infoLines.push(`${pc12.cyan("Local config:")}`);
9034
+ infoLines.push(...formatWhoami(projectConfig, projectConfig.apiKey, pc12));
9035
+ infoLines.push("");
9036
+ }
9037
+ if (globalConfig) {
9038
+ infoLines.push(`${pc12.magenta("Global config:")}`);
9039
+ infoLines.push(...formatWhoami(globalConfig, globalConfig.apiKey, pc12));
9040
+ }
9041
+ p9.note(infoLines.join("\n"));
9042
+ const choice = await p9.select({
9043
+ message: "What would you like to log out of?",
9044
+ options: [
9045
+ { value: "project", label: "This directory", hint: "local config only" },
9046
+ { value: "global", label: "Globally", hint: "global config only" },
9047
+ { value: "both", label: "Both", hint: "remove all credentials" }
9048
+ ]
9049
+ });
9050
+ if (p9.isCancel(choice)) {
9051
+ p9.cancel("Logout cancelled.");
9052
+ return;
9053
+ }
9054
+ targetScope = choice;
9055
+ } else if (hasProject) {
9056
+ const infoLines = ["You are logged in.", ""];
9057
+ infoLines.push(`${pc12.dim("Stored in")} ${pc12.cyan("local config")}`);
9058
+ infoLines.push(...formatWhoami(projectConfig, projectConfig.apiKey, pc12));
9059
+ p9.note(infoLines.join("\n"));
9060
+ targetScope = "project";
9061
+ } else if (hasGlobal) {
9062
+ const infoLines = ["You are logged in.", ""];
9063
+ infoLines.push(`${pc12.dim("Stored in")} ${pc12.magenta("global config")}`);
9064
+ infoLines.push(...formatWhoami(globalConfig, globalConfig.apiKey, pc12));
9065
+ p9.note(infoLines.join("\n"));
9066
+ targetScope = "global";
9067
+ } else {
9068
+ p9.log.warn("Credentials are set via environment variable or .onerc, not a config file.");
9069
+ p9.log.info("Remove the ONE_SECRET variable from your environment or .onerc file.");
9070
+ p9.outro("Nothing to remove.");
9071
+ return;
9072
+ }
9073
+ const scopeLabels = {
9074
+ project: "local",
9075
+ global: "global",
9076
+ both: "all"
9077
+ };
9078
+ const confirm6 = await p9.confirm({
9079
+ message: `Remove ${scopeLabels[targetScope]} credentials?`
9080
+ });
9081
+ if (p9.isCancel(confirm6) || !confirm6) {
9082
+ p9.cancel("Logout cancelled.");
9083
+ return;
9084
+ }
9085
+ if (targetScope === "project" || targetScope === "both") {
9086
+ const projectPath = getProjectConfigPath();
9087
+ if (fs16.existsSync(projectPath)) fs16.unlinkSync(projectPath);
9088
+ }
9089
+ if (targetScope === "global" || targetScope === "both") {
9090
+ const globalPath = getGlobalConfigPath();
9091
+ if (fs16.existsSync(globalPath)) fs16.unlinkSync(globalPath);
9092
+ }
9093
+ p9.log.success("Credentials cleared.");
9094
+ p9.log.info("Your API key is still active. Manage keys at app.withone.ai/settings");
9095
+ p9.outro("Logged out.");
9096
+ }
9097
+
8676
9098
  // src/index.ts
8677
9099
  var require3 = createRequire2(import.meta.url);
8678
9100
  var { version } = require3("../package.json");
@@ -8680,6 +9102,8 @@ var program = new Command();
8680
9102
  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.
8681
9103
 
8682
9104
  Setup:
9105
+ one login Authenticate via browser (opens app.withone.ai)
9106
+ one logout Clear local credentials
8683
9107
  one init Set up API key and install MCP server
8684
9108
  one add <platform> Connect a platform via OAuth (e.g. gmail, slack, shopify)
8685
9109
  one connection delete <key> Remove a connection (alias: one connection rm)
@@ -8697,8 +9121,8 @@ program.name("one").option("--agent", "Machine-readable JSON output (no colors,
8697
9121
 
8698
9122
  Workflows (multi-step):
8699
9123
  one flow list List saved workflows
8700
- one flow create [key] Create a workflow from JSON
8701
- one flow execute <key> Execute a workflow
9124
+ one flow create [key] Create a workflow from JSON (key can be group/key)
9125
+ one flow execute <key> Execute a workflow (key can be group/key)
8702
9126
  one flow validate <key> Validate a flow
8703
9127
 
8704
9128
  Data Sync (run "one sync install" first, then "one guide sync" for full reference):
@@ -8766,6 +9190,12 @@ program.hook("postAction", async () => {
8766
9190
  program.command("init").description("Set up One and install MCP to your AI agents (interactive: picks global or project scope)").option("-y, --yes", "Skip confirmations").option("-g, --global", "Write the One config globally (~/.one/config.json) \u2014 skips the scope picker").option("-p, --project", "Write the One config for this project only (~/.one/projects/<slug>/) \u2014 skips the scope picker").action(async (options) => {
8767
9191
  await initCommand(options);
8768
9192
  });
9193
+ program.command("login").description("Authenticate with One via browser").action(async () => {
9194
+ await loginCommand();
9195
+ });
9196
+ program.command("logout").description("Clear local credentials").action(async () => {
9197
+ await logoutCommand();
9198
+ });
8769
9199
  var config = program.command("config").description("Configure the CLI (access control, skills, ...)").action(async () => {
8770
9200
  await configCommand();
8771
9201
  });
@@ -8855,18 +9285,18 @@ config.command("reset").description("Remove the project config for the current d
8855
9285
  return;
8856
9286
  }
8857
9287
  if (!isAgentMode()) {
8858
- const p8 = await import("@clack/prompts");
8859
- const confirmed = await p8.confirm({
9288
+ const p10 = await import("@clack/prompts");
9289
+ const confirmed = await p10.confirm({
8860
9290
  message: "Delete the global config? This removes your API key for all projects without a project config.",
8861
9291
  initialValue: false
8862
9292
  });
8863
- if (p8.isCancel(confirmed) || !confirmed) {
9293
+ if (p10.isCancel(confirmed) || !confirmed) {
8864
9294
  console.log("Cancelled.");
8865
9295
  return;
8866
9296
  }
8867
9297
  }
8868
- const fs17 = await import("fs");
8869
- fs17.unlinkSync(globalPath);
9298
+ const fs18 = await import("fs");
9299
+ fs18.unlinkSync(globalPath);
8870
9300
  if (isAgentMode()) {
8871
9301
  json({ deleted: true, scope: "global" });
8872
9302
  } else {
@@ -8883,12 +9313,12 @@ config.command("reset").description("Remove the project config for the current d
8883
9313
  }
8884
9314
  return;
8885
9315
  }
8886
- const fs16 = await import("fs");
8887
- const configContent = fs16.readFileSync(resolved.path, "utf-8");
8888
- fs16.unlinkSync(resolved.path);
9316
+ const fs17 = await import("fs");
9317
+ const configContent = fs17.readFileSync(resolved.path, "utf-8");
9318
+ fs17.unlinkSync(resolved.path);
8889
9319
  const next = resolveConfig();
8890
- fs16.mkdirSync(path16.dirname(resolved.path), { recursive: true });
8891
- fs16.writeFileSync(resolved.path, configContent);
9320
+ fs17.mkdirSync(path16.dirname(resolved.path), { recursive: true });
9321
+ fs17.writeFileSync(resolved.path, configContent);
8892
9322
  let fallbackLabel;
8893
9323
  if (next.scope === "project") {
8894
9324
  fallbackLabel = `parent project config (${path16.basename(next.projectRoot)})`;
@@ -8898,19 +9328,19 @@ config.command("reset").description("Remove the project config for the current d
8898
9328
  fallbackLabel = "no config";
8899
9329
  }
8900
9330
  if (!isAgentMode()) {
8901
- const p8 = await import("@clack/prompts");
8902
- const confirmed = await p8.confirm({
9331
+ const p10 = await import("@clack/prompts");
9332
+ const confirmed = await p10.confirm({
8903
9333
  message: `Delete project config for ${path16.basename(resolved.projectRoot)}? Will fall back to ${fallbackLabel}.`,
8904
9334
  initialValue: false
8905
9335
  });
8906
- if (p8.isCancel(confirmed) || !confirmed) {
9336
+ if (p10.isCancel(confirmed) || !confirmed) {
8907
9337
  console.log("Cancelled.");
8908
9338
  return;
8909
9339
  }
8910
9340
  }
8911
- fs16.unlinkSync(resolved.path);
9341
+ fs17.unlinkSync(resolved.path);
8912
9342
  try {
8913
- fs16.rmdirSync(path16.dirname(resolved.path));
9343
+ fs17.rmdirSync(path16.dirname(resolved.path));
8914
9344
  } catch {
8915
9345
  }
8916
9346
  if (isAgentMode()) {