@withone/cli 2.3.2 → 2.5.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
@@ -28,7 +28,7 @@ import {
28
28
  validateActionInput,
29
29
  walkSteps,
30
30
  writeCache
31
- } from "./chunk-6FA7KUNQ.js";
31
+ } from "./chunk-MRQ47KSZ.js";
32
32
  import {
33
33
  memSqlCommand
34
34
  } from "./chunk-7FPQOAQ4.js";
@@ -147,7 +147,7 @@ import {
147
147
 
148
148
  // src/cli.ts
149
149
  import { createRequire as createRequire3 } from "module";
150
- import path13 from "path";
150
+ import path15 from "path";
151
151
  import { Command } from "commander";
152
152
 
153
153
  // src/commands/init.ts
@@ -1686,25 +1686,25 @@ async function loginCommand() {
1686
1686
  let targetScope = "global";
1687
1687
  const existingKey = getApiKey();
1688
1688
  if (existingKey) {
1689
- const pc17 = (await import("picocolors")).default;
1689
+ const pc18 = (await import("picocolors")).default;
1690
1690
  const resolved = resolveConfig();
1691
1691
  const whoami2 = resolved.config?.whoami;
1692
1692
  const env2 = getEnvFromApiKey(existingKey);
1693
- const envLabel2 = env2 === "test" ? pc17.yellow("test") : pc17.green("live");
1694
- const currentScope = resolved.scope === "project" ? pc17.cyan("local config") : pc17.magenta("global config");
1693
+ const envLabel2 = env2 === "test" ? pc18.yellow("test") : pc18.green("live");
1694
+ const currentScope = resolved.scope === "project" ? pc18.cyan("local config") : pc18.magenta("global config");
1695
1695
  const lines = ["You are already logged in.", ""];
1696
1696
  if (whoami2) {
1697
1697
  const contextParts2 = [];
1698
1698
  if (whoami2.organization) contextParts2.push(whoami2.organization.name);
1699
1699
  if (whoami2.project) contextParts2.push(whoami2.project.name);
1700
1700
  const scopeDisplay2 = contextParts2.length > 0 ? contextParts2.join(" / ") : "Personal";
1701
- lines.push(`${pc17.bold(scopeDisplay2)} ${pc17.dim("\xB7")} ${envLabel2}`);
1702
- lines.push(`${whoami2.user.name} ${pc17.dim(`(${whoami2.user.email})`)}`);
1703
- if (whoami2.organization) lines.push(`${pc17.dim("Org:")} ${whoami2.organization.name}`);
1704
- if (whoami2.project) lines.push(`${pc17.dim("Project:")} ${whoami2.project.name}`);
1701
+ lines.push(`${pc18.bold(scopeDisplay2)} ${pc18.dim("\xB7")} ${envLabel2}`);
1702
+ lines.push(`${whoami2.user.name} ${pc18.dim(`(${whoami2.user.email})`)}`);
1703
+ if (whoami2.organization) lines.push(`${pc18.dim("Org:")} ${whoami2.organization.name}`);
1704
+ if (whoami2.project) lines.push(`${pc18.dim("Project:")} ${whoami2.project.name}`);
1705
1705
  }
1706
1706
  lines.push("");
1707
- lines.push(`${pc17.dim("Stored in")} ${currentScope}`);
1707
+ lines.push(`${pc18.dim("Stored in")} ${currentScope}`);
1708
1708
  p2.note(lines.join("\n"));
1709
1709
  const scopeChoice = await p2.select({
1710
1710
  message: "Where would you like to log in?",
@@ -1723,42 +1723,42 @@ async function loginCommand() {
1723
1723
  if (!result) return;
1724
1724
  const { apiKey, whoami, keyName, grant, knowledgeOnly } = result;
1725
1725
  saveCredentials(apiKey, targetScope, { keyName, whoami, knowledgeOnly });
1726
- const pc16 = (await import("picocolors")).default;
1726
+ const pc17 = (await import("picocolors")).default;
1727
1727
  const env = getEnvFromApiKey(apiKey);
1728
1728
  const contextParts = [];
1729
1729
  if (whoami.organization) contextParts.push(whoami.organization.name);
1730
1730
  if (whoami.project) contextParts.push(whoami.project.name);
1731
1731
  const scopeDisplay = contextParts.length > 0 ? contextParts.join(" / ") : "Personal";
1732
- const envLabel = env === "test" ? pc16.yellow("test") : pc16.green("live");
1733
- const configLabel = targetScope === "project" ? pc16.cyan("local config") : pc16.magenta("global config");
1732
+ const envLabel = env === "test" ? pc17.yellow("test") : pc17.green("live");
1733
+ const configLabel = targetScope === "project" ? pc17.cyan("local config") : pc17.magenta("global config");
1734
1734
  const infoLines = [
1735
- `${pc16.bold(scopeDisplay)} ${pc16.dim("\xB7")} ${envLabel}`,
1736
- `${whoami.user.name} ${pc16.dim(`(${whoami.user.email})`)}`
1735
+ `${pc17.bold(scopeDisplay)} ${pc17.dim("\xB7")} ${envLabel}`,
1736
+ `${whoami.user.name} ${pc17.dim(`(${whoami.user.email})`)}`
1737
1737
  ];
1738
- if (keyName) infoLines.push(`${pc16.dim("Key:")} ${keyName}`);
1739
- infoLines.push(...grantNoteLines(grant, pc16.dim("Access:")));
1740
- if (whoami.organization) infoLines.push(`${pc16.dim("Org:")} ${whoami.organization.name}`);
1741
- if (whoami.project) infoLines.push(`${pc16.dim("Project:")} ${whoami.project.name}`);
1738
+ if (keyName) infoLines.push(`${pc17.dim("Key:")} ${keyName}`);
1739
+ infoLines.push(...grantNoteLines(grant, pc17.dim("Access:")));
1740
+ if (whoami.organization) infoLines.push(`${pc17.dim("Org:")} ${whoami.organization.name}`);
1741
+ if (whoami.project) infoLines.push(`${pc17.dim("Project:")} ${whoami.project.name}`);
1742
1742
  infoLines.push("");
1743
- infoLines.push(`${pc16.dim("Stored in")} ${configLabel}`);
1743
+ infoLines.push(`${pc17.dim("Stored in")} ${configLabel}`);
1744
1744
  p2.note(infoLines.join("\n"), "Logged in");
1745
1745
  console.log();
1746
- console.log(` ${pc16.dim("Next steps:")}`);
1747
- console.log(` ${pc16.cyan("one add <platform>")} ${pc16.dim("\u2014")} Connect a platform (e.g. gmail, slack, stripe)`);
1746
+ console.log(` ${pc17.dim("Next steps:")}`);
1747
+ console.log(` ${pc17.cyan("one add <platform>")} ${pc17.dim("\u2014")} Connect a platform (e.g. gmail, slack, stripe)`);
1748
1748
  console.log();
1749
- console.log(pc16.bold(" Copy-paste this to your AI agent:"));
1749
+ console.log(pc17.bold(" Copy-paste this to your AI agent:"));
1750
1750
  console.log();
1751
- console.log(pc16.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"));
1752
- console.log(pc16.cyan(" \u2502"));
1753
- console.log(pc16.cyan(" \u2502 The One CLI (`one`) is now installed on this machine. It"));
1754
- console.log(pc16.cyan(" \u2502 lets you interact with 750+ third-party platforms (Gmail,"));
1755
- console.log(pc16.cyan(" \u2502 Slack, Shopify, HubSpot, Stripe, GitHub, etc.) through"));
1756
- console.log(pc16.cyan(" \u2502 their APIs. Whenever you need to interact with any"));
1757
- console.log(pc16.cyan(" \u2502 third-party platform or external service, use the `one`"));
1758
- console.log(pc16.cyan(" \u2502 skill. Please save this to your project instructions or"));
1759
- console.log(pc16.cyan(" \u2502 memory so you remember it in future conversations."));
1760
- console.log(pc16.cyan(" \u2502"));
1761
- console.log(pc16.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"));
1751
+ console.log(pc17.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"));
1752
+ console.log(pc17.cyan(" \u2502"));
1753
+ console.log(pc17.cyan(" \u2502 The One CLI (`one`) is now installed on this machine. It"));
1754
+ console.log(pc17.cyan(" \u2502 lets you interact with 750+ third-party platforms (Gmail,"));
1755
+ console.log(pc17.cyan(" \u2502 Slack, Shopify, HubSpot, Stripe, GitHub, etc.) through"));
1756
+ console.log(pc17.cyan(" \u2502 their APIs. Whenever you need to interact with any"));
1757
+ console.log(pc17.cyan(" \u2502 third-party platform or external service, use the `one`"));
1758
+ console.log(pc17.cyan(" \u2502 skill. Please save this to your project instructions or"));
1759
+ console.log(pc17.cyan(" \u2502 memory so you remember it in future conversations."));
1760
+ console.log(pc17.cyan(" \u2502"));
1761
+ console.log(pc17.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"));
1762
1762
  console.log();
1763
1763
  p2.outro("Happy building!");
1764
1764
  }
@@ -2610,21 +2610,91 @@ async function resolveAllowedActions(api, actionIds) {
2610
2610
  );
2611
2611
  return resolved.filter((a) => a !== null);
2612
2612
  }
2613
- function computeConnectionAccess(platform, permissions, allowedActionIds, grantedActions) {
2614
- if (!allowedActionIds.includes("*")) {
2615
- const actions2 = grantedActions.filter((a) => a.platform === platform).map(({ actionId, title, method }) => ({ actionId, title, method }));
2616
- return { policy: "actions", actions: actions2 };
2613
+ var UNRESTRICTED = { reachable: true, methods: null, actionIds: null };
2614
+ var UNREACHABLE = { reachable: false, methods: [], actionIds: [] };
2615
+ function serverEnvelope(doc, connectionKey) {
2616
+ if (!doc) return UNRESTRICTED;
2617
+ if (doc.rules === null) return { reachable: true, methods: doc.methods, actionIds: null };
2618
+ const rule = doc.rules.find((r) => r.connectionKey === connectionKey);
2619
+ if (!rule) return UNREACHABLE;
2620
+ return {
2621
+ reachable: true,
2622
+ methods: rule.methods ?? doc.methods,
2623
+ actionIds: rule.actionIds ?? null
2624
+ };
2625
+ }
2626
+ function localEnvelope(permissions, actionIds, connectionKeys, connectionKey) {
2627
+ if (!connectionKeys.includes("*") && !connectionKeys.includes(connectionKey)) return UNREACHABLE;
2628
+ return {
2629
+ reachable: true,
2630
+ methods: PERMISSION_METHODS[permissions],
2631
+ actionIds: actionIds.includes("*") ? null : actionIds
2632
+ };
2633
+ }
2634
+ function intersectList(a, b) {
2635
+ if (a === null) return b;
2636
+ if (b === null) return a;
2637
+ const upper = new Set(b.map((x) => x.toUpperCase()));
2638
+ return a.filter((x) => upper.has(x.toUpperCase()));
2639
+ }
2640
+ function intersectEnvelopes(a, b) {
2641
+ if (!a.reachable || !b.reachable) return UNREACHABLE;
2642
+ return {
2643
+ reachable: true,
2644
+ methods: intersectList(a.methods, b.methods),
2645
+ actionIds: a.actionIds === null ? b.actionIds : b.actionIds === null ? a.actionIds : a.actionIds.filter((id) => b.actionIds.includes(id))
2646
+ };
2647
+ }
2648
+ function envelopeToAccess(envelope, resolved, localMethods = null) {
2649
+ if (!envelope.reachable || envelope.methods !== null && envelope.methods.length === 0) {
2650
+ return { access: { policy: "actions", actions: [] }, unresolved: [] };
2651
+ }
2652
+ if (envelope.actionIds !== null) {
2653
+ const actions2 = envelope.actionIds.flatMap((id) => {
2654
+ const hit = resolved.find((a) => a.actionId === id);
2655
+ if (!hit) return [];
2656
+ if (localMethods && !localMethods.includes(hit.method.toUpperCase())) return [];
2657
+ return [{ actionId: hit.actionId, title: hit.title, method: hit.method }];
2658
+ });
2659
+ return {
2660
+ access: { policy: "actions", actions: actions2 },
2661
+ unresolved: envelope.actionIds.filter((id) => !resolved.some((a) => a.actionId === id))
2662
+ };
2617
2663
  }
2618
- const methods = PERMISSION_METHODS[permissions];
2619
- if (methods !== null) {
2620
- return { policy: "methods", methods };
2664
+ if (envelope.methods !== null) {
2665
+ return { access: { policy: "methods", methods: envelope.methods.map((m) => m.toUpperCase()) }, unresolved: [] };
2621
2666
  }
2622
- return { policy: "full" };
2667
+ return { access: { policy: "full" }, unresolved: [] };
2623
2668
  }
2624
- function formatAccess(access, maxActions = 2) {
2669
+ var READ_WRITE = ["GET", "PATCH", "POST", "PUT"];
2670
+ function accessLevel(access) {
2625
2671
  switch (access.policy) {
2626
2672
  case "full":
2627
2673
  return "full";
2674
+ case "methods": {
2675
+ const set = [...new Set(access.methods.map((m) => m.toUpperCase()))].sort();
2676
+ if (set.length === 0) return "none";
2677
+ if (set.length === 1 && set[0] === "GET") return "read-only";
2678
+ if (set.join(",") === READ_WRITE.join(",")) return "read-write";
2679
+ return "custom";
2680
+ }
2681
+ case "actions":
2682
+ return access.actions.length === 0 ? "none" : "custom";
2683
+ }
2684
+ }
2685
+ var LEVEL_LABELS = {
2686
+ full: "Full access",
2687
+ "read-write": "Read & write",
2688
+ "read-only": "Read only",
2689
+ custom: "Custom",
2690
+ none: "No access"
2691
+ };
2692
+ function formatAccess(access, maxActions = 2) {
2693
+ const level = accessLevel(access);
2694
+ if (level !== "custom") return LEVEL_LABELS[level];
2695
+ switch (access.policy) {
2696
+ case "full":
2697
+ return LEVEL_LABELS.full;
2628
2698
  case "methods":
2629
2699
  return access.methods.join(", ");
2630
2700
  case "actions": {
@@ -2847,18 +2917,30 @@ async function connectionListCommand(options) {
2847
2917
  const spinner = createSpinner();
2848
2918
  spinner.start("Loading connections...");
2849
2919
  try {
2850
- const allConnections = await api.listConnections();
2920
+ const [allConnections, serverAccess] = await Promise.all([
2921
+ api.listConnections(),
2922
+ api.getOwnAccess().then(
2923
+ (doc) => ({ ok: true, doc }),
2924
+ () => ({ ok: false, doc: null })
2925
+ )
2926
+ ]);
2851
2927
  const ac = getAccessControlFromAllSources();
2852
2928
  const permissions = ac.permissions || "admin";
2853
2929
  const allowedKeys = ac.connectionKeys || ["*"];
2854
2930
  const actionIds = ac.actionIds || ["*"];
2855
2931
  const knowledgeOnly = ac.knowledgeAgent || false;
2856
- const accessFiltered = allowedKeys.includes("*") ? allConnections : allConnections.filter((conn) => allowedKeys.includes(conn.key));
2857
- const resolvedActions = await resolveAllowedActions(api, actionIds);
2858
- const unresolvedActionIds = actionIds.includes("*") ? [] : actionIds.filter((id) => !resolvedActions.some((a) => a.actionId === id));
2859
- const grantedActions = resolvedActions.filter((a) => isMethodAllowed(a.method, permissions));
2860
- const accessFor = (platform) => computeConnectionAccess(platform, permissions, actionIds, grantedActions);
2861
- const hintText = accessHint(permissions, actionIds, knowledgeOnly);
2932
+ const envelopeFor = (key) => intersectEnvelopes(serverEnvelope(serverAccess.doc, key), localEnvelope(permissions, actionIds, allowedKeys, key));
2933
+ const accessFiltered = allConnections.filter((conn) => envelopeFor(conn.key).reachable);
2934
+ const namedActionIds = new Set(actionIds.includes("*") ? [] : actionIds);
2935
+ for (const rule of serverAccess.doc?.rules ?? []) for (const id of rule.actionIds ?? []) namedActionIds.add(id);
2936
+ const resolvedActions = await resolveAllowedActions(api, [...namedActionIds]);
2937
+ const unresolved = /* @__PURE__ */ new Set();
2938
+ const accessFor = (conn) => {
2939
+ const { access, unresolved: missing } = envelopeToAccess(envelopeFor(conn.key), resolvedActions, PERMISSION_METHODS[permissions]);
2940
+ missing.forEach((id) => unresolved.add(id));
2941
+ return access;
2942
+ };
2943
+ const hintText = accessHint(permissions, actionIds, knowledgeOnly, serverAccess.ok ? serverAccess.doc : void 0);
2862
2944
  const searchQuery = options?.search?.toLowerCase();
2863
2945
  const filtered = searchQuery ? accessFiltered.filter((conn) => conn.platform.toLowerCase().includes(searchQuery)) : accessFiltered;
2864
2946
  const limitArg = options?.limit ? parseInt(options.limit, 10) : void 0;
@@ -2875,10 +2957,11 @@ async function connectionListCommand(options) {
2875
2957
  key: conn.key,
2876
2958
  ...conn.name && { name: conn.name },
2877
2959
  ...conn.tags?.length && { tags: conn.tags },
2878
- access: accessFor(conn.platform)
2960
+ access: withLevel(accessFor(conn))
2879
2961
  })),
2962
+ accessSource: serverAccess.ok ? "server" : "local",
2880
2963
  ...knowledgeOnly && { knowledgeOnly: true },
2881
- ...unresolvedActionIds.length > 0 && { unresolvedActionIds },
2964
+ ...unresolved.size > 0 && { unresolvedActionIds: [...unresolved] },
2882
2965
  ...hintText && { accessHint: hintText },
2883
2966
  ...limited.length < filtered.length && {
2884
2967
  hint: `Showing ${limited.length} of ${filtered.length} connections. Use --search <query> to filter by platform or --limit <n> to see more.`
@@ -2896,6 +2979,13 @@ async function connectionListCommand(options) {
2896
2979
  Try: ${pc6.cyan("one connection list")} to see all connections.`,
2897
2980
  "No Results"
2898
2981
  );
2982
+ } else if (serverAccess.doc?.rules && allConnections.length === 0) {
2983
+ p4.note(
2984
+ `This key reaches no connections: it can search actions and read their docs, but runs none.
2985
+
2986
+ Change it in the dashboard under API keys.`,
2987
+ "No Connections"
2988
+ );
2899
2989
  } else {
2900
2990
  p4.note(
2901
2991
  `No connections yet.
@@ -2913,10 +3003,9 @@ Add one with: ${pc6.cyan("one connection add gmail")}`,
2913
3003
  state: conn.state,
2914
3004
  key: conn.key,
2915
3005
  tags: conn.tags?.length ? conn.tags.join(", ") : "",
2916
- access: formatAccess(accessFor(conn.platform))
3006
+ access: formatAccess(accessFor(conn))
2917
3007
  }));
2918
3008
  const hasTags = rows.some((r) => r.tags);
2919
- const hasScopedAccess = rows.some((r) => r.access !== "full");
2920
3009
  printTable(
2921
3010
  [
2922
3011
  { key: "status", label: "" },
@@ -2924,21 +3013,22 @@ Add one with: ${pc6.cyan("one connection add gmail")}`,
2924
3013
  { key: "state", label: "Status" },
2925
3014
  { key: "key", label: "Connection Key", color: pc6.dim },
2926
3015
  ...hasTags ? [{ key: "tags", label: "Tags", color: pc6.dim }] : [],
2927
- ...hasScopedAccess ? [{ key: "access", label: "Access", color: pc6.yellow }] : []
3016
+ { key: "access", label: serverAccess.ok ? "Access" : "Access (CLI settings only)", color: pc6.yellow }
2928
3017
  ],
2929
3018
  rows
2930
3019
  );
2931
3020
  console.log();
2932
- if (hintText) {
2933
- const lines = [hintText];
2934
- if (unresolvedActionIds.length > 0) {
2935
- lines.push(
2936
- `Could not resolve ${unresolvedActionIds.length} allowlisted action id(s): ${unresolvedActionIds.join(", ")}`
2937
- );
3021
+ if (hintText || unresolved.size > 0) {
3022
+ const lines = hintText ? [hintText] : [];
3023
+ if (unresolved.size > 0) {
3024
+ lines.push(`Could not resolve ${unresolved.size} allowed action id(s): ${[...unresolved].join(", ")}`);
2938
3025
  }
2939
- p4.note(`${wrapText(lines.join("\n"))}
3026
+ p4.note(
3027
+ `${wrapText(lines.join("\n"))}
2940
3028
 
2941
- Change it with: ${pc6.cyan("one config")}`, "Access");
3029
+ This key's access: dashboard \u2192 API keys. This CLI's own limits: ${pc6.cyan("one config")}`,
3030
+ "Access"
3031
+ );
2942
3032
  }
2943
3033
  if (displayed.length < filtered.length) {
2944
3034
  p4.note(
@@ -3012,21 +3102,35 @@ async function connectionDeleteCommand(connectionKey, options) {
3012
3102
  error(`Error: ${error2 instanceof Error ? error2.message : "Unknown error"}`);
3013
3103
  }
3014
3104
  }
3015
- function accessHint(permissions, actionIds, knowledgeOnly) {
3105
+ function accessHint(permissions, actionIds, knowledgeOnly, server) {
3016
3106
  const parts = [];
3107
+ if (server === void 0) {
3108
+ parts.push(
3109
+ "One did not report this key's access, so each `access` shows only this CLI's own settings; One may allow less. Check the key in the dashboard under API keys."
3110
+ );
3111
+ } else if (server?.rules && server.rules.length === 0) {
3112
+ parts.push("This key reaches no connections: it can read docs and search actions, but no action will execute.");
3113
+ } else if (server?.rules?.some((r) => r.actionIds)) {
3114
+ parts.push(
3115
+ "Some connections are action-scoped: their `access.actions` are the only actions you may run there \u2014 use them directly: `actions load <actionId>` reads their docs, no find needed."
3116
+ );
3117
+ }
3017
3118
  if (!actionIds.includes("*")) {
3018
3119
  parts.push(
3019
- "Action-scoped: each connection's `access.actions` are the only actions you may run \u2014 use them directly: `actions load <actionId>` reads their docs, no find needed."
3120
+ "Action-scoped (local settings): each connection's `access.actions` are the only actions you may run \u2014 use them directly: `actions load <actionId>` reads their docs, no find needed."
3020
3121
  );
3021
3122
  } else if (permissions !== "admin") {
3022
3123
  const methods = PERMISSION_METHODS[permissions]?.join(", ") ?? "";
3023
- parts.push(`Permission level "${permissions}": only ${methods} actions will execute.`);
3124
+ parts.push(`Permission level "${permissions}" (local settings): only ${methods} actions will execute.`);
3024
3125
  }
3025
3126
  if (knowledgeOnly) {
3026
3127
  parts.push("Knowledge-only mode: `actions execute` is disabled \u2014 read knowledge and write code instead.");
3027
3128
  }
3028
3129
  return parts.length > 0 ? parts.join(" ") : null;
3029
3130
  }
3131
+ function withLevel(access) {
3132
+ return { ...access, level: accessLevel(access) };
3133
+ }
3030
3134
  function wrapText(text5, width = 72) {
3031
3135
  return text5.split("\n").map((paragraph) => {
3032
3136
  const lines = [];
@@ -3789,7 +3893,7 @@ var CUSTOM_ACTION_NOTE = '\n\nThis is a custom action: ALSO include "connectionK
3789
3893
  function codeGuideAction(action, knowledge, platform, tags, apiBase) {
3790
3894
  const base = apiBase.replace(/\/$/, "");
3791
3895
  const method = action.method.toUpperCase();
3792
- const path14 = action.path.startsWith("/") ? action.path : `/${action.path}`;
3896
+ const path16 = action.path.startsWith("/") ? action.path : `/${action.path}`;
3793
3897
  const connEnv = `ONE_${platformEnvSegment(platform)}_CONNECTION_KEY`;
3794
3898
  const customNote = tags.includes("custom") ? CUSTOM_ACTION_NOTE : "";
3795
3899
  return `### ${actionHeading(action)}
@@ -3798,7 +3902,7 @@ function codeGuideAction(action, knowledge, platform, tags, apiBase) {
3798
3902
  ${knowledge ?? NO_KNOWLEDGE}
3799
3903
 
3800
3904
  ## Calling this action from code
3801
- URL: ${base}/passthrough${path14}
3905
+ URL: ${base}/passthrough${path16}
3802
3906
  Method: ${method}
3803
3907
  Headers:
3804
3908
  - x-one-secret: the value of the ONE_SECRET env var
@@ -3809,7 +3913,7 @@ Headers:
3809
3913
  Environment variables: ONE_SECRET (their One API key) and ${connEnv} (the key of their ${platform} connection), both from the One dashboard.
3810
3914
 
3811
3915
  \`\`\`typescript
3812
- const response = await fetch("${base}/passthrough${path14}", {
3916
+ const response = await fetch("${base}/passthrough${path16}", {
3813
3917
  method: "${method}",
3814
3918
  headers: {
3815
3919
  "x-one-secret": process.env.ONE_SECRET,
@@ -4554,32 +4658,32 @@ function validateStepsArray(steps, pathPrefix, errors) {
4554
4658
  const validTypes = FLOW_SCHEMA.stepTypes.map((st) => st.type);
4555
4659
  for (let i = 0; i < steps.length; i++) {
4556
4660
  const step = steps[i];
4557
- const path14 = `${pathPrefix}[${i}]`;
4661
+ const path16 = `${pathPrefix}[${i}]`;
4558
4662
  if (!step || typeof step !== "object" || Array.isArray(step)) {
4559
- errors.push({ path: path14, message: "Step must be an object" });
4663
+ errors.push({ path: path16, message: "Step must be an object" });
4560
4664
  continue;
4561
4665
  }
4562
4666
  const s = step;
4563
4667
  if (!s.id || typeof s.id !== "string") {
4564
- errors.push({ path: `${path14}.id`, message: 'Step must have a string "id"' });
4668
+ errors.push({ path: `${path16}.id`, message: 'Step must have a string "id"' });
4565
4669
  }
4566
4670
  if (!s.name || typeof s.name !== "string") {
4567
- errors.push({ path: `${path14}.name`, message: 'Step must have a string "name"' });
4671
+ errors.push({ path: `${path16}.name`, message: 'Step must have a string "name"' });
4568
4672
  }
4569
4673
  if (!s.type || !validTypes.includes(s.type)) {
4570
- errors.push({ path: `${path14}.type`, message: `Step type must be one of: ${validTypes.join(", ")}` });
4674
+ errors.push({ path: `${path16}.type`, message: `Step type must be one of: ${validTypes.join(", ")}` });
4571
4675
  continue;
4572
4676
  }
4573
4677
  if (s.requires !== void 0) {
4574
4678
  if (!Array.isArray(s.requires)) {
4575
- errors.push({ path: `${path14}.requires`, message: '"requires" must be an array of selector strings (e.g. ["$.steps.foo.output.bar"])' });
4679
+ errors.push({ path: `${path16}.requires`, message: '"requires" must be an array of selector strings (e.g. ["$.steps.foo.output.bar"])' });
4576
4680
  } else {
4577
4681
  for (let r = 0; r < s.requires.length; r++) {
4578
4682
  const sel = s.requires[r];
4579
4683
  if (typeof sel !== "string") {
4580
- errors.push({ path: `${path14}.requires[${r}]`, message: '"requires" entry must be a selector string' });
4684
+ errors.push({ path: `${path16}.requires[${r}]`, message: '"requires" entry must be a selector string' });
4581
4685
  } else if (!sel.startsWith("$.")) {
4582
- errors.push({ path: `${path14}.requires[${r}]`, message: `"requires" entry "${sel}" must be a selector starting with "$." (e.g. "$.steps.foo.output.bar")` });
4686
+ errors.push({ path: `${path16}.requires[${r}]`, message: `"requires" entry "${sel}" must be a selector starting with "$." (e.g. "$.steps.foo.output.bar")` });
4583
4687
  }
4584
4688
  }
4585
4689
  }
@@ -4587,7 +4691,7 @@ function validateStepsArray(steps, pathPrefix, errors) {
4587
4691
  if (s.onError && typeof s.onError === "object") {
4588
4692
  const oe = s.onError;
4589
4693
  if (!FLOW_SCHEMA.errorStrategies.includes(oe.strategy)) {
4590
- errors.push({ path: `${path14}.onError.strategy`, message: `Error strategy must be one of: ${FLOW_SCHEMA.errorStrategies.join(", ")}` });
4694
+ errors.push({ path: `${path16}.onError.strategy`, message: `Error strategy must be one of: ${FLOW_SCHEMA.errorStrategies.join(", ")}` });
4591
4695
  }
4592
4696
  }
4593
4697
  const descriptor = getStepTypeDescriptor(s.type);
@@ -4597,14 +4701,14 @@ function validateStepsArray(steps, pathPrefix, errors) {
4597
4701
  if (!configObj || typeof configObj !== "object") {
4598
4702
  const hint = detectFlatConfigHint(s, descriptor);
4599
4703
  errors.push({
4600
- path: `${path14}.${configKey}`,
4704
+ path: `${path16}.${configKey}`,
4601
4705
  message: `${capitalize(descriptor.type)} step must have a "${configKey}" config object${hint}`
4602
4706
  });
4603
4707
  continue;
4604
4708
  }
4605
4709
  const config2 = configObj;
4606
4710
  for (const [fieldName, fd] of Object.entries(descriptor.fields)) {
4607
- const fieldPath = `${path14}.${configKey}.${fieldName}`;
4711
+ const fieldPath = `${path16}.${configKey}.${fieldName}`;
4608
4712
  const value = config2[fieldName];
4609
4713
  if (fd.required && (value === void 0 || value === null || value === "")) {
4610
4714
  errors.push({ path: fieldPath, message: `${capitalize(descriptor.type)} must have ${fd.type === "string" ? "a string" : fd.type === "array" ? "a" : "a"} "${fieldName}"` });
@@ -4637,30 +4741,30 @@ function validateStepsArray(steps, pathPrefix, errors) {
4637
4741
  }
4638
4742
  }
4639
4743
  if (descriptor.type === "action") {
4640
- validateConnectionForm(config2, `${path14}.${configKey}`, errors);
4744
+ validateConnectionForm(config2, `${path16}.${configKey}`, errors);
4641
4745
  }
4642
4746
  if (descriptor.type === "code") {
4643
4747
  const hasSource = typeof config2.source === "string" && config2.source.length > 0;
4644
4748
  const hasModule = typeof config2.module === "string" && config2.module.length > 0;
4645
4749
  if (!hasSource && !hasModule) {
4646
- errors.push({ path: `${path14}.${configKey}`, message: 'Code step must define either "source" (inline JS) or "module" (path to .mjs file)' });
4750
+ errors.push({ path: `${path16}.${configKey}`, message: 'Code step must define either "source" (inline JS) or "module" (path to .mjs file)' });
4647
4751
  } else if (hasSource && hasModule) {
4648
- errors.push({ path: `${path14}.${configKey}`, message: 'Code step cannot define both "source" and "module" \u2014 pick one' });
4752
+ errors.push({ path: `${path16}.${configKey}`, message: 'Code step cannot define both "source" and "module" \u2014 pick one' });
4649
4753
  }
4650
4754
  if (hasModule) {
4651
4755
  const m = config2.module;
4652
4756
  if (m.startsWith("/") || /^[a-zA-Z]:[\\/]/.test(m)) {
4653
- errors.push({ path: `${path14}.${configKey}.module`, message: "Code module path must be relative to the flow folder (no absolute paths)" });
4757
+ errors.push({ path: `${path16}.${configKey}.module`, message: "Code module path must be relative to the flow folder (no absolute paths)" });
4654
4758
  } else if (m.split(/[\\/]/).includes("..")) {
4655
- errors.push({ path: `${path14}.${configKey}.module`, message: 'Code module path must not escape the flow folder ("..")' });
4759
+ errors.push({ path: `${path16}.${configKey}.module`, message: 'Code module path must not escape the flow folder ("..")' });
4656
4760
  } else if (!m.endsWith(".mjs")) {
4657
- errors.push({ path: `${path14}.${configKey}.module`, message: "Code module must be a .mjs file" });
4761
+ errors.push({ path: `${path16}.${configKey}.module`, message: "Code module must be a .mjs file" });
4658
4762
  }
4659
4763
  }
4660
4764
  if (hasSource) {
4661
4765
  const syntaxError = checkCodeSourceSyntax(config2.source);
4662
4766
  if (syntaxError) {
4663
- errors.push({ path: `${path14}.${configKey}.source`, message: `Syntax error in code step: ${syntaxError}` });
4767
+ errors.push({ path: `${path16}.${configKey}.source`, message: `Syntax error in code step: ${syntaxError}` });
4664
4768
  }
4665
4769
  }
4666
4770
  }
@@ -4732,16 +4836,16 @@ function validateStepIds(flow2) {
4732
4836
  function collectIds(steps, pathPrefix) {
4733
4837
  for (let i = 0; i < steps.length; i++) {
4734
4838
  const step = steps[i];
4735
- const path14 = `${pathPrefix}[${i}]`;
4839
+ const path16 = `${pathPrefix}[${i}]`;
4736
4840
  if (seen.has(step.id)) {
4737
- errors.push({ path: `${path14}.id`, message: `Duplicate step ID: "${step.id}"` });
4841
+ errors.push({ path: `${path16}.id`, message: `Duplicate step ID: "${step.id}"` });
4738
4842
  } else {
4739
4843
  seen.add(step.id);
4740
4844
  }
4741
4845
  for (const { configKey, fieldName } of nestedKeys) {
4742
4846
  const config2 = step[configKey];
4743
4847
  if (config2 && Array.isArray(config2[fieldName])) {
4744
- collectIds(config2[fieldName], `${path14}.${configKey}.${fieldName}`);
4848
+ collectIds(config2[fieldName], `${path16}.${configKey}.${fieldName}`);
4745
4849
  }
4746
4850
  }
4747
4851
  }
@@ -4789,7 +4893,7 @@ function validateSelectorReferences(flow2, rootDir) {
4789
4893
  }
4790
4894
  return selectors;
4791
4895
  }
4792
- function checkSelectors(selectors, path14, precedingStepIds) {
4896
+ function checkSelectors(selectors, path16, precedingStepIds) {
4793
4897
  for (const selector of selectors) {
4794
4898
  const parts = selector.split(".");
4795
4899
  if (parts.length < 3) continue;
@@ -4797,15 +4901,15 @@ function validateSelectorReferences(flow2, rootDir) {
4797
4901
  if (root === "input") {
4798
4902
  const inputName = parts[2];
4799
4903
  if (!inputNames.has(inputName)) {
4800
- errors.push({ path: path14, message: `Selector "${selector}" references undefined input "${inputName}"` });
4904
+ errors.push({ path: path16, message: `Selector "${selector}" references undefined input "${inputName}"` });
4801
4905
  }
4802
4906
  } else if (root === "steps") {
4803
4907
  const stepId = parts[2].replace(/[\[\]]/g, "").split(/[\[\]]/)[0];
4804
4908
  if (!allStepIds.has(stepId)) {
4805
- errors.push({ path: path14, message: `Selector "${selector}" references undefined step "${stepId}"` });
4909
+ errors.push({ path: path16, message: `Selector "${selector}" references undefined step "${stepId}"` });
4806
4910
  } else if (precedingStepIds && !precedingStepIds.has(stepId)) {
4807
4911
  errors.push({
4808
- path: path14,
4912
+ path: path16,
4809
4913
  message: `Selector "${selector}" references step "${stepId}" which is declared after the current step. Steps execute in declaration order, so this will always resolve to undefined at runtime \u2014 move the dependency earlier in the steps array.`
4810
4914
  });
4811
4915
  }
@@ -4813,20 +4917,20 @@ function validateSelectorReferences(flow2, rootDir) {
4813
4917
  }
4814
4918
  }
4815
4919
  const EXPRESSION_FIELDS = /* @__PURE__ */ new Set(["condition.expression", "while.condition"]);
4816
- function checkOperatorsInSelectorField(value, path14) {
4920
+ function checkOperatorsInSelectorField(value, path16) {
4817
4921
  if (typeof value === "string" && value.startsWith("$.")) {
4818
4922
  if (value.includes("||")) {
4819
- errors.push({ path: path14, message: `Selector "${value}" contains unsupported operator "||". Selectors in data fields use dot-path resolution, not JS evaluation. Use the "default" field on the input definition instead, or use a "code" step for complex expressions.` });
4923
+ errors.push({ path: path16, message: `Selector "${value}" contains unsupported operator "||". Selectors in data fields use dot-path resolution, not JS evaluation. Use the "default" field on the input definition instead, or use a "code" step for complex expressions.` });
4820
4924
  } else if (value.includes("&&")) {
4821
- errors.push({ path: path14, message: `Selector "${value}" contains unsupported operator "&&". Selectors in data fields use dot-path resolution, not JS evaluation. Use a "condition" step or "code" step for complex expressions.` });
4925
+ errors.push({ path: path16, message: `Selector "${value}" contains unsupported operator "&&". Selectors in data fields use dot-path resolution, not JS evaluation. Use a "condition" step or "code" step for complex expressions.` });
4822
4926
  }
4823
4927
  } else if (value && typeof value === "object" && !Array.isArray(value)) {
4824
4928
  for (const [k, v] of Object.entries(value)) {
4825
- checkOperatorsInSelectorField(v, `${path14}.${k}`);
4929
+ checkOperatorsInSelectorField(v, `${path16}.${k}`);
4826
4930
  }
4827
4931
  } else if (Array.isArray(value)) {
4828
4932
  for (let i = 0; i < value.length; i++) {
4829
- checkOperatorsInSelectorField(value[i], `${path14}[${i}]`);
4933
+ checkOperatorsInSelectorField(value[i], `${path16}[${i}]`);
4830
4934
  }
4831
4935
  }
4832
4936
  }
@@ -4911,10 +5015,10 @@ var VALID_OUTPUT_SCHEMA_TYPES = /* @__PURE__ */ new Set(["string", "number", "bo
4911
5015
  function isOutputSchemaObject(v) {
4912
5016
  return !!v && typeof v === "object" && !Array.isArray(v);
4913
5017
  }
4914
- function walkOutputSchema(schema, path14) {
5018
+ function walkOutputSchema(schema, path16) {
4915
5019
  let current = schema;
4916
- for (let i = 0; i < path14.length; i++) {
4917
- const seg = path14[i];
5020
+ for (let i = 0; i < path16.length; i++) {
5021
+ const seg = path16[i];
4918
5022
  if (typeof current === "string") {
4919
5023
  return current === "unknown" || current === "object" || current === "array" ? "opaque" : "opaque";
4920
5024
  }
@@ -6861,8 +6965,8 @@ function sleep(ms) {
6861
6965
  return new Promise((resolve) => setTimeout(resolve, ms));
6862
6966
  }
6863
6967
  function interpolate(template, record) {
6864
- return template.replace(/\{?\{(\w+(?:\.\w+)*)\}\}?/g, (_, path14) => {
6865
- const parts = path14.split(".");
6968
+ return template.replace(/\{?\{(\w+(?:\.\w+)*)\}\}?/g, (_, path16) => {
6969
+ const parts = path16.split(".");
6866
6970
  let value = record;
6867
6971
  for (const part2 of parts) {
6868
6972
  if (typeof value !== "object" || value === null) return "";
@@ -6890,8 +6994,8 @@ function deepMerge(target, source) {
6890
6994
  }
6891
6995
  return result;
6892
6996
  }
6893
- function getByDotPath2(obj, path14) {
6894
- const parts = path14.split(".");
6997
+ function getByDotPath2(obj, path16) {
6998
+ const parts = path16.split(".");
6895
6999
  let current = obj;
6896
7000
  for (const part2 of parts) {
6897
7001
  if (current === null || current === void 0 || typeof current !== "object") return void 0;
@@ -6900,8 +7004,8 @@ function getByDotPath2(obj, path14) {
6900
7004
  return current;
6901
7005
  }
6902
7006
  function stripExcludedFields(obj, paths) {
6903
- for (const path14 of paths) {
6904
- stripOnePath(obj, path14.replace(/\[\]/g, ".*").split("."));
7007
+ for (const path16 of paths) {
7008
+ stripOnePath(obj, path16.replace(/\[\]/g, ".*").split("."));
6905
7009
  }
6906
7010
  }
6907
7011
  function stripOnePath(obj, parts) {
@@ -7322,8 +7426,8 @@ function sleep2(ms) {
7322
7426
  return new Promise((resolve) => setTimeout(resolve, ms));
7323
7427
  }
7324
7428
  function stripFields(record, paths) {
7325
- for (const path14 of paths) {
7326
- stripOnePath2(record, path14.split("."));
7429
+ for (const path16 of paths) {
7430
+ stripOnePath2(record, path16.split("."));
7327
7431
  }
7328
7432
  }
7329
7433
  function stripOnePath2(obj, parts) {
@@ -7657,7 +7761,7 @@ async function syncModel(api, profile, options) {
7657
7761
  records,
7658
7762
  tableCreated
7659
7763
  ) : void 0;
7660
- const report = await writePageToMemory(
7764
+ const report2 = await writePageToMemory(
7661
7765
  profile,
7662
7766
  records,
7663
7767
  {
@@ -7669,12 +7773,12 @@ async function syncModel(api, profile, options) {
7669
7773
  alreadyEnrichedIds
7670
7774
  }
7671
7775
  );
7672
- pageReport = report;
7673
- memPreserved += report.preserved;
7674
- for (const key of report.sourceKeysSeen) seenSourceKeys.add(key);
7776
+ pageReport = report2;
7777
+ memPreserved += report2.preserved;
7778
+ for (const key of report2.sourceKeysSeen) seenSourceKeys.add(key);
7675
7779
  if (hasHooks && !db) {
7676
- inserts = report.inserts;
7677
- updates = report.updates;
7780
+ inserts = report2.inserts;
7781
+ updates = report2.updates;
7678
7782
  }
7679
7783
  } catch (err) {
7680
7784
  const msg = err instanceof Error ? err.message : String(err);
@@ -7993,7 +8097,7 @@ function detectColumnType2(value) {
7993
8097
  }
7994
8098
  async function testSyncProfile(api, profile) {
7995
8099
  const checks = [];
7996
- const report = {
8100
+ const report2 = {
7997
8101
  platform: profile.platform,
7998
8102
  model: profile.model,
7999
8103
  ok: false,
@@ -8025,7 +8129,7 @@ async function testSyncProfile(api, profile) {
8025
8129
  ok: false,
8026
8130
  detail: err instanceof Error ? err.message : String(err)
8027
8131
  });
8028
- return report;
8132
+ return report2;
8029
8133
  }
8030
8134
  let actionDetails;
8031
8135
  try {
@@ -8037,7 +8141,7 @@ async function testSyncProfile(api, profile) {
8037
8141
  ok: false,
8038
8142
  detail: err instanceof Error ? err.message : String(err)
8039
8143
  });
8040
- return report;
8144
+ return report2;
8041
8145
  }
8042
8146
  if (actionDetails.tags?.includes("custom")) {
8043
8147
  checks.push({
@@ -8045,7 +8149,7 @@ async function testSyncProfile(api, profile) {
8045
8149
  ok: false,
8046
8150
  detail: `Action ${profile.actionId} is tagged "custom". Sync only supports passthrough actions. Run 'one actions find ${profile.platform} "list ${profile.model}" -t knowledge' to find one.`
8047
8151
  });
8048
- return report;
8152
+ return report2;
8049
8153
  }
8050
8154
  let responseData;
8051
8155
  try {
@@ -8062,7 +8166,7 @@ async function testSyncProfile(api, profile) {
8062
8166
  } catch (err) {
8063
8167
  const detail = err instanceof ApiError ? `HTTP ${err.status}: ${err.message}` : err instanceof Error ? err.message : String(err);
8064
8168
  checks.push({ name: "single-page fetch", ok: false, detail });
8065
- return report;
8169
+ return report2;
8066
8170
  }
8067
8171
  let resolvedResultsPath = profile.resultsPath;
8068
8172
  const rawCandidate = isRootPath(resolvedResultsPath) ? responseData : getByDotPath(responseData, resolvedResultsPath);
@@ -8071,8 +8175,8 @@ async function testSyncProfile(api, profile) {
8071
8175
  if (Array.isArray(responseData)) {
8072
8176
  resolvedResultsPath = "";
8073
8177
  records = responseData;
8074
- report.autoFixed = report.autoFixed ?? {};
8075
- report.autoFixed.resultsPath = "";
8178
+ report2.autoFixed = report2.autoFixed ?? {};
8179
+ report2.autoFixed.resultsPath = "";
8076
8180
  checks.push({
8077
8181
  name: `resultsPath auto-discovered \u2192 <root>`,
8078
8182
  ok: true,
@@ -8084,8 +8188,8 @@ async function testSyncProfile(api, profile) {
8084
8188
  if (arrayKey) {
8085
8189
  resolvedResultsPath = arrayKey;
8086
8190
  records = topObj[arrayKey];
8087
- report.autoFixed = report.autoFixed ?? {};
8088
- report.autoFixed.resultsPath = arrayKey;
8191
+ report2.autoFixed = report2.autoFixed ?? {};
8192
+ report2.autoFixed.resultsPath = arrayKey;
8089
8193
  checks.push({
8090
8194
  name: `resultsPath auto-discovered \u2192 "${arrayKey}"`,
8091
8195
  ok: true,
@@ -8101,7 +8205,7 @@ async function testSyncProfile(api, profile) {
8101
8205
  ok: false,
8102
8206
  detail: `Not an array. Response keys: [${topKeys.join(", ")}]`
8103
8207
  });
8104
- return report;
8208
+ return report2;
8105
8209
  }
8106
8210
  let wrappedPrimitives = false;
8107
8211
  if (records.length > 0 && typeof records[0] !== "object") {
@@ -8122,8 +8226,8 @@ async function testSyncProfile(api, profile) {
8122
8226
  });
8123
8227
  if (records.length === 0) {
8124
8228
  checks.push({ name: "sample record available", ok: false, detail: "empty result set" });
8125
- report.ok = checks.every((c) => c.ok === true || c.name === "sample record available");
8126
- return report;
8229
+ report2.ok = checks.every((c) => c.ok === true || c.name === "sample record available");
8230
+ return report2;
8127
8231
  }
8128
8232
  const first = records[0];
8129
8233
  let resolvedIdField = profile.idField;
@@ -8137,8 +8241,8 @@ async function testSyncProfile(api, profile) {
8137
8241
  if (found) {
8138
8242
  resolvedIdField = found;
8139
8243
  idValue = getByDotPath(first, found);
8140
- report.autoFixed = report.autoFixed ?? {};
8141
- report.autoFixed.idField = found;
8244
+ report2.autoFixed = report2.autoFixed ?? {};
8245
+ report2.autoFixed.idField = found;
8142
8246
  checks.push({
8143
8247
  name: `idField auto-discovered \u2192 "${found}"`,
8144
8248
  ok: true,
@@ -8175,7 +8279,7 @@ async function testSyncProfile(api, profile) {
8175
8279
  ok: true,
8176
8280
  detail: JSON.stringify(nextParams).slice(0, 120)
8177
8281
  });
8178
- report.paginationPreview = nextParams;
8282
+ report2.paginationPreview = nextParams;
8179
8283
  } else {
8180
8284
  checks.push({
8181
8285
  name: `pagination "${profile.pagination.type}"`,
@@ -8183,22 +8287,22 @@ async function testSyncProfile(api, profile) {
8183
8287
  detail: "no next page (single-page result or end-of-data)"
8184
8288
  });
8185
8289
  }
8186
- report.detectedColumns = Object.entries(first).map(([name, value]) => ({
8290
+ report2.detectedColumns = Object.entries(first).map(([name, value]) => ({
8187
8291
  name,
8188
8292
  type: detectColumnType2(value)
8189
8293
  }));
8190
- report.sample = first;
8191
- report.samples = records.slice(0, SEARCHABLE_SAMPLE_SIZE);
8192
- if (profile.enrich && (profile.identityKeys?.length ?? 0) > 0 && report.samples.length > 0) {
8193
- const merged = await enrichOneForPreview(api, profile, report.samples[0], connectionKey);
8294
+ report2.sample = first;
8295
+ report2.samples = records.slice(0, SEARCHABLE_SAMPLE_SIZE);
8296
+ if (profile.enrich && (profile.identityKeys?.length ?? 0) > 0 && report2.samples.length > 0) {
8297
+ const merged = await enrichOneForPreview(api, profile, report2.samples[0], connectionKey);
8194
8298
  if (merged) {
8195
8299
  const preview = buildIdentityKeysPreview([merged], profile);
8196
- if (preview) report.identityKeysPreview = { ...preview, previewedAfterEnrich: true };
8300
+ if (preview) report2.identityKeysPreview = { ...preview, previewedAfterEnrich: true };
8197
8301
  }
8198
8302
  }
8199
- report.identityKeysPreview ??= buildIdentityKeysPreview(report.samples, profile);
8200
- report.ok = checks.every((c) => c.ok);
8201
- return report;
8303
+ report2.identityKeysPreview ??= buildIdentityKeysPreview(report2.samples, profile);
8304
+ report2.ok = checks.every((c) => c.ok);
8305
+ return report2;
8202
8306
  }
8203
8307
 
8204
8308
  // src/lib/memory/sync/infer.ts
@@ -8974,11 +9078,11 @@ function isNoise(s) {
8974
9078
  if (/^-?\d+(\.\d+)?([eE][-+]?\d+)?$/.test(s)) return true;
8975
9079
  return false;
8976
9080
  }
8977
- function recordSample(stats, path14, value, jsType, recordIndex, totalSamples) {
8978
- let s = stats.get(path14);
9081
+ function recordSample(stats, path16, value, jsType, recordIndex, totalSamples) {
9082
+ let s = stats.get(path16);
8979
9083
  if (!s) {
8980
9084
  s = {
8981
- path: path14,
9085
+ path: path16,
8982
9086
  total: totalSamples,
8983
9087
  recordIndices: /* @__PURE__ */ new Set(),
8984
9088
  lenSum: 0,
@@ -8987,7 +9091,7 @@ function recordSample(stats, path14, value, jsType, recordIndex, totalSamples) {
8987
9091
  primaryType: jsType,
8988
9092
  examples: []
8989
9093
  };
8990
- stats.set(path14, s);
9094
+ stats.set(path16, s);
8991
9095
  }
8992
9096
  s.recordIndices.add(recordIndex);
8993
9097
  s.lenSum += value.length;
@@ -8996,28 +9100,28 @@ function recordSample(stats, path14, value, jsType, recordIndex, totalSamples) {
8996
9100
  if (s.primaryType !== jsType) s.primaryType = "mixed";
8997
9101
  if (s.examples.length < 3 && value.length < 200) s.examples.push(value);
8998
9102
  }
8999
- function walkRecord(record, path14, stats, recordIndex, totalSamples) {
9103
+ function walkRecord(record, path16, stats, recordIndex, totalSamples) {
9000
9104
  if (record === null || record === void 0) return;
9001
9105
  if (typeof record === "string") {
9002
9106
  const trimmed = record.trim();
9003
- if (trimmed) recordSample(stats, path14, trimmed, "string", recordIndex, totalSamples);
9107
+ if (trimmed) recordSample(stats, path16, trimmed, "string", recordIndex, totalSamples);
9004
9108
  return;
9005
9109
  }
9006
9110
  if (typeof record === "number" || typeof record === "boolean") {
9007
9111
  const str = String(record);
9008
9112
  const kind = typeof record === "number" ? "number" : "boolean";
9009
- if (str) recordSample(stats, path14, str, kind, recordIndex, totalSamples);
9113
+ if (str) recordSample(stats, path16, str, kind, recordIndex, totalSamples);
9010
9114
  return;
9011
9115
  }
9012
9116
  if (Array.isArray(record)) {
9013
- const childPath = path14 ? `${path14}[]` : "[]";
9117
+ const childPath = path16 ? `${path16}[]` : "[]";
9014
9118
  for (const item of record) walkRecord(item, childPath, stats, recordIndex, totalSamples);
9015
9119
  return;
9016
9120
  }
9017
9121
  if (typeof record === "object") {
9018
9122
  for (const [key, value] of Object.entries(record)) {
9019
9123
  if (key.startsWith("_")) continue;
9020
- const childPath = path14 ? `${path14}.${key}` : key;
9124
+ const childPath = path16 ? `${path16}.${key}` : key;
9021
9125
  walkRecord(value, childPath, stats, recordIndex, totalSamples);
9022
9126
  }
9023
9127
  }
@@ -9371,38 +9475,38 @@ async function syncTestCommand(platformModel, options = {}) {
9371
9475
  );
9372
9476
  }
9373
9477
  const api = getApi();
9374
- const report = await testSyncProfile(api, profile);
9375
- if (report.autoFixed && Object.keys(report.autoFixed).length > 0) {
9478
+ const report2 = await testSyncProfile(api, profile);
9479
+ if (report2.autoFixed && Object.keys(report2.autoFixed).length > 0) {
9376
9480
  try {
9377
9481
  const existing = readProfile(platform, model);
9378
9482
  if (existing) {
9379
- const patched = { ...existing, ...report.autoFixed };
9483
+ const patched = { ...existing, ...report2.autoFixed };
9380
9484
  writeProfile(patched);
9381
9485
  }
9382
9486
  } catch {
9383
9487
  }
9384
9488
  }
9385
- const searchablePreview = options.showSearchable ? buildSearchablePreview(profile, report.samples) : null;
9489
+ const searchablePreview = options.showSearchable ? buildSearchablePreview(profile, report2.samples) : null;
9386
9490
  if (isAgentMode()) {
9387
- json({ ...report, ...searchablePreview ? { searchable: searchablePreview } : {} });
9491
+ json({ ...report2, ...searchablePreview ? { searchable: searchablePreview } : {} });
9388
9492
  return;
9389
9493
  }
9390
- for (const c of report.checks) {
9494
+ for (const c of report2.checks) {
9391
9495
  const mark = c.ok ? pc11.green("\u2713") : pc11.red("\u2717");
9392
9496
  console.log(` ${mark} ${c.name}${c.detail ? pc11.dim(` \u2014 ${c.detail}`) : ""}`);
9393
9497
  }
9394
- if (report.detectedColumns && report.detectedColumns.length > 0) {
9498
+ if (report2.detectedColumns && report2.detectedColumns.length > 0) {
9395
9499
  console.log(`
9396
9500
  ${pc11.bold("Detected columns:")}`);
9397
- for (const col of report.detectedColumns.slice(0, 20)) {
9501
+ for (const col of report2.detectedColumns.slice(0, 20)) {
9398
9502
  console.log(` ${col.name.padEnd(30)} ${pc11.dim(col.type)}`);
9399
9503
  }
9400
- if (report.detectedColumns.length > 20) {
9401
- console.log(pc11.dim(` ... and ${report.detectedColumns.length - 20} more`));
9504
+ if (report2.detectedColumns.length > 20) {
9505
+ console.log(pc11.dim(` ... and ${report2.detectedColumns.length - 20} more`));
9402
9506
  }
9403
9507
  }
9404
- if (report.identityKeysPreview) {
9405
- const { perRecord, sampleKeys, entityFanOut, resolvesAfterEnrich } = report.identityKeysPreview;
9508
+ if (report2.identityKeysPreview) {
9509
+ const { perRecord, sampleKeys, entityFanOut, resolvesAfterEnrich } = report2.identityKeysPreview;
9406
9510
  const total = perRecord.reduce((a, b) => a + b, 0);
9407
9511
  const min = perRecord.length ? Math.min(...perRecord) : 0;
9408
9512
  const max = perRecord.length ? Math.max(...perRecord) : 0;
@@ -9444,7 +9548,7 @@ async function syncTestCommand(platformModel, options = {}) {
9444
9548
  }
9445
9549
  console.log(
9446
9550
  `
9447
- ${report.ok ? pc11.green("Profile looks good.") : pc11.red("Profile has issues.")} ` + (report.ok ? `Run: ${pc11.bold(`one sync run ${platform} --models ${model}`)}` : "Fix the issues above and test again.")
9551
+ ${report2.ok ? pc11.green("Profile looks good.") : pc11.red("Profile has issues.")} ` + (report2.ok ? `Run: ${pc11.bold(`one sync run ${platform} --models ${model}`)}` : "Fix the issues above and test again.")
9448
9552
  );
9449
9553
  }
9450
9554
  function buildSearchablePreview(profile, samples) {
@@ -9452,7 +9556,7 @@ function buildSearchablePreview(profile, samples) {
9452
9556
  const first = samples[0];
9453
9557
  const paths = getSearchablePaths(profile);
9454
9558
  if (paths) {
9455
- const perPathAgg = paths.map((path14) => ({ path: path14, hits: 0, total: samples.length, sample: "" }));
9559
+ const perPathAgg = paths.map((path16) => ({ path: path16, hits: 0, total: samples.length, sample: "" }));
9456
9560
  for (const record of samples) {
9457
9561
  const { paths: perPath } = extractSearchableFromPaths(record, paths);
9458
9562
  perPath.forEach((p9, i) => {
@@ -9720,8 +9824,8 @@ async function syncSuggestSearchableCommand(platformModel, options = {}) {
9720
9824
  );
9721
9825
  }
9722
9826
  const api = getApi();
9723
- const report = await testSyncProfile(api, profile);
9724
- const samples = report.samples ?? [];
9827
+ const report2 = await testSyncProfile(api, profile);
9828
+ const samples = report2.samples ?? [];
9725
9829
  if (samples.length === 0) {
9726
9830
  error(
9727
9831
  "No sample records available to rank against. Fix the failures in `sync test` first, then re-run suggest-searchable."
@@ -10409,16 +10513,16 @@ function projectEmbeddingApiKey(cfg) {
10409
10513
  }
10410
10514
  function redactSecrets(cfg) {
10411
10515
  const copy = JSON.parse(JSON.stringify(cfg));
10412
- for (const path14 of SECRET_PATHS) {
10413
- const val = getPath(copy, path14);
10516
+ for (const path16 of SECRET_PATHS) {
10517
+ const val = getPath(copy, path16);
10414
10518
  if (typeof val === "string" && val.length > 0) {
10415
- setPath(copy, path14, `${val.slice(0, 6)}\u2026(redacted, use --show-secrets)`);
10519
+ setPath(copy, path16, `${val.slice(0, 6)}\u2026(redacted, use --show-secrets)`);
10416
10520
  }
10417
10521
  }
10418
10522
  return copy;
10419
10523
  }
10420
- function getPath(obj, path14) {
10421
- const parts = path14.split(".");
10524
+ function getPath(obj, path16) {
10525
+ const parts = path16.split(".");
10422
10526
  let cur = obj;
10423
10527
  for (const part2 of parts) {
10424
10528
  if (cur == null || typeof cur !== "object") return void 0;
@@ -10426,8 +10530,8 @@ function getPath(obj, path14) {
10426
10530
  }
10427
10531
  return cur;
10428
10532
  }
10429
- function setPath(obj, path14, value) {
10430
- const parts = path14.split(".");
10533
+ function setPath(obj, path16, value) {
10534
+ const parts = path16.split(".");
10431
10535
  let cur = obj;
10432
10536
  for (let i = 0; i < parts.length - 1; i++) {
10433
10537
  const part2 = parts[i];
@@ -10437,8 +10541,8 @@ function setPath(obj, path14, value) {
10437
10541
  cur[parts[parts.length - 1]] = value;
10438
10542
  return obj;
10439
10543
  }
10440
- function unsetPath(obj, path14) {
10441
- const parts = path14.split(".");
10544
+ function unsetPath(obj, path16) {
10545
+ const parts = path16.split(".");
10442
10546
  let cur = obj;
10443
10547
  for (let i = 0; i < parts.length - 1; i++) {
10444
10548
  const part2 = parts[i];
@@ -11411,8 +11515,9 @@ Request specific sections:
11411
11515
  - Always use the **exact action ID** from a find answer \u2014 don't guess
11412
11516
  - Always read **knowledge** before executing any action
11413
11517
  - Connection keys come from \`one connection list\` \u2014 don't hardcode them
11414
- - \`connection list\` also reports an \`access\` field per connection (\`full\` / \`methods\` / \`actions\`) \u2014 read it before planning so you don't propose an action the access config will reject
11518
+ - \`connection list\` also reports an \`access\` field per connection (\`full\` / \`methods\` / \`actions\`, plus a \`level\`: full, read-write, read-only, custom, none) \u2014 what One enforces for this key, narrowed by \`one config\`. Read it before planning so you don't propose an action that will be refused. \`accessSource: "local"\` means One did not report it, so it is unverified
11415
11519
  - Skills stay in lockstep with the CLI version automatically \u2014 every command checks a \`.one-cli-version\` marker in the canonical skill dir and refreshes the files if the CLI has been upgraded. Check manually with \`one config skills status\`; force a resync with \`one config skills sync\`
11520
+ - Building an app whose users connect their own tools? \`one skills add connect\` (run inside the app) installs the One Connect skill from the \`@withone/connect\` version the app uses, into \`.agents/skills/one-connect\` and \`.claude/skills/one-connect\`. \`one --agent skills list\` shows what is installed; any \`one\` command in the project refreshes it after an SDK upgrade
11416
11521
  `;
11417
11522
  var GUIDE_ACTIONS = `# One Actions \u2014 Reference
11418
11523
 
@@ -11426,7 +11531,7 @@ Always follow this sequence. Never execute without reading the action's document
11426
11531
  one --agent connection list
11427
11532
  \`\`\`
11428
11533
 
11429
- Returns platforms, status, connection keys, tags, and an \`access\` field per connection describing what the current access config lets you run there:
11534
+ Returns platforms, status, connection keys, tags, and an \`access\` field per connection: what One enforces for this key (the consent page's choices, or the dashboard's), narrowed by \`one config\`. \`access.level\` names it as the consent page does (full, read-write, read-only, custom, none):
11430
11535
 
11431
11536
  | \`access\` | Meaning |
11432
11537
  |----------|---------|
@@ -11434,9 +11539,9 @@ Returns platforms, status, connection keys, tags, and an \`access\` field per co
11434
11539
  | \`{"policy": "methods", "methods": ["GET"]}\` | Only actions with these HTTP methods will execute |
11435
11540
  | \`{"policy": "actions", "actions": [{"actionId", "title", "method"}]}\` | Only these exact actions \u2014 use them directly: \`actions load <actionId>\` reads their docs, no find needed |
11436
11541
 
11437
- Also present when relevant: \`knowledgeOnly: true\` (execution disabled \u2014 read the docs and write code instead), \`unresolvedActionIds\` (allowlisted ids that could not be looked up), and \`accessHint\` (a one-line summary of the restriction).
11542
+ \`accessSource\` is \`server\` when One reported it, \`local\` when it could not (then it is unverified). Also present when relevant: \`knowledgeOnly: true\` (execution disabled \u2014 read the docs and write code instead), \`unresolvedActionIds\` (allowlisted ids that could not be looked up), and \`accessHint\` (a one-line summary of the restriction).
11438
11543
 
11439
- Read \`access\` before planning \u2014 it prevents proposing an action the config will reject. Change it with \`one config\`.
11544
+ Read \`access\` before planning \u2014 it prevents proposing an action that will be refused. The key's access changes in the dashboard (API keys); this CLI's own limits with \`one config\`.
11440
11545
 
11441
11546
  ### 1b. Delete a Connection
11442
11547
 
@@ -12806,22 +12911,22 @@ function buildWorkflowIdeas(connections) {
12806
12911
  // src/commands/logout.ts
12807
12912
  import fs13 from "fs";
12808
12913
  import * as p8 from "@clack/prompts";
12809
- function formatWhoami(config2, apiKey, pc16) {
12914
+ function formatWhoami(config2, apiKey, pc17) {
12810
12915
  const whoami = config2.whoami;
12811
12916
  const env = getEnvFromApiKey(apiKey);
12812
- const envLabel = env === "test" ? pc16.yellow("test") : pc16.green("live");
12917
+ const envLabel = env === "test" ? pc17.yellow("test") : pc17.green("live");
12813
12918
  const lines = [];
12814
12919
  if (whoami) {
12815
12920
  const contextParts = [];
12816
12921
  if (whoami.organization) contextParts.push(whoami.organization.name);
12817
12922
  if (whoami.project) contextParts.push(whoami.project.name);
12818
12923
  const scopeDisplay = contextParts.length > 0 ? contextParts.join(" / ") : "Personal";
12819
- lines.push(`${pc16.bold(scopeDisplay)} ${pc16.dim("\xB7")} ${envLabel}`);
12820
- lines.push(`${whoami.user.name} ${pc16.dim(`(${whoami.user.email})`)}`);
12821
- if (config2.apiKeyName) lines.push(`${pc16.dim("Key:")} ${config2.apiKeyName}`);
12924
+ lines.push(`${pc17.bold(scopeDisplay)} ${pc17.dim("\xB7")} ${envLabel}`);
12925
+ lines.push(`${whoami.user.name} ${pc17.dim(`(${whoami.user.email})`)}`);
12926
+ if (config2.apiKeyName) lines.push(`${pc17.dim("Key:")} ${config2.apiKeyName}`);
12822
12927
  } else {
12823
12928
  const keyLabel = config2.apiKeyName ?? `${apiKey.slice(0, 8)}...`;
12824
- lines.push(`${pc16.dim("Key:")} ${keyLabel} ${pc16.dim("\xB7")} ${envLabel}`);
12929
+ lines.push(`${pc17.dim("Key:")} ${keyLabel} ${pc17.dim("\xB7")} ${envLabel}`);
12825
12930
  }
12826
12931
  return lines;
12827
12932
  }
@@ -12850,7 +12955,7 @@ async function logoutCommand() {
12850
12955
  json({ status: cleared ? "logged_out" : "not_logged_in", message: cleared ? "Credentials cleared." : "No config found." });
12851
12956
  return;
12852
12957
  }
12853
- const pc16 = (await import("picocolors")).default;
12958
+ const pc17 = (await import("picocolors")).default;
12854
12959
  const globalConfig = readGlobalConfig();
12855
12960
  const projectConfig = readProjectConfig();
12856
12961
  const hasGlobal = globalConfig?.apiKey != null;
@@ -12859,13 +12964,13 @@ async function logoutCommand() {
12859
12964
  if (hasGlobal && hasProject) {
12860
12965
  const infoLines = ["You are logged in with multiple configs.", ""];
12861
12966
  if (projectConfig) {
12862
- infoLines.push(`${pc16.cyan("Local config:")}`);
12863
- infoLines.push(...formatWhoami(projectConfig, projectConfig.apiKey, pc16));
12967
+ infoLines.push(`${pc17.cyan("Local config:")}`);
12968
+ infoLines.push(...formatWhoami(projectConfig, projectConfig.apiKey, pc17));
12864
12969
  infoLines.push("");
12865
12970
  }
12866
12971
  if (globalConfig) {
12867
- infoLines.push(`${pc16.magenta("Global config:")}`);
12868
- infoLines.push(...formatWhoami(globalConfig, globalConfig.apiKey, pc16));
12972
+ infoLines.push(`${pc17.magenta("Global config:")}`);
12973
+ infoLines.push(...formatWhoami(globalConfig, globalConfig.apiKey, pc17));
12869
12974
  }
12870
12975
  p8.note(infoLines.join("\n"));
12871
12976
  const choice = await p8.select({
@@ -12883,14 +12988,14 @@ async function logoutCommand() {
12883
12988
  targetScope = choice;
12884
12989
  } else if (hasProject) {
12885
12990
  const infoLines = ["You are logged in.", ""];
12886
- infoLines.push(`${pc16.dim("Stored in")} ${pc16.cyan("local config")}`);
12887
- infoLines.push(...formatWhoami(projectConfig, projectConfig.apiKey, pc16));
12991
+ infoLines.push(`${pc17.dim("Stored in")} ${pc17.cyan("local config")}`);
12992
+ infoLines.push(...formatWhoami(projectConfig, projectConfig.apiKey, pc17));
12888
12993
  p8.note(infoLines.join("\n"));
12889
12994
  targetScope = "project";
12890
12995
  } else if (hasGlobal) {
12891
12996
  const infoLines = ["You are logged in.", ""];
12892
- infoLines.push(`${pc16.dim("Stored in")} ${pc16.magenta("global config")}`);
12893
- infoLines.push(...formatWhoami(globalConfig, globalConfig.apiKey, pc16));
12997
+ infoLines.push(`${pc17.dim("Stored in")} ${pc17.magenta("global config")}`);
12998
+ infoLines.push(...formatWhoami(globalConfig, globalConfig.apiKey, pc17));
12894
12999
  p8.note(infoLines.join("\n"));
12895
13000
  targetScope = "global";
12896
13001
  } else {
@@ -12924,6 +13029,538 @@ async function logoutCommand() {
12924
13029
  p8.outro("Logged out.");
12925
13030
  }
12926
13031
 
13032
+ // src/lib/project-skills.ts
13033
+ import crypto2 from "crypto";
13034
+ import fs14 from "fs";
13035
+ import os3 from "os";
13036
+ import path13 from "path";
13037
+ import zlib from "zlib";
13038
+ var OPTIONAL_SKILLS = [
13039
+ {
13040
+ name: "connect",
13041
+ dirName: "one-connect",
13042
+ title: "One Connect",
13043
+ description: "Add One Connect to an app: your users grant it scoped, revocable access to their own tools.",
13044
+ packageName: "@withone/connect",
13045
+ packagePath: "skills/one-connect"
13046
+ }
13047
+ ];
13048
+ function findOptionalSkill(name) {
13049
+ const wanted = name.trim().toLowerCase();
13050
+ return OPTIONAL_SKILLS.find((s) => s.name === wanted || s.dirName === wanted);
13051
+ }
13052
+ var UNIVERSAL_PROJECT_DIR = ".agents/skills";
13053
+ var SKILL_AGENT_TARGETS = [
13054
+ { id: "claude-code", name: "Claude Code", projectDir: ".claude/skills", projectMarker: null, homeDir: ".claude/skills" },
13055
+ { id: "codex", name: "Codex", projectDir: UNIVERSAL_PROJECT_DIR, projectMarker: null, homeDir: ".codex/skills" },
13056
+ { id: "cursor", name: "Cursor", projectDir: UNIVERSAL_PROJECT_DIR, projectMarker: null, homeDir: ".cursor/skills" },
13057
+ { id: "amp", name: "Amp", projectDir: UNIVERSAL_PROJECT_DIR, projectMarker: null, homeDir: ".amp/skills" },
13058
+ { id: "opencode", name: "OpenCode", projectDir: UNIVERSAL_PROJECT_DIR, projectMarker: null, homeDir: ".opencode/skills" },
13059
+ { id: "windsurf", name: "Windsurf", projectDir: ".windsurf/skills", projectMarker: ".windsurf", homeDir: ".codeium/windsurf/skills" },
13060
+ { id: "kiro", name: "Kiro", projectDir: ".kiro/skills", projectMarker: ".kiro", homeDir: ".kiro/skills" },
13061
+ { id: "goose", name: "Goose", projectDir: ".goose/skills", projectMarker: ".goose", homeDir: ".config/goose/skills" },
13062
+ { id: "roo", name: "Roo", projectDir: ".roo/skills", projectMarker: ".roo", homeDir: ".roo/skills" }
13063
+ ];
13064
+ function findAgentTarget(id) {
13065
+ return SKILL_AGENT_TARGETS.find((a) => a.id === id.trim().toLowerCase());
13066
+ }
13067
+ function findProjectRoot(start) {
13068
+ let dir = path13.resolve(start);
13069
+ for (; ; ) {
13070
+ if (fs14.existsSync(path13.join(dir, "package.json")) || fs14.existsSync(path13.join(dir, ".git"))) return dir;
13071
+ const parent = path13.dirname(dir);
13072
+ if (parent === dir) return null;
13073
+ dir = parent;
13074
+ }
13075
+ }
13076
+ function findInstalledPackage(start, packageName) {
13077
+ let dir = path13.resolve(start);
13078
+ for (; ; ) {
13079
+ const pkgDir = path13.join(dir, "node_modules", ...packageName.split("/"));
13080
+ const manifest = path13.join(pkgDir, "package.json");
13081
+ if (fs14.existsSync(manifest)) {
13082
+ try {
13083
+ const { version: version2 } = JSON.parse(fs14.readFileSync(manifest, "utf-8"));
13084
+ if (version2) return { dir: pkgDir, version: version2 };
13085
+ } catch {
13086
+ }
13087
+ }
13088
+ const parent = path13.dirname(dir);
13089
+ if (parent === dir) return null;
13090
+ dir = parent;
13091
+ }
13092
+ }
13093
+ var SkillSourceError = class extends Error {
13094
+ constructor(message, code) {
13095
+ super(message);
13096
+ this.code = code;
13097
+ }
13098
+ };
13099
+ function projectSkillSource(skill, projectDir) {
13100
+ const installed = findInstalledPackage(projectDir, skill.packageName);
13101
+ if (!installed) return null;
13102
+ const dir = path13.join(installed.dir, ...skill.packagePath.split("/"));
13103
+ if (!fs14.existsSync(path13.join(dir, "SKILL.md"))) {
13104
+ throw new SkillSourceError(
13105
+ `${skill.packageName} ${installed.version} does not include the ${skill.title} skill. Upgrade it: npm install ${skill.packageName}@latest`,
13106
+ "skill-missing"
13107
+ );
13108
+ }
13109
+ return { kind: "project", version: installed.version, dir, origin: installed.dir, cleanup: () => {
13110
+ } };
13111
+ }
13112
+ function registryBase() {
13113
+ return (process.env.ONE_SKILLS_REGISTRY || "https://registry.npmjs.org").replace(/\/+$/, "");
13114
+ }
13115
+ async function registrySkillSource(skill, fetchImpl = fetch) {
13116
+ const metaUrl = `${registryBase()}/${skill.packageName.replace("/", "%2f")}/latest`;
13117
+ let meta;
13118
+ try {
13119
+ const res = await fetchImpl(metaUrl, { headers: { accept: "application/json" } });
13120
+ if (!res.ok) throw new Error(`HTTP ${res.status}`);
13121
+ meta = await res.json();
13122
+ } catch (err) {
13123
+ throw new SkillSourceError(`Could not reach the npm registry for ${skill.packageName} (${err.message}).`, "registry-failed");
13124
+ }
13125
+ const version2 = meta.version;
13126
+ const tarball = meta.dist?.tarball;
13127
+ const integrity = meta.dist?.integrity;
13128
+ if (!version2 || !tarball || !integrity) {
13129
+ throw new SkillSourceError(`The npm registry returned no tarball for ${skill.packageName}.`, "registry-failed");
13130
+ }
13131
+ let bytes;
13132
+ try {
13133
+ const res = await fetchImpl(tarball);
13134
+ if (!res.ok) throw new Error(`HTTP ${res.status}`);
13135
+ bytes = Buffer.from(await res.arrayBuffer());
13136
+ } catch (err) {
13137
+ throw new SkillSourceError(`Could not download ${skill.packageName}@${version2} (${err.message}).`, "registry-failed");
13138
+ }
13139
+ if (!matchesIntegrity(bytes, integrity)) {
13140
+ throw new SkillSourceError(`${skill.packageName}@${version2} failed its integrity check; nothing was installed.`, "integrity");
13141
+ }
13142
+ const files = readTarEntries(zlib.gunzipSync(bytes), `package/${skill.packagePath}/`);
13143
+ if (!files.some((f) => f.path === "SKILL.md")) {
13144
+ throw new SkillSourceError(`${skill.packageName}@${version2} does not include the ${skill.title} skill.`, "not-in-package");
13145
+ }
13146
+ const tmp = fs14.mkdtempSync(path13.join(os3.tmpdir(), "one-skill-"));
13147
+ for (const file of files) {
13148
+ const dest = path13.join(tmp, ...file.path.split("/"));
13149
+ fs14.mkdirSync(path13.dirname(dest), { recursive: true });
13150
+ fs14.writeFileSync(dest, file.data);
13151
+ }
13152
+ return {
13153
+ kind: "registry",
13154
+ version: version2,
13155
+ dir: tmp,
13156
+ origin: tarball,
13157
+ cleanup: () => {
13158
+ try {
13159
+ fs14.rmSync(tmp, { recursive: true, force: true });
13160
+ } catch {
13161
+ }
13162
+ }
13163
+ };
13164
+ }
13165
+ function matchesIntegrity(bytes, integrity) {
13166
+ return integrity.split(/\s+/).some((entry) => {
13167
+ const dash = entry.indexOf("-");
13168
+ if (dash < 0) return false;
13169
+ const algorithm = entry.slice(0, dash);
13170
+ if (!["sha512", "sha384", "sha256"].includes(algorithm)) return false;
13171
+ return crypto2.createHash(algorithm).update(bytes).digest("base64") === entry.slice(dash + 1);
13172
+ });
13173
+ }
13174
+ function readTarEntries(tar, prefix) {
13175
+ const out = [];
13176
+ let offset = 0;
13177
+ let paxPath = null;
13178
+ const text5 = (start, length) => {
13179
+ const raw = tar.subarray(start, start + length);
13180
+ const end = raw.indexOf(0);
13181
+ return raw.subarray(0, end < 0 ? raw.length : end).toString("utf-8");
13182
+ };
13183
+ while (offset + 512 <= tar.length) {
13184
+ const header = tar.subarray(offset, offset + 512);
13185
+ if (header.every((b) => b === 0)) break;
13186
+ const size = parseInt(text5(offset + 124, 12).trim() || "0", 8);
13187
+ const type = String.fromCharCode(header[156] || 48);
13188
+ let name = text5(offset, 100);
13189
+ if (text5(offset + 257, 5) === "ustar") {
13190
+ const pre = text5(offset + 345, 155);
13191
+ if (pre) name = `${pre}/${name}`;
13192
+ }
13193
+ const body = tar.subarray(offset + 512, offset + 512 + size);
13194
+ if (type === "x") {
13195
+ const match = /\d+ path=([^\n]*)\n/.exec(body.toString("utf-8"));
13196
+ paxPath = match ? match[1] : null;
13197
+ } else {
13198
+ const full = paxPath ?? name;
13199
+ paxPath = null;
13200
+ if ((type === "0" || type === "\0") && full.startsWith(prefix)) {
13201
+ const rel = full.slice(prefix.length);
13202
+ const parts = rel.split("/");
13203
+ if (rel && !path13.isAbsolute(rel) && !parts.includes("..") && !parts.includes("")) {
13204
+ out.push({ path: rel, data: Buffer.from(body) });
13205
+ }
13206
+ }
13207
+ }
13208
+ offset += 512 + Math.ceil(size / 512) * 512;
13209
+ }
13210
+ return out;
13211
+ }
13212
+ var SKILL_STAMP = ".one-skill.json";
13213
+ function canonicalSkillDir(skill, plan) {
13214
+ return path13.join(plan.root, ...UNIVERSAL_PROJECT_DIR.split("/"), skill.dirName);
13215
+ }
13216
+ function readStamp(dir) {
13217
+ try {
13218
+ return JSON.parse(fs14.readFileSync(path13.join(dir, SKILL_STAMP), "utf-8"));
13219
+ } catch {
13220
+ return null;
13221
+ }
13222
+ }
13223
+ function resolveAgentFolders(plan) {
13224
+ const explicit = plan.agents.length > 0;
13225
+ const chosen = SKILL_AGENT_TARGETS.filter((agent) => {
13226
+ if (explicit) return plan.agents.includes(agent.id);
13227
+ if (plan.scope === "global") {
13228
+ return fs14.existsSync(path13.join(plan.root, path13.dirname(agent.homeDir)));
13229
+ }
13230
+ return agent.projectMarker === null || fs14.existsSync(path13.join(plan.root, agent.projectMarker));
13231
+ });
13232
+ const readers = [];
13233
+ const byDir = /* @__PURE__ */ new Map();
13234
+ for (const agent of chosen) {
13235
+ const dir = plan.scope === "project" ? agent.projectDir : agent.homeDir;
13236
+ if (dir === UNIVERSAL_PROJECT_DIR) {
13237
+ readers.push(agent.name);
13238
+ continue;
13239
+ }
13240
+ byDir.set(dir, [...byDir.get(dir) ?? [], agent.name]);
13241
+ }
13242
+ return { readers, folders: [...byDir].map(([dir, agents]) => ({ dir, agents })) };
13243
+ }
13244
+ function copyDir(src, dest) {
13245
+ fs14.mkdirSync(dest, { recursive: true });
13246
+ for (const entry of fs14.readdirSync(src, { withFileTypes: true })) {
13247
+ if (entry.name === SKILL_STAMP) continue;
13248
+ const from = path13.join(src, entry.name);
13249
+ const to = path13.join(dest, entry.name);
13250
+ if (entry.isDirectory()) copyDir(from, to);
13251
+ else fs14.copyFileSync(from, to);
13252
+ }
13253
+ }
13254
+ function removePath(target) {
13255
+ try {
13256
+ const stat = fs14.lstatSync(target);
13257
+ if (stat.isSymbolicLink()) fs14.unlinkSync(target);
13258
+ else fs14.rmSync(target, { recursive: true, force: true });
13259
+ } catch {
13260
+ }
13261
+ }
13262
+ function linkDir(target, linkPath) {
13263
+ removePath(linkPath);
13264
+ fs14.mkdirSync(path13.dirname(linkPath), { recursive: true });
13265
+ try {
13266
+ fs14.symlinkSync(path13.relative(path13.dirname(linkPath), target), linkPath, "dir");
13267
+ return "symlink";
13268
+ } catch {
13269
+ }
13270
+ try {
13271
+ fs14.symlinkSync(target, linkPath, "junction");
13272
+ return "junction";
13273
+ } catch {
13274
+ }
13275
+ copyDir(target, linkPath);
13276
+ return "copy";
13277
+ }
13278
+ function installSkill(skill, source, plan, cliVersion2) {
13279
+ const canonical = canonicalSkillDir(skill, plan);
13280
+ const previous = readStamp(canonical);
13281
+ const { readers, folders } = resolveAgentFolders(plan);
13282
+ const nextPaths = new Set(folders.map((f) => `${f.dir}/${skill.dirName}`));
13283
+ for (const link of previous?.links ?? []) {
13284
+ if (!nextPaths.has(toPosix(link.path))) removePath(fromStamp(plan.root, link.path));
13285
+ }
13286
+ removePath(canonical);
13287
+ copyDir(source.dir, canonical);
13288
+ const links = folders.map((folder) => {
13289
+ const rel = `${folder.dir}/${skill.dirName}`;
13290
+ return { path: rel, kind: linkDir(canonical, fromStamp(plan.root, rel)), agents: folder.agents };
13291
+ });
13292
+ const stamp = {
13293
+ skill: skill.name,
13294
+ package: skill.packageName,
13295
+ version: source.version,
13296
+ source: source.kind,
13297
+ scope: plan.scope,
13298
+ links,
13299
+ installedBy: `@withone/cli@${cliVersion2}`,
13300
+ installedAt: (/* @__PURE__ */ new Date()).toISOString()
13301
+ };
13302
+ fs14.writeFileSync(path13.join(canonical, SKILL_STAMP), JSON.stringify(stamp, null, 2) + "\n");
13303
+ return { skill, version: source.version, source: source.kind, origin: source.origin, scope: plan.scope, root: plan.root, canonical, readers, links };
13304
+ }
13305
+ function removeSkill(skill, plan) {
13306
+ const canonical = canonicalSkillDir(skill, plan);
13307
+ const stamp = readStamp(canonical);
13308
+ if (!stamp && !fs14.existsSync(path13.join(canonical, "SKILL.md"))) return { removed: false, paths: [] };
13309
+ const paths = [];
13310
+ for (const link of stamp?.links ?? []) {
13311
+ const full = fromStamp(plan.root, link.path);
13312
+ if (fs14.existsSync(full) || isDanglingLink(full)) {
13313
+ removePath(full);
13314
+ paths.push(full);
13315
+ }
13316
+ }
13317
+ removePath(canonical);
13318
+ paths.push(canonical);
13319
+ return { removed: true, paths };
13320
+ }
13321
+ function toPosix(p9) {
13322
+ return p9.split("\\").join("/");
13323
+ }
13324
+ function fromStamp(root, rel) {
13325
+ return path13.join(root, ...toPosix(rel).split("/"));
13326
+ }
13327
+ function isDanglingLink(p9) {
13328
+ try {
13329
+ return fs14.lstatSync(p9).isSymbolicLink();
13330
+ } catch {
13331
+ return false;
13332
+ }
13333
+ }
13334
+ function installedSkill(skill, plan) {
13335
+ const canonical = canonicalSkillDir(skill, plan);
13336
+ if (!fs14.existsSync(path13.join(canonical, "SKILL.md"))) return null;
13337
+ const stamp = readStamp(canonical);
13338
+ return { scope: plan.scope, root: plan.root, canonical, version: stamp?.version ?? null, source: stamp?.source ?? null, links: stamp?.links ?? [] };
13339
+ }
13340
+ function globalRoot() {
13341
+ return homeDir();
13342
+ }
13343
+ function refreshProjectSkills(cwd, cliVersion2) {
13344
+ const root = findProjectRoot(cwd);
13345
+ if (!root || path13.resolve(root) === path13.resolve(homeDir())) return [];
13346
+ const results = [];
13347
+ for (const skill of OPTIONAL_SKILLS) {
13348
+ const canonical = canonicalSkillDir(skill, { scope: "project", root });
13349
+ const stamp = readStamp(canonical);
13350
+ if (!stamp || stamp.scope !== "project" || stamp.source !== "project") continue;
13351
+ const installed = findInstalledPackage(root, skill.packageName);
13352
+ if (!installed || installed.version === stamp.version) continue;
13353
+ let source;
13354
+ try {
13355
+ source = projectSkillSource(skill, root);
13356
+ } catch {
13357
+ continue;
13358
+ }
13359
+ if (!source) continue;
13360
+ const agents = SKILL_AGENT_TARGETS.filter((a) => stamp.links.some((l) => l.agents.includes(a.name)) || a.projectDir === UNIVERSAL_PROJECT_DIR).map((a) => a.id);
13361
+ installSkill(skill, source, { scope: "project", root, agents }, cliVersion2);
13362
+ results.push({ skill: skill.name, from: stamp.version, to: source.version, root });
13363
+ }
13364
+ return results;
13365
+ }
13366
+
13367
+ // src/commands/skills.ts
13368
+ import path14 from "path";
13369
+ import pc16 from "picocolors";
13370
+ function requireSkill(name) {
13371
+ const skill = findOptionalSkill(name);
13372
+ if (!skill) {
13373
+ const known = OPTIONAL_SKILLS.map((s) => s.name).join(", ");
13374
+ error(`Unknown skill "${name}". Available: ${known}. The One skill itself is installed by \`one init\`.`);
13375
+ }
13376
+ return skill;
13377
+ }
13378
+ function parseAgents(raw) {
13379
+ if (!raw) return [];
13380
+ const ids = raw.split(",").map((s) => s.trim().toLowerCase()).filter(Boolean);
13381
+ for (const id of ids) {
13382
+ if (!findAgentTarget(id)) {
13383
+ error(`Unknown agent "${id}". Use: ${SKILL_AGENT_TARGETS.map((a) => a.id).join(", ")}.`);
13384
+ }
13385
+ }
13386
+ return ids;
13387
+ }
13388
+ function resolvePlan(options) {
13389
+ const agents = parseAgents(options.agents);
13390
+ if (options.global) return { scope: "global", root: globalRoot(), agents };
13391
+ const root = findProjectRoot(process.cwd());
13392
+ if (!root || path14.resolve(root) === path14.resolve(homeDir())) {
13393
+ error("No project here: run this inside your app (a folder with package.json or .git), or pass --global.");
13394
+ }
13395
+ return { scope: "project", root, agents };
13396
+ }
13397
+ async function resolveSource(skill, plan) {
13398
+ try {
13399
+ const local = projectSkillSource(skill, plan.scope === "project" ? plan.root : process.cwd());
13400
+ if (local) return local;
13401
+ return await registrySkillSource(skill);
13402
+ } catch (err) {
13403
+ if (err instanceof SkillSourceError) error(err.message);
13404
+ throw err;
13405
+ }
13406
+ }
13407
+ function display(p9, root) {
13408
+ const rel = path14.relative(root, p9);
13409
+ return rel && !rel.startsWith("..") ? rel : p9;
13410
+ }
13411
+ function report(command, result, packageInProject) {
13412
+ const where = (p9) => display(p9, result.root);
13413
+ if (isAgentMode()) {
13414
+ json({
13415
+ command,
13416
+ skill: result.skill.name,
13417
+ name: result.skill.dirName,
13418
+ version: result.version,
13419
+ source: { kind: result.source, from: result.origin },
13420
+ scope: result.scope,
13421
+ root: result.root,
13422
+ installedTo: [
13423
+ { path: result.canonical, kind: "folder", agents: result.readers },
13424
+ ...result.links.map((l) => ({ path: path14.join(result.root, ...l.path.split("/")), kind: l.kind, agents: l.agents }))
13425
+ ],
13426
+ next: nextSteps(result, packageInProject)
13427
+ });
13428
+ return;
13429
+ }
13430
+ const scopeLabel2 = result.scope === "project" ? "this project" : "every project on this machine";
13431
+ console.log();
13432
+ console.log(` ${pc16.green("\u2713")} ${pc16.bold(`${result.skill.title} skill ${result.version}`)} installed for ${scopeLabel2}`);
13433
+ console.log();
13434
+ const from = result.source === "project" ? `${where(result.origin)} ${pc16.dim("(the version your code uses)")}` : `npm ${pc16.dim(result.scope === "project" ? `(latest ${result.skill.packageName}; not installed in this project yet)` : `(latest ${result.skill.packageName})`)}`;
13435
+ console.log(` ${pc16.dim("From")} ${from}`);
13436
+ const rows = [
13437
+ { path: where(result.canonical), agents: result.readers },
13438
+ ...result.links.map((l) => ({ path: `${l.path}${l.kind === "copy" ? pc16.dim(" (copy)") : ""}`, agents: l.agents }))
13439
+ ];
13440
+ rows.forEach((row, i) => {
13441
+ const agents = row.agents.length ? pc16.dim(row.agents.join(", ")) : pc16.dim("shared copy the links point to");
13442
+ console.log(` ${pc16.dim(i === 0 ? "To " : " ")} ${row.path} ${agents}`);
13443
+ });
13444
+ console.log();
13445
+ console.log(` ${pc16.bold("Next")}`);
13446
+ nextSteps(result, packageInProject).forEach((step, i) => console.log(` ${i + 1}. ${step}`));
13447
+ console.log();
13448
+ }
13449
+ function nextSteps(result, packageInProject) {
13450
+ const steps = [];
13451
+ if (result.scope === "project" && !packageInProject) steps.push(`Install the SDK: npm install ${result.skill.packageName}`);
13452
+ steps.push(`Ask your agent: "Add ${result.skill.title} to this app." The skill tells it every step.`);
13453
+ steps.push("Set the secrets it names in your server environment yourself; never paste them into a chat.");
13454
+ if (result.scope === "project") {
13455
+ const folders = [`${UNIVERSAL_DIR}/${result.skill.dirName}`, ...result.links.map((l) => l.path)];
13456
+ steps.push(`Commit ${folders.join(" and ")} so your team's agents share it. Any \`one\` command here refreshes it when ${result.skill.packageName} is upgraded.`);
13457
+ } else {
13458
+ steps.push(`Refresh it after a new ${result.skill.packageName} release: one skills update ${result.skill.name} --global`);
13459
+ }
13460
+ return steps;
13461
+ }
13462
+ var UNIVERSAL_DIR = ".agents/skills";
13463
+ async function skillsAddCommand(name, options) {
13464
+ const skill = requireSkill(name);
13465
+ const plan = resolvePlan(options);
13466
+ const source = await resolveSource(skill, plan);
13467
+ try {
13468
+ const result = installSkill(skill, source, plan, getCurrentVersion());
13469
+ const packageInProject = plan.scope === "project" && findInstalledPackage(plan.root, skill.packageName) !== null;
13470
+ report("skills add", result, packageInProject);
13471
+ } finally {
13472
+ source.cleanup();
13473
+ }
13474
+ }
13475
+ async function skillsUpdateCommand(name, options) {
13476
+ const skills2 = name ? [requireSkill(name)] : OPTIONAL_SKILLS;
13477
+ const plan = resolvePlan(options);
13478
+ const updated = [];
13479
+ const skipped = [];
13480
+ for (const skill of skills2) {
13481
+ const current = installedSkill(skill, plan);
13482
+ if (!current) {
13483
+ skipped.push(skill.name);
13484
+ continue;
13485
+ }
13486
+ const agents = plan.agents.length ? plan.agents : SKILL_AGENT_TARGETS.filter((a) => current.links.some((l) => l.agents.includes(a.name)) || plan.scope === "project" && a.projectDir === UNIVERSAL_DIR).map((a) => a.id);
13487
+ const source = await resolveSource(skill, plan);
13488
+ try {
13489
+ updated.push(installSkill(skill, source, { ...plan, agents }, getCurrentVersion()));
13490
+ } finally {
13491
+ source.cleanup();
13492
+ }
13493
+ }
13494
+ if (name && updated.length === 0) error(`${skills2[0].title} skill is not installed here. Add it: one skills add ${skills2[0].name}${plan.scope === "global" ? " --global" : ""}`);
13495
+ if (isAgentMode()) {
13496
+ json({
13497
+ command: "skills update",
13498
+ scope: plan.scope,
13499
+ updated: updated.map((r) => ({ skill: r.skill.name, version: r.version, source: r.source, path: r.canonical })),
13500
+ notInstalled: skipped
13501
+ });
13502
+ return;
13503
+ }
13504
+ if (updated.length === 0) {
13505
+ console.log("No optional skills are installed here. See: one skills list");
13506
+ return;
13507
+ }
13508
+ for (const r of updated) console.log(` ${pc16.green("\u2713")} ${r.skill.title} skill ${r.version} ${pc16.dim(display(r.canonical, r.root))}`);
13509
+ }
13510
+ async function skillsRemoveCommand(name, options) {
13511
+ const skill = requireSkill(name);
13512
+ const plan = resolvePlan(options);
13513
+ const result = removeSkill(skill, plan);
13514
+ if (isAgentMode()) {
13515
+ json({ command: "skills remove", skill: skill.name, scope: plan.scope, removed: result.removed, paths: result.paths });
13516
+ return;
13517
+ }
13518
+ if (!result.removed) {
13519
+ console.log(`${skill.title} skill is not installed ${plan.scope === "global" ? "globally" : "in this project"}.`);
13520
+ return;
13521
+ }
13522
+ console.log(` ${pc16.green("\u2713")} Removed the ${skill.title} skill`);
13523
+ for (const p9 of result.paths) console.log(` ${pc16.dim(display(p9, plan.root))}`);
13524
+ }
13525
+ async function skillsListCommand() {
13526
+ const projectRoot = findProjectRoot(process.cwd());
13527
+ const inProject = projectRoot && path14.resolve(projectRoot) !== path14.resolve(homeDir()) ? projectRoot : null;
13528
+ const optional = OPTIONAL_SKILLS.map((skill) => {
13529
+ const project = inProject ? installedSkill(skill, { scope: "project", root: inProject }) : null;
13530
+ const global = installedSkill(skill, { scope: "global", root: globalRoot() });
13531
+ const pkg = inProject ? findInstalledPackage(inProject, skill.packageName) : null;
13532
+ return {
13533
+ skill: skill.name,
13534
+ name: skill.dirName,
13535
+ title: skill.title,
13536
+ description: skill.description,
13537
+ package: skill.packageName,
13538
+ packageInProject: pkg?.version ?? null,
13539
+ project: project ? { version: project.version, path: project.canonical, upToDate: !pkg || project.version === pkg.version } : null,
13540
+ global: global ? { version: global.version, path: global.canonical } : null
13541
+ };
13542
+ });
13543
+ const oneSkill = {
13544
+ skill: "one",
13545
+ title: "One CLI",
13546
+ managedBy: "one init",
13547
+ installed: isSkillInstalled(),
13548
+ version: readInstalledSkillVersion()
13549
+ };
13550
+ if (isAgentMode()) {
13551
+ json({ command: "skills list", project: inProject, skills: [oneSkill, ...optional] });
13552
+ return;
13553
+ }
13554
+ console.log();
13555
+ console.log(` ${pc16.bold("one")} ${oneSkill.installed ? pc16.green(`installed ${oneSkill.version ?? ""}`.trim()) : pc16.yellow("not installed")} ${pc16.dim("managed by one init")}`);
13556
+ for (const s of optional) {
13557
+ const state = s.project ? pc16.green(`project ${s.project.version ?? ""}`.trim()) + (s.project.upToDate ? "" : pc16.yellow(` (package is ${s.packageInProject}: run one skills update ${s.skill})`)) : s.global ? pc16.green(`global ${s.global.version ?? ""}`.trim()) : pc16.dim(`not installed \u2014 one skills add ${s.skill}`);
13558
+ console.log(` ${pc16.bold(s.skill.padEnd(8))} ${state}`);
13559
+ console.log(` ${" ".repeat(8)} ${pc16.dim(s.description)}`);
13560
+ }
13561
+ console.log();
13562
+ }
13563
+
12927
13564
  // src/cli.ts
12928
13565
  silenceWarningsInAgentMode();
12929
13566
  var require4 = createRequire3(import.meta.url);
@@ -12946,6 +13583,12 @@ program.name("one").option("--agent", "Machine-readable JSON output (no colors,
12946
13583
  3. one actions execute <p> <id> <key> Execute the action
12947
13584
  one actions load <id> --section <s> More of an action's docs: a section a digest left out, --full, or --toc
12948
13585
 
13586
+ Skills for your coding agent:
13587
+ one skills list The One skill and the optional ones (installed or not)
13588
+ one skills add connect Add the One Connect skill to this project (--global for every project)
13589
+ one skills update [connect] Re-copy from the installed @withone/connect after an upgrade
13590
+ one skills remove connect Remove it
13591
+
12949
13592
  Guide:
12950
13593
  one guide [topic] Full CLI guide (topics: overview, actions, workflows, memory, sync, all)
12951
13594
 
@@ -13019,6 +13662,12 @@ program.hook("preAction", (thisCommand, actionCommand) => {
13019
13662
  } catch {
13020
13663
  }
13021
13664
  }
13665
+ if (commandName !== "skills") {
13666
+ try {
13667
+ refreshProjectSkills(process.cwd(), getCurrentVersion());
13668
+ } catch {
13669
+ }
13670
+ }
13022
13671
  });
13023
13672
  program.hook("postAction", async () => {
13024
13673
  await closeBackendIfCached();
@@ -13041,6 +13690,19 @@ program.command("login").description("Authenticate with One via browser").action
13041
13690
  program.command("logout").description("Clear local credentials").action(async () => {
13042
13691
  await logoutCommand();
13043
13692
  });
13693
+ var skills = program.command("skills").description("Optional skills for your coding agent, such as One Connect (the One skill itself comes from one init)");
13694
+ skills.command("list").description("Show the One skill and each optional skill, with where it is installed").action(async () => {
13695
+ await skillsListCommand();
13696
+ });
13697
+ skills.command("add <name>").description("Install an optional skill (connect) into this project, from the SDK version it uses").option("-g, --global", "Install for every project on this machine instead").option("--agents <ids>", "Comma-separated agents to link: claude-code, codex, cursor, amp, opencode, windsurf, kiro, goose, roo").action(async (name, options) => {
13698
+ await skillsAddCommand(name, options);
13699
+ });
13700
+ skills.command("update [name]").description("Re-copy installed optional skills from their source (all when no name is given)").option("-g, --global", "Update the global install").option("--agents <ids>", "Change which agents are linked").action(async (name, options) => {
13701
+ await skillsUpdateCommand(name, options);
13702
+ });
13703
+ skills.command("remove <name>").alias("rm").description("Remove an optional skill and the agent folders that point at it").option("-g, --global", "Remove the global install").action(async (name, options) => {
13704
+ await skillsRemoveCommand(name, options);
13705
+ });
13044
13706
  var config = program.command("config").description("Configure the CLI (access control, skills, ...)").action(async () => {
13045
13707
  await configCommand();
13046
13708
  });
@@ -13140,8 +13802,8 @@ config.command("reset").description("Remove the project config for the current d
13140
13802
  return;
13141
13803
  }
13142
13804
  }
13143
- const fs15 = await import("fs");
13144
- fs15.unlinkSync(globalPath);
13805
+ const fs16 = await import("fs");
13806
+ fs16.unlinkSync(globalPath);
13145
13807
  if (isAgentMode()) {
13146
13808
  json({ deleted: true, scope: "global" });
13147
13809
  } else {
@@ -13158,15 +13820,15 @@ config.command("reset").description("Remove the project config for the current d
13158
13820
  }
13159
13821
  return;
13160
13822
  }
13161
- const fs14 = await import("fs");
13162
- const configContent = fs14.readFileSync(resolved.path, "utf-8");
13163
- fs14.unlinkSync(resolved.path);
13823
+ const fs15 = await import("fs");
13824
+ const configContent = fs15.readFileSync(resolved.path, "utf-8");
13825
+ fs15.unlinkSync(resolved.path);
13164
13826
  const next = resolveConfig();
13165
- fs14.mkdirSync(path13.dirname(resolved.path), { recursive: true });
13166
- fs14.writeFileSync(resolved.path, configContent);
13827
+ fs15.mkdirSync(path15.dirname(resolved.path), { recursive: true });
13828
+ fs15.writeFileSync(resolved.path, configContent);
13167
13829
  let fallbackLabel;
13168
13830
  if (next.scope === "project") {
13169
- fallbackLabel = `parent project config (${path13.basename(next.projectRoot)})`;
13831
+ fallbackLabel = `parent project config (${path15.basename(next.projectRoot)})`;
13170
13832
  } else if (next.scope === "global") {
13171
13833
  fallbackLabel = "global config";
13172
13834
  } else {
@@ -13175,7 +13837,7 @@ config.command("reset").description("Remove the project config for the current d
13175
13837
  if (!isAgentMode()) {
13176
13838
  const p9 = await import("@clack/prompts");
13177
13839
  const confirmed = await p9.confirm({
13178
- message: `Delete project config for ${path13.basename(resolved.projectRoot)}? Will fall back to ${fallbackLabel}.`,
13840
+ message: `Delete project config for ${path15.basename(resolved.projectRoot)}? Will fall back to ${fallbackLabel}.`,
13179
13841
  initialValue: false
13180
13842
  });
13181
13843
  if (p9.isCancel(confirmed) || !confirmed) {
@@ -13183,9 +13845,9 @@ config.command("reset").description("Remove the project config for the current d
13183
13845
  return;
13184
13846
  }
13185
13847
  }
13186
- fs14.unlinkSync(resolved.path);
13848
+ fs15.unlinkSync(resolved.path);
13187
13849
  try {
13188
- fs14.rmdirSync(path13.dirname(resolved.path));
13850
+ fs15.rmdirSync(path15.dirname(resolved.path));
13189
13851
  } catch {
13190
13852
  }
13191
13853
  if (isAgentMode()) {
@@ -13348,22 +14010,22 @@ program.command("whoami").description("Show the user, organization, and project
13348
14010
  });
13349
14011
  return;
13350
14012
  }
13351
- const pc16 = (await import("picocolors")).default;
14013
+ const pc17 = (await import("picocolors")).default;
13352
14014
  const contextParts = [];
13353
14015
  if (whoami.organization) contextParts.push(whoami.organization.name);
13354
14016
  if (whoami.project) contextParts.push(whoami.project.name);
13355
14017
  const scopeDisplay = contextParts.length > 0 ? contextParts.join(" / ") : "Personal";
13356
- const envLabel = env === "test" ? pc16.yellow("test") : pc16.green("live");
13357
- const configLabel = configScope === "project" ? pc16.cyan("project config") : pc16.magenta("global config");
14018
+ const envLabel = env === "test" ? pc17.yellow("test") : pc17.green("live");
14019
+ const configLabel = configScope === "project" ? pc17.cyan("project config") : pc17.magenta("global config");
13358
14020
  console.log();
13359
- console.log(` ${pc16.bold(scopeDisplay)} ${pc16.dim("\xB7")} ${envLabel}`);
13360
- console.log(` ${whoami.user.name} ${pc16.dim(`(${whoami.user.email})`)}`);
13361
- if (keyName) console.log(` ${pc16.dim("Key:")} ${keyName}`);
13362
- if (whoami.organization) console.log(` ${pc16.dim("Org:")} ${whoami.organization.name} ${pc16.dim(`(${whoami.organization.id})`)}`);
13363
- if (whoami.project) console.log(` ${pc16.dim("Project:")} ${whoami.project.name} ${pc16.dim(`(${whoami.project.id})`)}`);
14021
+ console.log(` ${pc17.bold(scopeDisplay)} ${pc17.dim("\xB7")} ${envLabel}`);
14022
+ console.log(` ${whoami.user.name} ${pc17.dim(`(${whoami.user.email})`)}`);
14023
+ if (keyName) console.log(` ${pc17.dim("Key:")} ${keyName}`);
14024
+ if (whoami.organization) console.log(` ${pc17.dim("Org:")} ${whoami.organization.name} ${pc17.dim(`(${whoami.organization.id})`)}`);
14025
+ if (whoami.project) console.log(` ${pc17.dim("Project:")} ${whoami.project.name} ${pc17.dim(`(${whoami.project.id})`)}`);
13364
14026
  console.log();
13365
- console.log(` ${pc16.dim("Using")} ${configLabel}`);
13366
- console.log(` ${pc16.dim("API:")} ${apiBase}`);
14027
+ console.log(` ${pc17.dim("Using")} ${configLabel}`);
14028
+ console.log(` ${pc17.dim("API:")} ${apiBase}`);
13367
14029
  console.log();
13368
14030
  });
13369
14031
  program.command("add [platform]").description("Shortcut for: connection add").option("--tag <name>", "Tag the new connection (disambiguates multiple connections per platform in sync/flow profiles)").action(async (platform, options) => {