@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-
|
|
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
|
|
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
|
|
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" ?
|
|
1694
|
-
const currentScope = resolved.scope === "project" ?
|
|
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(`${
|
|
1702
|
-
lines.push(`${whoami2.user.name} ${
|
|
1703
|
-
if (whoami2.organization) lines.push(`${
|
|
1704
|
-
if (whoami2.project) lines.push(`${
|
|
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(`${
|
|
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
|
|
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" ?
|
|
1733
|
-
const configLabel = targetScope === "project" ?
|
|
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
|
-
`${
|
|
1736
|
-
`${whoami.user.name} ${
|
|
1735
|
+
`${pc17.bold(scopeDisplay)} ${pc17.dim("\xB7")} ${envLabel}`,
|
|
1736
|
+
`${whoami.user.name} ${pc17.dim(`(${whoami.user.email})`)}`
|
|
1737
1737
|
];
|
|
1738
|
-
if (keyName) infoLines.push(`${
|
|
1739
|
-
infoLines.push(...grantNoteLines(grant,
|
|
1740
|
-
if (whoami.organization) infoLines.push(`${
|
|
1741
|
-
if (whoami.project) infoLines.push(`${
|
|
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(`${
|
|
1743
|
+
infoLines.push(`${pc17.dim("Stored in")} ${configLabel}`);
|
|
1744
1744
|
p2.note(infoLines.join("\n"), "Logged in");
|
|
1745
1745
|
console.log();
|
|
1746
|
-
console.log(` ${
|
|
1747
|
-
console.log(` ${
|
|
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(
|
|
1749
|
+
console.log(pc17.bold(" Copy-paste this to your AI agent:"));
|
|
1750
1750
|
console.log();
|
|
1751
|
-
console.log(
|
|
1752
|
-
console.log(
|
|
1753
|
-
console.log(
|
|
1754
|
-
console.log(
|
|
1755
|
-
console.log(
|
|
1756
|
-
console.log(
|
|
1757
|
-
console.log(
|
|
1758
|
-
console.log(
|
|
1759
|
-
console.log(
|
|
1760
|
-
console.log(
|
|
1761
|
-
console.log(
|
|
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
|
-
|
|
2614
|
-
|
|
2615
|
-
|
|
2616
|
-
|
|
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
|
-
|
|
2619
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
2857
|
-
const
|
|
2858
|
-
const
|
|
2859
|
-
const
|
|
2860
|
-
const
|
|
2861
|
-
const
|
|
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
|
|
2960
|
+
access: withLevel(accessFor(conn))
|
|
2879
2961
|
})),
|
|
2962
|
+
accessSource: serverAccess.ok ? "server" : "local",
|
|
2880
2963
|
...knowledgeOnly && { knowledgeOnly: true },
|
|
2881
|
-
...
|
|
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
|
|
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
|
-
|
|
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 (
|
|
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(
|
|
3026
|
+
p4.note(
|
|
3027
|
+
`${wrapText(lines.join("\n"))}
|
|
2940
3028
|
|
|
2941
|
-
|
|
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
|
|
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${
|
|
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${
|
|
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
|
|
4661
|
+
const path16 = `${pathPrefix}[${i}]`;
|
|
4558
4662
|
if (!step || typeof step !== "object" || Array.isArray(step)) {
|
|
4559
|
-
errors.push({ path:
|
|
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: `${
|
|
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: `${
|
|
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: `${
|
|
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: `${
|
|
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: `${
|
|
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: `${
|
|
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: `${
|
|
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: `${
|
|
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 = `${
|
|
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, `${
|
|
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: `${
|
|
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: `${
|
|
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: `${
|
|
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: `${
|
|
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: `${
|
|
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: `${
|
|
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
|
|
4839
|
+
const path16 = `${pathPrefix}[${i}]`;
|
|
4736
4840
|
if (seen.has(step.id)) {
|
|
4737
|
-
errors.push({ path: `${
|
|
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], `${
|
|
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,
|
|
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:
|
|
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:
|
|
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:
|
|
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,
|
|
4920
|
+
function checkOperatorsInSelectorField(value, path16) {
|
|
4817
4921
|
if (typeof value === "string" && value.startsWith("$.")) {
|
|
4818
4922
|
if (value.includes("||")) {
|
|
4819
|
-
errors.push({ path:
|
|
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:
|
|
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, `${
|
|
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], `${
|
|
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,
|
|
5018
|
+
function walkOutputSchema(schema, path16) {
|
|
4915
5019
|
let current = schema;
|
|
4916
|
-
for (let i = 0; i <
|
|
4917
|
-
const seg =
|
|
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, (_,
|
|
6865
|
-
const parts =
|
|
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,
|
|
6894
|
-
const parts =
|
|
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
|
|
6904
|
-
stripOnePath(obj,
|
|
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
|
|
7326
|
-
stripOnePath2(record,
|
|
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
|
|
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 =
|
|
7673
|
-
memPreserved +=
|
|
7674
|
-
for (const key of
|
|
7776
|
+
pageReport = report2;
|
|
7777
|
+
memPreserved += report2.preserved;
|
|
7778
|
+
for (const key of report2.sourceKeysSeen) seenSourceKeys.add(key);
|
|
7675
7779
|
if (hasHooks && !db) {
|
|
7676
|
-
inserts =
|
|
7677
|
-
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
8075
|
-
|
|
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
|
-
|
|
8088
|
-
|
|
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
|
|
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
|
-
|
|
8126
|
-
return
|
|
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
|
-
|
|
8141
|
-
|
|
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
|
-
|
|
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
|
-
|
|
8290
|
+
report2.detectedColumns = Object.entries(first).map(([name, value]) => ({
|
|
8187
8291
|
name,
|
|
8188
8292
|
type: detectColumnType2(value)
|
|
8189
8293
|
}));
|
|
8190
|
-
|
|
8191
|
-
|
|
8192
|
-
if (profile.enrich && (profile.identityKeys?.length ?? 0) > 0 &&
|
|
8193
|
-
const merged = await enrichOneForPreview(api, profile,
|
|
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)
|
|
8300
|
+
if (preview) report2.identityKeysPreview = { ...preview, previewedAfterEnrich: true };
|
|
8197
8301
|
}
|
|
8198
8302
|
}
|
|
8199
|
-
|
|
8200
|
-
|
|
8201
|
-
return
|
|
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,
|
|
8978
|
-
let s = stats.get(
|
|
9081
|
+
function recordSample(stats, path16, value, jsType, recordIndex, totalSamples) {
|
|
9082
|
+
let s = stats.get(path16);
|
|
8979
9083
|
if (!s) {
|
|
8980
9084
|
s = {
|
|
8981
|
-
path:
|
|
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(
|
|
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,
|
|
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,
|
|
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,
|
|
9113
|
+
if (str) recordSample(stats, path16, str, kind, recordIndex, totalSamples);
|
|
9010
9114
|
return;
|
|
9011
9115
|
}
|
|
9012
9116
|
if (Array.isArray(record)) {
|
|
9013
|
-
const childPath =
|
|
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 =
|
|
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
|
|
9375
|
-
if (
|
|
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, ...
|
|
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,
|
|
9489
|
+
const searchablePreview = options.showSearchable ? buildSearchablePreview(profile, report2.samples) : null;
|
|
9386
9490
|
if (isAgentMode()) {
|
|
9387
|
-
json({ ...
|
|
9491
|
+
json({ ...report2, ...searchablePreview ? { searchable: searchablePreview } : {} });
|
|
9388
9492
|
return;
|
|
9389
9493
|
}
|
|
9390
|
-
for (const c of
|
|
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 (
|
|
9498
|
+
if (report2.detectedColumns && report2.detectedColumns.length > 0) {
|
|
9395
9499
|
console.log(`
|
|
9396
9500
|
${pc11.bold("Detected columns:")}`);
|
|
9397
|
-
for (const col of
|
|
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 (
|
|
9401
|
-
console.log(pc11.dim(` ... and ${
|
|
9504
|
+
if (report2.detectedColumns.length > 20) {
|
|
9505
|
+
console.log(pc11.dim(` ... and ${report2.detectedColumns.length - 20} more`));
|
|
9402
9506
|
}
|
|
9403
9507
|
}
|
|
9404
|
-
if (
|
|
9405
|
-
const { perRecord, sampleKeys, entityFanOut, resolvesAfterEnrich } =
|
|
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
|
-
${
|
|
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((
|
|
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
|
|
9724
|
-
const 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
|
|
10413
|
-
const val = getPath(copy,
|
|
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,
|
|
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,
|
|
10421
|
-
const parts =
|
|
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,
|
|
10430
|
-
const parts =
|
|
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,
|
|
10441
|
-
const parts =
|
|
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
|
|
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
|
|
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
|
|
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,
|
|
12914
|
+
function formatWhoami(config2, apiKey, pc17) {
|
|
12810
12915
|
const whoami = config2.whoami;
|
|
12811
12916
|
const env = getEnvFromApiKey(apiKey);
|
|
12812
|
-
const envLabel = env === "test" ?
|
|
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(`${
|
|
12820
|
-
lines.push(`${whoami.user.name} ${
|
|
12821
|
-
if (config2.apiKeyName) lines.push(`${
|
|
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(`${
|
|
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
|
|
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(`${
|
|
12863
|
-
infoLines.push(...formatWhoami(projectConfig, projectConfig.apiKey,
|
|
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(`${
|
|
12868
|
-
infoLines.push(...formatWhoami(globalConfig, globalConfig.apiKey,
|
|
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(`${
|
|
12887
|
-
infoLines.push(...formatWhoami(projectConfig, projectConfig.apiKey,
|
|
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(`${
|
|
12893
|
-
infoLines.push(...formatWhoami(globalConfig, globalConfig.apiKey,
|
|
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
|
|
13144
|
-
|
|
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
|
|
13162
|
-
const configContent =
|
|
13163
|
-
|
|
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
|
-
|
|
13166
|
-
|
|
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 (${
|
|
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 ${
|
|
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
|
-
|
|
13848
|
+
fs15.unlinkSync(resolved.path);
|
|
13187
13849
|
try {
|
|
13188
|
-
|
|
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
|
|
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" ?
|
|
13357
|
-
const configLabel = configScope === "project" ?
|
|
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(` ${
|
|
13360
|
-
console.log(` ${whoami.user.name} ${
|
|
13361
|
-
if (keyName) console.log(` ${
|
|
13362
|
-
if (whoami.organization) console.log(` ${
|
|
13363
|
-
if (whoami.project) console.log(` ${
|
|
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(` ${
|
|
13366
|
-
console.log(` ${
|
|
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) => {
|