@withone/cli 1.49.0 → 1.51.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/README.md +50 -4
- package/dist/{chunk-2TWFL3CS.js → chunk-D6W756DN.js} +3 -2
- package/dist/{chunk-4WWK3GO5.js → chunk-UV4YYDF3.js} +1 -1
- package/dist/{chunk-7RV7T5PX.js → chunk-Z4KAQGIG.js} +139 -15
- package/dist/{chunk-GXOIR3GF.js → chunk-ZOJW6IMU.js} +278 -13
- package/dist/{flow-runner-IBB42ED2.js → flow-runner-YDKAYGZW.js} +1 -1
- package/dist/index.js +539 -262
- package/dist/{migrate-TRUZVAT5.js → migrate-LXDIZMXH.js} +2 -2
- package/dist/{runtime-2PRVL2NO.js → runtime-HDZCBTWH.js} +1 -1
- package/dist/{schema-LCRWSFD5.js → schema-SYMG6SRC.js} +1 -1
- package/dist/{sql-HG7R2JML.js → sql-AZDCMY7G.js} +2 -2
- package/package.json +2 -1
- package/profiles/fathom/meetings.json +4 -0
- package/profiles/gmail/gmailThreads.json +5 -0
- package/profiles/google-calendar/events.json +5 -1
- package/skills/one/SKILL.md +63 -6
package/dist/index.js
CHANGED
|
@@ -3,6 +3,7 @@ import {
|
|
|
3
3
|
FLOW_SCHEMA,
|
|
4
4
|
FlowRunner,
|
|
5
5
|
OneApi,
|
|
6
|
+
PERMISSION_METHODS,
|
|
6
7
|
TimeoutError,
|
|
7
8
|
buildActionKnowledgeWithGuidance,
|
|
8
9
|
buildCacheMeta,
|
|
@@ -31,19 +32,22 @@ import {
|
|
|
31
32
|
validateActionInput,
|
|
32
33
|
walkSteps,
|
|
33
34
|
writeCache
|
|
34
|
-
} from "./chunk-
|
|
35
|
+
} from "./chunk-D6W756DN.js";
|
|
35
36
|
import {
|
|
36
37
|
memSqlCommand
|
|
37
|
-
} from "./chunk-
|
|
38
|
+
} from "./chunk-UV4YYDF3.js";
|
|
38
39
|
import {
|
|
40
|
+
collectIdentityKeys,
|
|
39
41
|
countRecords,
|
|
40
42
|
deleteDatabase,
|
|
41
43
|
deleteRecords,
|
|
42
44
|
dropTable,
|
|
43
45
|
ensureTable,
|
|
44
46
|
evolveSchema,
|
|
47
|
+
extractSearchableFromPaths,
|
|
45
48
|
generateTemplate,
|
|
46
49
|
getDatabaseSize,
|
|
50
|
+
getSearchablePaths,
|
|
47
51
|
isSqliteAvailable,
|
|
48
52
|
listBuiltinProfiles,
|
|
49
53
|
listProfiles,
|
|
@@ -53,13 +57,15 @@ import {
|
|
|
53
57
|
openDatabase,
|
|
54
58
|
readProfile,
|
|
55
59
|
rebuildFtsIndex,
|
|
60
|
+
resolveEntityIdentity,
|
|
56
61
|
resolveProfileConnectionKey,
|
|
57
62
|
sanitizeTableName,
|
|
58
63
|
tableExists,
|
|
59
64
|
upsertRecords,
|
|
60
65
|
writeDraftProfile,
|
|
66
|
+
writePageToMemory,
|
|
61
67
|
writeProfile
|
|
62
|
-
} from "./chunk-
|
|
68
|
+
} from "./chunk-ZOJW6IMU.js";
|
|
63
69
|
import {
|
|
64
70
|
getByDotPath
|
|
65
71
|
} from "./chunk-44CV5IMX.js";
|
|
@@ -92,9 +98,8 @@ import {
|
|
|
92
98
|
getBackendPlugin,
|
|
93
99
|
listBackendPlugins,
|
|
94
100
|
loadBackendFromConfig,
|
|
95
|
-
updateRecord
|
|
96
|
-
|
|
97
|
-
} from "./chunk-7RV7T5PX.js";
|
|
101
|
+
updateRecord
|
|
102
|
+
} from "./chunk-Z4KAQGIG.js";
|
|
98
103
|
import {
|
|
99
104
|
DEFAULT_MEMORY_CONFIG,
|
|
100
105
|
defaultSearchableText,
|
|
@@ -1032,25 +1037,25 @@ async function loginCommand() {
|
|
|
1032
1037
|
let targetScope = "global";
|
|
1033
1038
|
const existingKey = getApiKey();
|
|
1034
1039
|
if (existingKey) {
|
|
1035
|
-
const
|
|
1040
|
+
const pc16 = (await import("picocolors")).default;
|
|
1036
1041
|
const resolved2 = resolveConfig();
|
|
1037
1042
|
const whoami2 = resolved2.config?.whoami;
|
|
1038
1043
|
const env2 = getEnvFromApiKey(existingKey);
|
|
1039
|
-
const envLabel2 = env2 === "test" ?
|
|
1040
|
-
const currentScope = resolved2.scope === "project" ?
|
|
1044
|
+
const envLabel2 = env2 === "test" ? pc16.yellow("test") : pc16.green("live");
|
|
1045
|
+
const currentScope = resolved2.scope === "project" ? pc16.cyan("local config") : pc16.magenta("global config");
|
|
1041
1046
|
const lines = ["You are already logged in.", ""];
|
|
1042
1047
|
if (whoami2) {
|
|
1043
1048
|
const contextParts2 = [];
|
|
1044
1049
|
if (whoami2.organization) contextParts2.push(whoami2.organization.name);
|
|
1045
1050
|
if (whoami2.project) contextParts2.push(whoami2.project.name);
|
|
1046
1051
|
const scopeDisplay2 = contextParts2.length > 0 ? contextParts2.join(" / ") : "Personal";
|
|
1047
|
-
lines.push(`${
|
|
1048
|
-
lines.push(`${whoami2.user.name} ${
|
|
1049
|
-
if (whoami2.organization) lines.push(`${
|
|
1050
|
-
if (whoami2.project) lines.push(`${
|
|
1052
|
+
lines.push(`${pc16.bold(scopeDisplay2)} ${pc16.dim("\xB7")} ${envLabel2}`);
|
|
1053
|
+
lines.push(`${whoami2.user.name} ${pc16.dim(`(${whoami2.user.email})`)}`);
|
|
1054
|
+
if (whoami2.organization) lines.push(`${pc16.dim("Org:")} ${whoami2.organization.name}`);
|
|
1055
|
+
if (whoami2.project) lines.push(`${pc16.dim("Project:")} ${whoami2.project.name}`);
|
|
1051
1056
|
}
|
|
1052
1057
|
lines.push("");
|
|
1053
|
-
lines.push(`${
|
|
1058
|
+
lines.push(`${pc16.dim("Stored in")} ${currentScope}`);
|
|
1054
1059
|
p2.note(lines.join("\n"));
|
|
1055
1060
|
const scopeChoice = await p2.select({
|
|
1056
1061
|
message: "Where would you like to log in?",
|
|
@@ -1073,40 +1078,40 @@ async function loginCommand() {
|
|
|
1073
1078
|
if (resolved.config) {
|
|
1074
1079
|
writeConfig({ ...resolved.config, whoami }, targetScope);
|
|
1075
1080
|
}
|
|
1076
|
-
const
|
|
1081
|
+
const pc15 = (await import("picocolors")).default;
|
|
1077
1082
|
const env = getEnvFromApiKey(apiKey);
|
|
1078
1083
|
const contextParts = [];
|
|
1079
1084
|
if (whoami.organization) contextParts.push(whoami.organization.name);
|
|
1080
1085
|
if (whoami.project) contextParts.push(whoami.project.name);
|
|
1081
1086
|
const scopeDisplay = contextParts.length > 0 ? contextParts.join(" / ") : "Personal";
|
|
1082
|
-
const envLabel = env === "test" ?
|
|
1083
|
-
const configLabel = targetScope === "project" ?
|
|
1087
|
+
const envLabel = env === "test" ? pc15.yellow("test") : pc15.green("live");
|
|
1088
|
+
const configLabel = targetScope === "project" ? pc15.cyan("local config") : pc15.magenta("global config");
|
|
1084
1089
|
const infoLines = [
|
|
1085
|
-
`${
|
|
1086
|
-
`${whoami.user.name} ${
|
|
1090
|
+
`${pc15.bold(scopeDisplay)} ${pc15.dim("\xB7")} ${envLabel}`,
|
|
1091
|
+
`${whoami.user.name} ${pc15.dim(`(${whoami.user.email})`)}`
|
|
1087
1092
|
];
|
|
1088
|
-
if (whoami.organization) infoLines.push(`${
|
|
1089
|
-
if (whoami.project) infoLines.push(`${
|
|
1093
|
+
if (whoami.organization) infoLines.push(`${pc15.dim("Org:")} ${whoami.organization.name}`);
|
|
1094
|
+
if (whoami.project) infoLines.push(`${pc15.dim("Project:")} ${whoami.project.name}`);
|
|
1090
1095
|
infoLines.push("");
|
|
1091
|
-
infoLines.push(`${
|
|
1096
|
+
infoLines.push(`${pc15.dim("Stored in")} ${configLabel}`);
|
|
1092
1097
|
p2.note(infoLines.join("\n"), "Logged in");
|
|
1093
1098
|
console.log();
|
|
1094
|
-
console.log(` ${
|
|
1095
|
-
console.log(` ${
|
|
1099
|
+
console.log(` ${pc15.dim("Next steps:")}`);
|
|
1100
|
+
console.log(` ${pc15.cyan("one add <platform>")} ${pc15.dim("\u2014")} Connect a platform (e.g. gmail, slack, stripe)`);
|
|
1096
1101
|
console.log();
|
|
1097
|
-
console.log(
|
|
1102
|
+
console.log(pc15.bold(" Copy-paste this to your AI agent:"));
|
|
1098
1103
|
console.log();
|
|
1099
|
-
console.log(
|
|
1100
|
-
console.log(
|
|
1101
|
-
console.log(
|
|
1102
|
-
console.log(
|
|
1103
|
-
console.log(
|
|
1104
|
-
console.log(
|
|
1105
|
-
console.log(
|
|
1106
|
-
console.log(
|
|
1107
|
-
console.log(
|
|
1108
|
-
console.log(
|
|
1109
|
-
console.log(
|
|
1104
|
+
console.log(pc15.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"));
|
|
1105
|
+
console.log(pc15.cyan(" \u2502"));
|
|
1106
|
+
console.log(pc15.cyan(" \u2502 The One CLI (`one`) is now installed on this machine. It"));
|
|
1107
|
+
console.log(pc15.cyan(" \u2502 lets you interact with 400+ third-party platforms (Gmail,"));
|
|
1108
|
+
console.log(pc15.cyan(" \u2502 Slack, Shopify, HubSpot, Stripe, GitHub, etc.) through"));
|
|
1109
|
+
console.log(pc15.cyan(" \u2502 their APIs. Whenever you need to interact with any"));
|
|
1110
|
+
console.log(pc15.cyan(" \u2502 third-party platform or external service, use the `one`"));
|
|
1111
|
+
console.log(pc15.cyan(" \u2502 skill. Please save this to your project instructions or"));
|
|
1112
|
+
console.log(pc15.cyan(" \u2502 memory so you remember it in future conversations."));
|
|
1113
|
+
console.log(pc15.cyan(" \u2502"));
|
|
1114
|
+
console.log(pc15.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"));
|
|
1110
1115
|
console.log();
|
|
1111
1116
|
p2.outro("Happy building!");
|
|
1112
1117
|
}
|
|
@@ -1942,6 +1947,54 @@ function maskApiKey(key) {
|
|
|
1942
1947
|
import * as p4 from "@clack/prompts";
|
|
1943
1948
|
import pc4 from "picocolors";
|
|
1944
1949
|
|
|
1950
|
+
// src/lib/access.ts
|
|
1951
|
+
async function resolveAllowedActions(api, actionIds) {
|
|
1952
|
+
if (actionIds.includes("*")) return [];
|
|
1953
|
+
const resolved = await Promise.all(
|
|
1954
|
+
actionIds.map(async (actionId) => {
|
|
1955
|
+
try {
|
|
1956
|
+
const { details } = await resolveActionDetails(api, actionId, { warn: () => {
|
|
1957
|
+
} });
|
|
1958
|
+
if (!details.connectionPlatform) return null;
|
|
1959
|
+
return {
|
|
1960
|
+
actionId,
|
|
1961
|
+
title: details.title,
|
|
1962
|
+
method: details.method,
|
|
1963
|
+
platform: details.connectionPlatform
|
|
1964
|
+
};
|
|
1965
|
+
} catch {
|
|
1966
|
+
return null;
|
|
1967
|
+
}
|
|
1968
|
+
})
|
|
1969
|
+
);
|
|
1970
|
+
return resolved.filter((a) => a !== null);
|
|
1971
|
+
}
|
|
1972
|
+
function computeConnectionAccess(platform, permissions, allowedActionIds, grantedActions) {
|
|
1973
|
+
if (!allowedActionIds.includes("*")) {
|
|
1974
|
+
const actions2 = grantedActions.filter((a) => a.platform === platform).map(({ actionId, title, method }) => ({ actionId, title, method }));
|
|
1975
|
+
return { policy: "actions", actions: actions2 };
|
|
1976
|
+
}
|
|
1977
|
+
const methods = PERMISSION_METHODS[permissions];
|
|
1978
|
+
if (methods !== null) {
|
|
1979
|
+
return { policy: "methods", methods };
|
|
1980
|
+
}
|
|
1981
|
+
return { policy: "full" };
|
|
1982
|
+
}
|
|
1983
|
+
function formatAccess(access, maxActions = 2) {
|
|
1984
|
+
switch (access.policy) {
|
|
1985
|
+
case "full":
|
|
1986
|
+
return "full";
|
|
1987
|
+
case "methods":
|
|
1988
|
+
return access.methods.join(", ");
|
|
1989
|
+
case "actions": {
|
|
1990
|
+
if (access.actions.length === 0) return "none";
|
|
1991
|
+
const shown = access.actions.slice(0, maxActions).map((a) => `${a.title} (${a.method})`);
|
|
1992
|
+
const rest = access.actions.length - shown.length;
|
|
1993
|
+
return rest > 0 ? `${shown.join(", ")} +${rest} more` : shown.join(", ");
|
|
1994
|
+
}
|
|
1995
|
+
}
|
|
1996
|
+
}
|
|
1997
|
+
|
|
1945
1998
|
// src/lib/platforms.ts
|
|
1946
1999
|
function findPlatform(platforms, query) {
|
|
1947
2000
|
const normalizedQuery = query.toLowerCase().trim();
|
|
@@ -2155,8 +2208,16 @@ async function connectionListCommand(options) {
|
|
|
2155
2208
|
try {
|
|
2156
2209
|
const allConnections = await api.listConnections();
|
|
2157
2210
|
const ac = getAccessControlFromAllSources();
|
|
2211
|
+
const permissions = ac.permissions || "admin";
|
|
2158
2212
|
const allowedKeys = ac.connectionKeys || ["*"];
|
|
2213
|
+
const actionIds = ac.actionIds || ["*"];
|
|
2214
|
+
const knowledgeOnly = ac.knowledgeAgent || false;
|
|
2159
2215
|
const accessFiltered = allowedKeys.includes("*") ? allConnections : allConnections.filter((conn) => allowedKeys.includes(conn.key));
|
|
2216
|
+
const resolvedActions = await resolveAllowedActions(api, actionIds);
|
|
2217
|
+
const unresolvedActionIds = actionIds.includes("*") ? [] : actionIds.filter((id) => !resolvedActions.some((a) => a.actionId === id));
|
|
2218
|
+
const grantedActions = resolvedActions.filter((a) => isMethodAllowed(a.method, permissions));
|
|
2219
|
+
const accessFor = (platform) => computeConnectionAccess(platform, permissions, actionIds, grantedActions);
|
|
2220
|
+
const hintText = accessHint(permissions, actionIds, knowledgeOnly);
|
|
2160
2221
|
const searchQuery = options?.search?.toLowerCase();
|
|
2161
2222
|
const filtered = searchQuery ? accessFiltered.filter((conn) => conn.platform.toLowerCase().includes(searchQuery)) : accessFiltered;
|
|
2162
2223
|
const limitArg = options?.limit ? parseInt(options.limit, 10) : void 0;
|
|
@@ -2172,8 +2233,12 @@ async function connectionListCommand(options) {
|
|
|
2172
2233
|
state: conn.state,
|
|
2173
2234
|
key: conn.key,
|
|
2174
2235
|
...conn.name && { name: conn.name },
|
|
2175
|
-
...conn.tags?.length && { tags: conn.tags }
|
|
2236
|
+
...conn.tags?.length && { tags: conn.tags },
|
|
2237
|
+
access: accessFor(conn.platform)
|
|
2176
2238
|
})),
|
|
2239
|
+
...knowledgeOnly && { knowledgeOnly: true },
|
|
2240
|
+
...unresolvedActionIds.length > 0 && { unresolvedActionIds },
|
|
2241
|
+
...hintText && { accessHint: hintText },
|
|
2177
2242
|
...limited.length < filtered.length && {
|
|
2178
2243
|
hint: `Showing ${limited.length} of ${filtered.length} connections. Use --search <query> to filter by platform or --limit <n> to see more.`
|
|
2179
2244
|
}
|
|
@@ -2206,20 +2271,34 @@ Add one with: ${pc4.cyan("one connection add gmail")}`,
|
|
|
2206
2271
|
platform: conn.platform,
|
|
2207
2272
|
state: conn.state,
|
|
2208
2273
|
key: conn.key,
|
|
2209
|
-
tags: conn.tags?.length ? conn.tags.join(", ") : ""
|
|
2274
|
+
tags: conn.tags?.length ? conn.tags.join(", ") : "",
|
|
2275
|
+
access: formatAccess(accessFor(conn.platform))
|
|
2210
2276
|
}));
|
|
2211
2277
|
const hasTags = rows.some((r) => r.tags);
|
|
2278
|
+
const hasScopedAccess = rows.some((r) => r.access !== "full");
|
|
2212
2279
|
printTable(
|
|
2213
2280
|
[
|
|
2214
2281
|
{ key: "status", label: "" },
|
|
2215
2282
|
{ key: "platform", label: "Platform" },
|
|
2216
2283
|
{ key: "state", label: "Status" },
|
|
2217
2284
|
{ key: "key", label: "Connection Key", color: pc4.dim },
|
|
2218
|
-
...hasTags ? [{ key: "tags", label: "Tags", color: pc4.dim }] : []
|
|
2285
|
+
...hasTags ? [{ key: "tags", label: "Tags", color: pc4.dim }] : [],
|
|
2286
|
+
...hasScopedAccess ? [{ key: "access", label: "Access", color: pc4.yellow }] : []
|
|
2219
2287
|
],
|
|
2220
2288
|
rows
|
|
2221
2289
|
);
|
|
2222
2290
|
console.log();
|
|
2291
|
+
if (hintText) {
|
|
2292
|
+
const lines = [hintText];
|
|
2293
|
+
if (unresolvedActionIds.length > 0) {
|
|
2294
|
+
lines.push(
|
|
2295
|
+
`Could not resolve ${unresolvedActionIds.length} allowlisted action id(s): ${unresolvedActionIds.join(", ")}`
|
|
2296
|
+
);
|
|
2297
|
+
}
|
|
2298
|
+
p4.note(`${wrapText(lines.join("\n"))}
|
|
2299
|
+
|
|
2300
|
+
Change it with: ${pc4.cyan("one config")}`, "Access");
|
|
2301
|
+
}
|
|
2223
2302
|
if (displayed.length < filtered.length) {
|
|
2224
2303
|
p4.note(
|
|
2225
2304
|
`Showing ${displayed.length} of ${filtered.length}. Increase --limit or run without it to see all.`,
|
|
@@ -2292,6 +2371,37 @@ async function connectionDeleteCommand(connectionKey, options) {
|
|
|
2292
2371
|
error(`Error: ${error2 instanceof Error ? error2.message : "Unknown error"}`);
|
|
2293
2372
|
}
|
|
2294
2373
|
}
|
|
2374
|
+
function accessHint(permissions, actionIds, knowledgeOnly) {
|
|
2375
|
+
const parts = [];
|
|
2376
|
+
if (!actionIds.includes("*")) {
|
|
2377
|
+
parts.push(
|
|
2378
|
+
"Action-scoped: each connection's `access.actions` are the only actions you may run \u2014 use them directly, no `actions search` needed."
|
|
2379
|
+
);
|
|
2380
|
+
} else if (permissions !== "admin") {
|
|
2381
|
+
const methods = PERMISSION_METHODS[permissions]?.join(", ") ?? "";
|
|
2382
|
+
parts.push(`Permission level "${permissions}": only ${methods} actions will execute.`);
|
|
2383
|
+
}
|
|
2384
|
+
if (knowledgeOnly) {
|
|
2385
|
+
parts.push("Knowledge-only mode: `actions execute` is disabled \u2014 read knowledge and write code instead.");
|
|
2386
|
+
}
|
|
2387
|
+
return parts.length > 0 ? parts.join(" ") : null;
|
|
2388
|
+
}
|
|
2389
|
+
function wrapText(text5, width = 72) {
|
|
2390
|
+
return text5.split("\n").map((paragraph) => {
|
|
2391
|
+
const lines = [];
|
|
2392
|
+
let line = "";
|
|
2393
|
+
for (const word of paragraph.split(/\s+/)) {
|
|
2394
|
+
if (line && line.length + 1 + word.length > width) {
|
|
2395
|
+
lines.push(line);
|
|
2396
|
+
line = word;
|
|
2397
|
+
} else {
|
|
2398
|
+
line = line ? `${line} ${word}` : word;
|
|
2399
|
+
}
|
|
2400
|
+
}
|
|
2401
|
+
if (line) lines.push(line);
|
|
2402
|
+
return lines.join("\n");
|
|
2403
|
+
}).join("\n");
|
|
2404
|
+
}
|
|
2295
2405
|
function getStatusIndicator(state) {
|
|
2296
2406
|
switch (state) {
|
|
2297
2407
|
case "operational":
|
|
@@ -5374,152 +5484,6 @@ function runShellHook(command, events) {
|
|
|
5374
5484
|
});
|
|
5375
5485
|
}
|
|
5376
5486
|
|
|
5377
|
-
// src/lib/memory/sync/mem-writer.ts
|
|
5378
|
-
function resolveWildcardPath(root, path12) {
|
|
5379
|
-
const segments = path12.split("[]");
|
|
5380
|
-
if (segments.length === 1) {
|
|
5381
|
-
return [getByDotPath(root, path12)];
|
|
5382
|
-
}
|
|
5383
|
-
const recurse = (value, idx) => {
|
|
5384
|
-
if (value === null || value === void 0) return [];
|
|
5385
|
-
const segment = segments[idx];
|
|
5386
|
-
const head = segment.startsWith(".") ? segment.slice(1) : segment;
|
|
5387
|
-
if (idx === segments.length - 1) {
|
|
5388
|
-
if (!Array.isArray(value)) return [];
|
|
5389
|
-
if (head === "") return value;
|
|
5390
|
-
return value.map((el) => getByDotPath(el, head));
|
|
5391
|
-
}
|
|
5392
|
-
if (!Array.isArray(value)) return [];
|
|
5393
|
-
const results = [];
|
|
5394
|
-
for (const el of value) {
|
|
5395
|
-
const next = head === "" ? el : getByDotPath(el, head);
|
|
5396
|
-
results.push(...recurse(next, idx + 1));
|
|
5397
|
-
}
|
|
5398
|
-
return results;
|
|
5399
|
-
};
|
|
5400
|
-
const firstHead = segments[0];
|
|
5401
|
-
const firstValue = firstHead === "" ? root : getByDotPath(root, firstHead);
|
|
5402
|
-
return recurse(firstValue, 1);
|
|
5403
|
-
}
|
|
5404
|
-
function extractSearchableFromPaths(record, paths) {
|
|
5405
|
-
const parts = [];
|
|
5406
|
-
const perPath = [];
|
|
5407
|
-
for (const path12 of paths) {
|
|
5408
|
-
const values = resolveWildcardPath(record, path12);
|
|
5409
|
-
const collected = [];
|
|
5410
|
-
const absorb = (v) => {
|
|
5411
|
-
if (v === null || v === void 0) return;
|
|
5412
|
-
if (typeof v === "string") {
|
|
5413
|
-
const t = v.trim();
|
|
5414
|
-
if (t) collected.push(t);
|
|
5415
|
-
} else if (typeof v === "number" || typeof v === "boolean") {
|
|
5416
|
-
collected.push(String(v));
|
|
5417
|
-
} else if (Array.isArray(v)) {
|
|
5418
|
-
for (const inner of v) absorb(inner);
|
|
5419
|
-
}
|
|
5420
|
-
};
|
|
5421
|
-
for (const v of values) absorb(v);
|
|
5422
|
-
if (collected.length > 0) {
|
|
5423
|
-
parts.push(...collected);
|
|
5424
|
-
perPath.push({ path: path12, found: true, sample: collected.join(" ").slice(0, 80) });
|
|
5425
|
-
} else {
|
|
5426
|
-
perPath.push({ path: path12, found: false, sample: "" });
|
|
5427
|
-
}
|
|
5428
|
-
}
|
|
5429
|
-
const text5 = parts.join(" ").replace(/\s+/g, " ").trim();
|
|
5430
|
-
return { text: text5, paths: perPath };
|
|
5431
|
-
}
|
|
5432
|
-
function getSearchablePaths(profile) {
|
|
5433
|
-
const paths = profile.memory?.searchable;
|
|
5434
|
-
if (!paths || !Array.isArray(paths) || paths.length === 0) return void 0;
|
|
5435
|
-
return paths;
|
|
5436
|
-
}
|
|
5437
|
-
async function writePageToMemory(profile, records, opts = {}) {
|
|
5438
|
-
const report = {
|
|
5439
|
-
attempted: 0,
|
|
5440
|
-
inserted: 0,
|
|
5441
|
-
updated: 0,
|
|
5442
|
-
skipped: 0,
|
|
5443
|
-
sourceKeysSeen: [],
|
|
5444
|
-
inserts: [],
|
|
5445
|
-
updates: []
|
|
5446
|
-
};
|
|
5447
|
-
const type = `${profile.platform}/${profile.model}`;
|
|
5448
|
-
const identityKey = profile.identityKey;
|
|
5449
|
-
const embedFlag = opts.embedOverride ?? deriveEmbedFlag(profile);
|
|
5450
|
-
const searchablePaths = getSearchablePaths(profile);
|
|
5451
|
-
for (const record of records) {
|
|
5452
|
-
report.attempted++;
|
|
5453
|
-
const externalId = getByDotPath(record, profile.idField);
|
|
5454
|
-
if (externalId === void 0 || externalId === null || externalId === "") {
|
|
5455
|
-
report.skipped++;
|
|
5456
|
-
continue;
|
|
5457
|
-
}
|
|
5458
|
-
if (typeof externalId === "object") {
|
|
5459
|
-
report.skipped++;
|
|
5460
|
-
continue;
|
|
5461
|
-
}
|
|
5462
|
-
const sourceKey = `${type}:${String(externalId)}`;
|
|
5463
|
-
const keys = [sourceKey];
|
|
5464
|
-
if (identityKey) {
|
|
5465
|
-
const raw = getByDotPath(record, identityKey);
|
|
5466
|
-
if (raw !== null && raw !== void 0 && raw !== "") {
|
|
5467
|
-
keys.push(`${deriveIdentityPrefix(identityKey)}:${String(raw).toLowerCase().trim()}`);
|
|
5468
|
-
}
|
|
5469
|
-
}
|
|
5470
|
-
const data = { ...record };
|
|
5471
|
-
for (const k of Object.keys(data)) {
|
|
5472
|
-
if (k.startsWith("_")) delete data[k];
|
|
5473
|
-
}
|
|
5474
|
-
const searchable_text = searchablePaths ? extractSearchableFromPaths(record, searchablePaths).text : void 0;
|
|
5475
|
-
try {
|
|
5476
|
-
const res = await upsertRecord(
|
|
5477
|
-
{
|
|
5478
|
-
type,
|
|
5479
|
-
data,
|
|
5480
|
-
keys,
|
|
5481
|
-
searchable_text,
|
|
5482
|
-
sources: {
|
|
5483
|
-
[sourceKey]: {
|
|
5484
|
-
last_synced_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
5485
|
-
metadata: { source: profile.platform }
|
|
5486
|
-
}
|
|
5487
|
-
},
|
|
5488
|
-
tags: ["synced", profile.platform],
|
|
5489
|
-
embed: embedFlag
|
|
5490
|
-
},
|
|
5491
|
-
// Sync must REPLACE: if a field vanished at the source (phone
|
|
5492
|
-
// number removed, Gmail thread archived) it must also vanish
|
|
5493
|
-
// from memory. The default merge behaviour is correct for
|
|
5494
|
-
// interactive `mem add` / `mem update`, wrong for sync.
|
|
5495
|
-
{ embed: embedFlag, replace: true }
|
|
5496
|
-
);
|
|
5497
|
-
report.sourceKeysSeen.push(sourceKey);
|
|
5498
|
-
if (res.action === "inserted") {
|
|
5499
|
-
report.inserted++;
|
|
5500
|
-
if (opts.capturePerAction) report.inserts.push(record);
|
|
5501
|
-
} else {
|
|
5502
|
-
report.updated++;
|
|
5503
|
-
if (opts.capturePerAction) report.updates.push(record);
|
|
5504
|
-
}
|
|
5505
|
-
} catch {
|
|
5506
|
-
report.skipped++;
|
|
5507
|
-
}
|
|
5508
|
-
}
|
|
5509
|
-
return report;
|
|
5510
|
-
}
|
|
5511
|
-
function deriveEmbedFlag(profile) {
|
|
5512
|
-
if (profile.memory && typeof profile.memory.embed === "boolean") return profile.memory.embed;
|
|
5513
|
-
return false;
|
|
5514
|
-
}
|
|
5515
|
-
function deriveIdentityPrefix(dotPath) {
|
|
5516
|
-
const lower = dotPath.toLowerCase();
|
|
5517
|
-
if (lower.includes("email")) return "email";
|
|
5518
|
-
if (lower.includes("phone")) return "phone";
|
|
5519
|
-
if (lower.includes("domain")) return "domain";
|
|
5520
|
-
return "id";
|
|
5521
|
-
}
|
|
5522
|
-
|
|
5523
5487
|
// src/lib/memory/sync/transform.ts
|
|
5524
5488
|
import { spawn as spawn3 } from "child_process";
|
|
5525
5489
|
var TRANSFORM_TIMEOUT_MS = 6e4;
|
|
@@ -5683,6 +5647,32 @@ function pickFields(obj, fields) {
|
|
|
5683
5647
|
}
|
|
5684
5648
|
return result;
|
|
5685
5649
|
}
|
|
5650
|
+
function findEnrichedIds(db, model, idField, config2, records, tableCreated) {
|
|
5651
|
+
const enriched = /* @__PURE__ */ new Set();
|
|
5652
|
+
if (!tableCreated || records.length === 0) return enriched;
|
|
5653
|
+
const tsField = config2.timestampField ?? "_enriched_at";
|
|
5654
|
+
const safeTable = model.replace(/[^a-zA-Z0-9_]/g, "_");
|
|
5655
|
+
const safeIdField = idField.replace(/"/g, '""');
|
|
5656
|
+
const safeTsField = tsField.replace(/"/g, '""');
|
|
5657
|
+
try {
|
|
5658
|
+
const cols = db.prepare(`PRAGMA table_info("${safeTable}")`).all().map((c) => c.name);
|
|
5659
|
+
if (!cols.includes(tsField) || !cols.includes(idField)) return enriched;
|
|
5660
|
+
const ids = records.map((r) => r[idField]).filter((id) => typeof id === "string" || typeof id === "number");
|
|
5661
|
+
if (ids.length === 0) return enriched;
|
|
5662
|
+
const CHUNK = 500;
|
|
5663
|
+
for (let i = 0; i < ids.length; i += CHUNK) {
|
|
5664
|
+
const chunk = ids.slice(i, i + CHUNK);
|
|
5665
|
+
const placeholders = chunk.map(() => "?").join(",");
|
|
5666
|
+
const rows = db.prepare(
|
|
5667
|
+
`SELECT "${safeIdField}" AS id FROM "${safeTable}" WHERE "${safeTsField}" IS NOT NULL AND "${safeIdField}" IN (${placeholders})`
|
|
5668
|
+
).all(...chunk);
|
|
5669
|
+
for (const row of rows) enriched.add(String(row.id));
|
|
5670
|
+
}
|
|
5671
|
+
} catch {
|
|
5672
|
+
return /* @__PURE__ */ new Set();
|
|
5673
|
+
}
|
|
5674
|
+
return enriched;
|
|
5675
|
+
}
|
|
5686
5676
|
async function enrichPhase(api, db, config2, model, idField, connectionKey, platform, ctx = {}) {
|
|
5687
5677
|
const startTime = Date.now();
|
|
5688
5678
|
const tsField = config2.timestampField ?? "_enriched_at";
|
|
@@ -5782,9 +5772,9 @@ async function enrichPhase(api, db, config2, model, idField, connectionKey, plat
|
|
|
5782
5772
|
stripExcludedFields(merged, ctx.exclude);
|
|
5783
5773
|
}
|
|
5784
5774
|
if (ctx.identityKey) {
|
|
5785
|
-
const
|
|
5786
|
-
if (
|
|
5787
|
-
merged._identity =
|
|
5775
|
+
const identity = resolveEntityIdentity(merged, ctx.identityKey);
|
|
5776
|
+
if (identity?.value) {
|
|
5777
|
+
merged._identity = identity.value;
|
|
5788
5778
|
}
|
|
5789
5779
|
}
|
|
5790
5780
|
const setClauses = [];
|
|
@@ -6063,7 +6053,7 @@ async function syncModel(api, profile, options) {
|
|
|
6063
6053
|
updateModelState(platform, model, { status: "failed", pagesProcessed, lastCursor }),
|
|
6064
6054
|
(async () => {
|
|
6065
6055
|
try {
|
|
6066
|
-
const { getBackend: getBackend2 } = await import("./runtime-
|
|
6056
|
+
const { getBackend: getBackend2 } = await import("./runtime-HDZCBTWH.js");
|
|
6067
6057
|
const backend = await getBackend2();
|
|
6068
6058
|
await Promise.race([
|
|
6069
6059
|
backend.close(),
|
|
@@ -6094,6 +6084,7 @@ async function syncModel(api, profile, options) {
|
|
|
6094
6084
|
let enrichedTotal = 0;
|
|
6095
6085
|
let enrichSkipped = 0;
|
|
6096
6086
|
let enrichRateLimited = 0;
|
|
6087
|
+
let memPreserved = 0;
|
|
6097
6088
|
try {
|
|
6098
6089
|
if (needsSqlite) db = await openDatabase(platform);
|
|
6099
6090
|
let sinceDate = null;
|
|
@@ -6263,9 +6254,9 @@ async function syncModel(api, profile, options) {
|
|
|
6263
6254
|
}
|
|
6264
6255
|
if (profile.identityKey) {
|
|
6265
6256
|
for (const record of records) {
|
|
6266
|
-
const
|
|
6267
|
-
if (
|
|
6268
|
-
record._identity =
|
|
6257
|
+
const identity = resolveEntityIdentity(record, profile.identityKey);
|
|
6258
|
+
if (identity?.value) {
|
|
6259
|
+
record._identity = identity.value;
|
|
6269
6260
|
}
|
|
6270
6261
|
}
|
|
6271
6262
|
}
|
|
@@ -6298,6 +6289,14 @@ async function syncModel(api, profile, options) {
|
|
|
6298
6289
|
let pageReport = null;
|
|
6299
6290
|
if (options.toMemory !== false) {
|
|
6300
6291
|
try {
|
|
6292
|
+
const alreadyEnrichedIds = profile.enrich && db ? findEnrichedIds(
|
|
6293
|
+
db,
|
|
6294
|
+
model,
|
|
6295
|
+
profile.idField,
|
|
6296
|
+
profile.enrich,
|
|
6297
|
+
records,
|
|
6298
|
+
tableCreated
|
|
6299
|
+
) : void 0;
|
|
6301
6300
|
const report = await writePageToMemory(
|
|
6302
6301
|
profile,
|
|
6303
6302
|
records,
|
|
@@ -6306,10 +6305,12 @@ async function syncModel(api, profile, options) {
|
|
|
6306
6305
|
// --embed / --no-embed on `sync run` threads through as a
|
|
6307
6306
|
// per-run override so you can backfill embeddings on a
|
|
6308
6307
|
// first-synced-without-embed platform in one shot.
|
|
6309
|
-
embedOverride: options.embed
|
|
6308
|
+
embedOverride: options.embed,
|
|
6309
|
+
alreadyEnrichedIds
|
|
6310
6310
|
}
|
|
6311
6311
|
);
|
|
6312
6312
|
pageReport = report;
|
|
6313
|
+
memPreserved += report.preserved;
|
|
6313
6314
|
for (const key of report.sourceKeysSeen) seenSourceKeys.add(key);
|
|
6314
6315
|
if (hasHooks && !db) {
|
|
6315
6316
|
inserts = report.inserts;
|
|
@@ -6418,7 +6419,7 @@ async function syncModel(api, profile, options) {
|
|
|
6418
6419
|
db.exec(`DROP TABLE IF EXISTS _seen_ids`);
|
|
6419
6420
|
}
|
|
6420
6421
|
if (options.toMemory !== false) {
|
|
6421
|
-
const backend = await (await import("./runtime-
|
|
6422
|
+
const backend = await (await import("./runtime-HDZCBTWH.js")).getBackend();
|
|
6422
6423
|
const type = `${platform}/${model}`;
|
|
6423
6424
|
const existing = await backend.listKeysByType(type);
|
|
6424
6425
|
const sourcePrefix = `${type}:`;
|
|
@@ -6450,6 +6451,12 @@ async function syncModel(api, profile, options) {
|
|
|
6450
6451
|
process.stderr.write(`Syncing ${platform}/${model}... page ${pagesProcessed} (${totalRecords} records) done
|
|
6451
6452
|
`);
|
|
6452
6453
|
}
|
|
6454
|
+
if (!isAgentMode() && memPreserved > 0) {
|
|
6455
|
+
process.stderr.write(
|
|
6456
|
+
` Kept ${memPreserved} already-enriched record(s) in memory (the list shape would have replaced the enriched payload).
|
|
6457
|
+
`
|
|
6458
|
+
);
|
|
6459
|
+
}
|
|
6453
6460
|
let enrichResult = null;
|
|
6454
6461
|
if (profile.enrich && db && tableCreated && !options.dryRun) {
|
|
6455
6462
|
enrichResult = await enrichPhase(
|
|
@@ -6498,7 +6505,7 @@ async function syncModel(api, profile, options) {
|
|
|
6498
6505
|
let statusCounts;
|
|
6499
6506
|
if (options.toMemory !== false) {
|
|
6500
6507
|
try {
|
|
6501
|
-
const backend = await (await import("./runtime-
|
|
6508
|
+
const backend = await (await import("./runtime-HDZCBTWH.js")).getBackend();
|
|
6502
6509
|
const typeName = `${platform}/${model}`;
|
|
6503
6510
|
const [active, archived] = await Promise.all([
|
|
6504
6511
|
backend.count(typeName, { status: "active" }),
|
|
@@ -6529,7 +6536,12 @@ async function syncModel(api, profile, options) {
|
|
|
6529
6536
|
...reconcileSkipped ? { reconcileSkipped } : {},
|
|
6530
6537
|
...statusCounts ? { statusCounts } : {},
|
|
6531
6538
|
...hasHooks ? { hooksInserted, hooksUpdated } : {},
|
|
6532
|
-
|
|
6539
|
+
// `memPreserved` counts already-enriched rows this run wrote
|
|
6540
|
+
// non-authoritatively (enriched payload kept, list fields refreshed).
|
|
6541
|
+
// Reported to agents too — the human path only gets a stderr line, and
|
|
6542
|
+
// an agent seeing `recordsSynced: 200, enriched: 0` otherwise has no way
|
|
6543
|
+
// to tell "nothing needed enriching" from "enrich is broken".
|
|
6544
|
+
...profile.enrich ? { enriched: enrichedTotal, enrichSkipped, enrichRateLimited, ...memPreserved > 0 ? { memPreserved } : {} } : {}
|
|
6533
6545
|
};
|
|
6534
6546
|
} catch (err) {
|
|
6535
6547
|
const failedCount = db ? countRecords(db, model) : existingState?.totalRecords ?? 0;
|
|
@@ -6569,6 +6581,33 @@ async function syncModel(api, profile, options) {
|
|
|
6569
6581
|
|
|
6570
6582
|
// src/lib/memory/sync/test.ts
|
|
6571
6583
|
var SEARCHABLE_SAMPLE_SIZE = 5;
|
|
6584
|
+
function buildIdentityKeysPreview(samples, profile) {
|
|
6585
|
+
if (!profile.identityKey && !(profile.identityKeys && profile.identityKeys.length > 0)) return void 0;
|
|
6586
|
+
const perRecord = [];
|
|
6587
|
+
const sampleKeys = /* @__PURE__ */ new Set();
|
|
6588
|
+
const fanOutValues = /* @__PURE__ */ new Set();
|
|
6589
|
+
let fanOutCount = 0;
|
|
6590
|
+
for (const rec of samples) {
|
|
6591
|
+
const keys = collectIdentityKeys(rec, profile);
|
|
6592
|
+
perRecord.push(keys.length);
|
|
6593
|
+
for (const k of keys) {
|
|
6594
|
+
if (sampleKeys.size < 8) sampleKeys.add(k);
|
|
6595
|
+
}
|
|
6596
|
+
const identity = resolveEntityIdentity(rec, profile.identityKey);
|
|
6597
|
+
if (identity && identity.values.length > 1) {
|
|
6598
|
+
fanOutCount++;
|
|
6599
|
+
for (const v of identity.values) {
|
|
6600
|
+
if (fanOutValues.size < 4) fanOutValues.add(`${identity.prefix}:${v}`);
|
|
6601
|
+
}
|
|
6602
|
+
}
|
|
6603
|
+
}
|
|
6604
|
+
return {
|
|
6605
|
+
perRecord,
|
|
6606
|
+
sampleKeys: [...sampleKeys],
|
|
6607
|
+
...fanOutCount > 0 ? { entityFanOut: { count: fanOutCount, sampleValues: [...fanOutValues] } } : {},
|
|
6608
|
+
...profile.enrich && (profile.identityKeys?.length ?? 0) > 0 ? { resolvesAfterEnrich: true } : {}
|
|
6609
|
+
};
|
|
6610
|
+
}
|
|
6572
6611
|
function detectColumnType2(value) {
|
|
6573
6612
|
if (value === null || value === void 0) return "TEXT";
|
|
6574
6613
|
if (typeof value === "string") return "TEXT";
|
|
@@ -6775,6 +6814,7 @@ async function testSyncProfile(api, profile) {
|
|
|
6775
6814
|
}));
|
|
6776
6815
|
report.sample = first;
|
|
6777
6816
|
report.samples = records.slice(0, SEARCHABLE_SAMPLE_SIZE);
|
|
6817
|
+
report.identityKeysPreview = buildIdentityKeysPreview(report.samples, profile);
|
|
6778
6818
|
report.ok = checks.every((c) => c.ok);
|
|
6779
6819
|
return report;
|
|
6780
6820
|
}
|
|
@@ -7974,6 +8014,29 @@ async function syncTestCommand(platformModel, options = {}) {
|
|
|
7974
8014
|
console.log(pc9.dim(` ... and ${report.detectedColumns.length - 20} more`));
|
|
7975
8015
|
}
|
|
7976
8016
|
}
|
|
8017
|
+
if (report.identityKeysPreview) {
|
|
8018
|
+
const { perRecord, sampleKeys, entityFanOut, resolvesAfterEnrich } = report.identityKeysPreview;
|
|
8019
|
+
const total = perRecord.reduce((a, b) => a + b, 0);
|
|
8020
|
+
const min = perRecord.length ? Math.min(...perRecord) : 0;
|
|
8021
|
+
const max = perRecord.length ? Math.max(...perRecord) : 0;
|
|
8022
|
+
const mark = total === 0 ? pc9.yellow("~") : pc9.green("\u2713");
|
|
8023
|
+
console.log(`
|
|
8024
|
+
${pc9.bold("Merge + identity keys")} ${pc9.dim(`(cross-platform \u2014 #128)`)}`);
|
|
8025
|
+
console.log(` ${mark} ${perRecord.length} sample${perRecord.length === 1 ? "" : "s"}, ${min}\u2013${max} key${max === 1 ? "" : "s"} per record`);
|
|
8026
|
+
if (sampleKeys.length > 0) {
|
|
8027
|
+
console.log(` ${pc9.dim("e.g.")} ${sampleKeys.slice(0, 8).map((k) => pc9.cyan(k)).join(", ")}`);
|
|
8028
|
+
} else if (resolvesAfterEnrich) {
|
|
8029
|
+
console.log(` ${pc9.dim("note:")} 0 on these list-shape samples \u2014 this profile enriches, and its identityKeys paths resolve in the enrich phase.`);
|
|
8030
|
+
} else {
|
|
8031
|
+
console.log(` ${pc9.yellow("note:")} no identity keys resolved on these samples \u2014 check the identityKey/identityKeys paths.`);
|
|
8032
|
+
}
|
|
8033
|
+
if (entityFanOut) {
|
|
8034
|
+
console.log(` ${pc9.yellow("warn:")} identityKey resolved to MULTIPLE values on ${entityFanOut.count} of ${perRecord.length} samples \u2014 those records get NO merge key.`);
|
|
8035
|
+
console.log(` ${pc9.dim("saw:")} ${entityFanOut.sampleValues.map((v) => pc9.cyan(v)).join(", ")}`);
|
|
8036
|
+
console.log(` ${pc9.dim('A singular identityKey means "this record IS this entity", so a fan-out has no safe answer.')}`);
|
|
8037
|
+
console.log(` ${pc9.dim("Point it at a single-valued path, or move it to identityKeys[] for participant associations.")}`);
|
|
8038
|
+
}
|
|
8039
|
+
}
|
|
7977
8040
|
if (searchablePreview) {
|
|
7978
8041
|
console.log(`
|
|
7979
8042
|
${pc9.bold("Searchable preview")} ${pc9.dim(`(${searchablePreview.mode}, ${searchablePreview.sampledRecords} sample${searchablePreview.sampledRecords === 1 ? "" : "s"})`)}`);
|
|
@@ -8131,7 +8194,7 @@ ${result.total} results`);
|
|
|
8131
8194
|
}
|
|
8132
8195
|
}
|
|
8133
8196
|
async function syncSqlCommand(platformModel, sql) {
|
|
8134
|
-
const { syncSqlCommand: runSyncSql } = await import("./sql-
|
|
8197
|
+
const { syncSqlCommand: runSyncSql } = await import("./sql-AZDCMY7G.js");
|
|
8135
8198
|
await runSyncSql(platformModel, sql);
|
|
8136
8199
|
}
|
|
8137
8200
|
async function syncDeleteCommand(platformModel, options) {
|
|
@@ -8209,7 +8272,7 @@ async function syncDeleteCommand(platformModel, options) {
|
|
|
8209
8272
|
async function maybeAutoMigrateLegacy(platform, models) {
|
|
8210
8273
|
const dbSize = getDatabaseSize(platform);
|
|
8211
8274
|
if (!dbSize || dbSize === "0 B") return;
|
|
8212
|
-
const { getBackend: getBackend2 } = await import("./runtime-
|
|
8275
|
+
const { getBackend: getBackend2 } = await import("./runtime-HDZCBTWH.js");
|
|
8213
8276
|
const backend = await getBackend2();
|
|
8214
8277
|
let memoryHasData = false;
|
|
8215
8278
|
for (const model of models) {
|
|
@@ -8225,7 +8288,7 @@ async function maybeAutoMigrateLegacy(platform, models) {
|
|
|
8225
8288
|
` detected legacy .one/sync/data/${platform}.db (${dbSize}) \u2014 auto-migrating into memory before sync.
|
|
8226
8289
|
`
|
|
8227
8290
|
);
|
|
8228
|
-
const { memMigrateCommand: memMigrateCommand3 } = await import("./migrate-
|
|
8291
|
+
const { memMigrateCommand: memMigrateCommand3 } = await import("./migrate-LXDIZMXH.js");
|
|
8229
8292
|
await memMigrateCommand3({ platform, yes: true });
|
|
8230
8293
|
return;
|
|
8231
8294
|
}
|
|
@@ -8234,7 +8297,7 @@ async function maybeAutoMigrateLegacy(platform, models) {
|
|
|
8234
8297
|
initialValue: true
|
|
8235
8298
|
});
|
|
8236
8299
|
if (p7.isCancel(shouldMigrate) || !shouldMigrate) return;
|
|
8237
|
-
const { memMigrateCommand: memMigrateCommand2 } = await import("./migrate-
|
|
8300
|
+
const { memMigrateCommand: memMigrateCommand2 } = await import("./migrate-LXDIZMXH.js");
|
|
8238
8301
|
await memMigrateCommand2({ platform, yes: true });
|
|
8239
8302
|
}
|
|
8240
8303
|
async function syncSuggestSearchableCommand(platformModel, options = {}) {
|
|
@@ -8295,7 +8358,7 @@ async function syncSuggestSearchableCommand(platformModel, options = {}) {
|
|
|
8295
8358
|
async function syncListCommand(platform) {
|
|
8296
8359
|
const profiles = listProfiles(platform);
|
|
8297
8360
|
const state = await readSyncState();
|
|
8298
|
-
const { getBackend: getBackend2 } = await import("./runtime-
|
|
8361
|
+
const { getBackend: getBackend2 } = await import("./runtime-HDZCBTWH.js");
|
|
8299
8362
|
const backend = await getBackend2();
|
|
8300
8363
|
const syncs = await Promise.all(profiles.map(async (p10) => {
|
|
8301
8364
|
const modelState = state[p10.platform]?.[p10.model];
|
|
@@ -8570,7 +8633,7 @@ function registerSyncSubcommands(sync) {
|
|
|
8570
8633
|
await syncSqlCommand(platformModel, sql);
|
|
8571
8634
|
});
|
|
8572
8635
|
sync.command("schema <platform/model>").description("Inspect the JSON structure of synced records (field paths, types, examples) \u2014 useful before writing `sync sql` queries").action(async (platformModel) => {
|
|
8573
|
-
const { syncSchemaCommand } = await import("./schema-
|
|
8636
|
+
const { syncSchemaCommand } = await import("./schema-SYMG6SRC.js");
|
|
8574
8637
|
await syncSchemaCommand(platformModel);
|
|
8575
8638
|
});
|
|
8576
8639
|
sync.command("delete <platform/model>").description('Delete records from local sync data (e.g. one sync delete notion/pages --id "abc-123")').option("--id <value>", "Delete record by ID").option("--where <conditions>", 'Delete records matching conditions (e.g. "status=archived")').option("--where-sql <predicate>", `Delete using a raw SQL WHERE clause (e.g. "json_extract(data, '$.type') = 'promotion'")`).option("--yes", "Skip confirmation prompt").action(async (platformModel, options) => {
|
|
@@ -8992,6 +9055,7 @@ function parseValue(raw) {
|
|
|
8992
9055
|
}
|
|
8993
9056
|
|
|
8994
9057
|
// src/commands/mem/records.ts
|
|
9058
|
+
import pc10 from "picocolors";
|
|
8995
9059
|
async function memAddCommand(type, dataRaw, flags) {
|
|
8996
9060
|
requireMemoryInit();
|
|
8997
9061
|
const data = parseJsonArg(dataRaw, "data");
|
|
@@ -9016,8 +9080,12 @@ async function memGetCommand(id, flags) {
|
|
|
9016
9080
|
async function memUpdateCommand(id, patchRaw) {
|
|
9017
9081
|
requireMemoryInit();
|
|
9018
9082
|
const patch = parseJsonArg(patchRaw, "patch");
|
|
9019
|
-
|
|
9020
|
-
|
|
9083
|
+
const isPatchObject = !!patch && typeof patch === "object" && !Array.isArray(patch);
|
|
9084
|
+
if (isPatchObject && "keys" in patch) {
|
|
9085
|
+
error("`keys` is not a data field. Use `one mem key <id> --set <csv>` (or --add/--remove) to change a record's merge keys.");
|
|
9086
|
+
}
|
|
9087
|
+
if (isPatchObject && ("identity_keys" in patch || "identityKeys" in patch)) {
|
|
9088
|
+
error("`identity_keys` is not a data field. Participant associations are derived on sync from the profile's `identityKeys` \u2014 edit the sync profile and re-run `one sync run`, then query with `one mem find-by-key <key>`.");
|
|
9021
9089
|
}
|
|
9022
9090
|
const updated = await updateRecord(id, { data: patch });
|
|
9023
9091
|
if (!updated) error(`Record ${id} not found`);
|
|
@@ -9046,7 +9114,7 @@ async function memKeyCommand(id, flags) {
|
|
|
9046
9114
|
if (outcome.status === "not_found") error(`Record ${id} not found`);
|
|
9047
9115
|
if (outcome.status === "conflict") {
|
|
9048
9116
|
error(
|
|
9049
|
-
`Key "${outcome.key}" is already owned by active record ${outcome.recordId} (type ${outcome.recordType}).
|
|
9117
|
+
`Key "${outcome.key}" is already owned by active record ${outcome.recordId} (type ${outcome.recordType}). Merge keys must be unique across active records \u2014 archive or re-key that record first.`
|
|
9050
9118
|
);
|
|
9051
9119
|
}
|
|
9052
9120
|
printRecord(outcome.record);
|
|
@@ -9180,9 +9248,104 @@ async function memFindBySourceCommand(sourceKey) {
|
|
|
9180
9248
|
if (!record) error(`No record owns source "${sourceKey}"`);
|
|
9181
9249
|
printRecord(record);
|
|
9182
9250
|
}
|
|
9251
|
+
function summarizeRecord(r) {
|
|
9252
|
+
const d = r.data ?? {};
|
|
9253
|
+
for (const field of ["title", "subject", "name", "full_name", "display_name", "summary", "headline", "email"]) {
|
|
9254
|
+
const v = d[field];
|
|
9255
|
+
if (typeof v === "string" && v.trim()) return v.trim().slice(0, 80);
|
|
9256
|
+
}
|
|
9257
|
+
return pc10.dim("(untitled)");
|
|
9258
|
+
}
|
|
9259
|
+
function relativeTime(iso) {
|
|
9260
|
+
if (!iso) return "";
|
|
9261
|
+
const then = new Date(iso).getTime();
|
|
9262
|
+
if (Number.isNaN(then)) return "";
|
|
9263
|
+
const s = Math.max(0, Math.floor((Date.now() - then) / 1e3));
|
|
9264
|
+
if (s < 60) return `${s}s ago`;
|
|
9265
|
+
const m = Math.floor(s / 60);
|
|
9266
|
+
if (m < 60) return `${m}m ago`;
|
|
9267
|
+
const h = Math.floor(m / 60);
|
|
9268
|
+
if (h < 24) return `${h}h ago`;
|
|
9269
|
+
const d = Math.floor(h / 24);
|
|
9270
|
+
if (d < 30) return `${d}d ago`;
|
|
9271
|
+
const mo = Math.floor(d / 30);
|
|
9272
|
+
if (mo < 12) return `${mo}mo ago`;
|
|
9273
|
+
return `${Math.floor(mo / 12)}y ago`;
|
|
9274
|
+
}
|
|
9275
|
+
var FIND_BY_KEY_FETCH_CAP = 2e3;
|
|
9276
|
+
function normalizeQueryKey(key) {
|
|
9277
|
+
return key.trim().toLowerCase();
|
|
9278
|
+
}
|
|
9279
|
+
async function memFindByKeyCommand(key, secondKey, flags) {
|
|
9280
|
+
requireMemoryInit();
|
|
9281
|
+
const backend = await getBackend();
|
|
9282
|
+
const rawKeys = secondKey ? [key, secondKey] : [key];
|
|
9283
|
+
const keys = rawKeys.map(normalizeQueryKey);
|
|
9284
|
+
const perType = parsePositiveInt(flags.limit, 10, "--limit");
|
|
9285
|
+
const fetchPage = async (ks) => {
|
|
9286
|
+
const rows = await backend.findByKeys(ks, { type: flags.type, limit: FIND_BY_KEY_FETCH_CAP + 1 });
|
|
9287
|
+
return { records: rows.slice(0, FIND_BY_KEY_FETCH_CAP), truncated: rows.length > FIND_BY_KEY_FETCH_CAP };
|
|
9288
|
+
};
|
|
9289
|
+
let page = await fetchPage(keys);
|
|
9290
|
+
let matchedKeys = keys;
|
|
9291
|
+
if (page.records.length === 0 && keys.some((k, i) => k !== rawKeys[i])) {
|
|
9292
|
+
const verbatim = await fetchPage(rawKeys);
|
|
9293
|
+
if (verbatim.records.length > 0) {
|
|
9294
|
+
page = verbatim;
|
|
9295
|
+
matchedKeys = rawKeys;
|
|
9296
|
+
}
|
|
9297
|
+
}
|
|
9298
|
+
const { records, truncated } = page;
|
|
9299
|
+
const byType = /* @__PURE__ */ new Map();
|
|
9300
|
+
for (const r of records) {
|
|
9301
|
+
const list = byType.get(r.type) ?? [];
|
|
9302
|
+
list.push(r);
|
|
9303
|
+
byType.set(r.type, list);
|
|
9304
|
+
}
|
|
9305
|
+
if (isAgentMode()) {
|
|
9306
|
+
const grouped = {};
|
|
9307
|
+
for (const [type, list] of byType) {
|
|
9308
|
+
grouped[type] = { count: list.length, items: list.slice(0, perType) };
|
|
9309
|
+
}
|
|
9310
|
+
json({
|
|
9311
|
+
keys: matchedKeys,
|
|
9312
|
+
total: records.length,
|
|
9313
|
+
truncated,
|
|
9314
|
+
fetchCap: FIND_BY_KEY_FETCH_CAP,
|
|
9315
|
+
perTypeLimit: perType,
|
|
9316
|
+
byType: grouped
|
|
9317
|
+
});
|
|
9318
|
+
return;
|
|
9319
|
+
}
|
|
9320
|
+
const label = matchedKeys.map((k) => pc10.cyan(k)).join(pc10.dim(" + "));
|
|
9321
|
+
if (records.length === 0) {
|
|
9322
|
+
console.log(`
|
|
9323
|
+
No records linked to ${label}.
|
|
9324
|
+
`);
|
|
9325
|
+
return;
|
|
9326
|
+
}
|
|
9327
|
+
console.log();
|
|
9328
|
+
console.log(` ${label} ${pc10.dim("\u2014")} ${records.length}${truncated ? "+" : ""} record${records.length === 1 ? "" : "s"} across ${byType.size} type${byType.size === 1 ? "" : "s"}`);
|
|
9329
|
+
console.log();
|
|
9330
|
+
const typeWidth = Math.min(30, Math.max(...[...byType.keys()].map((t) => t.length)));
|
|
9331
|
+
for (const [type, list] of byType) {
|
|
9332
|
+
console.log(` ${pc10.bold(type.padEnd(typeWidth))} ${pc10.dim(`${list.length} record${list.length === 1 ? "" : "s"}`)}`);
|
|
9333
|
+
for (const r of list.slice(0, perType)) {
|
|
9334
|
+
console.log(` ${pc10.dim("\xB7")} ${summarizeRecord(r)} ${pc10.dim(relativeTime(r.updated_at))} ${pc10.dim(r.id)}`);
|
|
9335
|
+
}
|
|
9336
|
+
if (list.length > perType) {
|
|
9337
|
+
console.log(` ${pc10.dim(`\u2026 and ${list.length - perType} more (raise --limit)`)}`);
|
|
9338
|
+
}
|
|
9339
|
+
}
|
|
9340
|
+
if (truncated) {
|
|
9341
|
+
console.log();
|
|
9342
|
+
console.log(` ${pc10.yellow("\u26A0")} Stopped at ${FIND_BY_KEY_FETCH_CAP} matches \u2014 there are more, and types sorting after the last one shown are missing entirely. Re-run with --type <type> to see them.`);
|
|
9343
|
+
}
|
|
9344
|
+
console.log();
|
|
9345
|
+
}
|
|
9183
9346
|
|
|
9184
9347
|
// src/commands/mem/doctor.ts
|
|
9185
|
-
import
|
|
9348
|
+
import pc11 from "picocolors";
|
|
9186
9349
|
async function memDoctorCommand() {
|
|
9187
9350
|
const checks = [];
|
|
9188
9351
|
const cfg = getMemoryConfig();
|
|
@@ -9289,19 +9452,19 @@ function emit(checks, capInfo) {
|
|
|
9289
9452
|
return;
|
|
9290
9453
|
}
|
|
9291
9454
|
for (const c of checks) {
|
|
9292
|
-
const mark = c.ok ?
|
|
9293
|
-
const detail = c.detail ?
|
|
9455
|
+
const mark = c.ok ? pc11.green("\u2713") : pc11.red("\u2717");
|
|
9456
|
+
const detail = c.detail ? pc11.dim(` \u2014 ${c.detail}`) : "";
|
|
9294
9457
|
console.log(` ${mark} ${c.name}${detail}`);
|
|
9295
9458
|
}
|
|
9296
9459
|
if (!allOk) {
|
|
9297
|
-
console.log("\n" +
|
|
9460
|
+
console.log("\n" + pc11.yellow("Memory is not fully healthy."));
|
|
9298
9461
|
process.exitCode = 1;
|
|
9299
9462
|
} else {
|
|
9300
|
-
console.log("\n" +
|
|
9463
|
+
console.log("\n" + pc11.green("Memory is healthy."));
|
|
9301
9464
|
}
|
|
9302
9465
|
const line = semanticSearchUpgradeLine({ vectorSearchAvailable: capInfo.vectorSearchAvailable });
|
|
9303
9466
|
if (line) console.log(`
|
|
9304
|
-
${
|
|
9467
|
+
${pc11.dim(line)}`);
|
|
9305
9468
|
}
|
|
9306
9469
|
|
|
9307
9470
|
// src/commands/mem/export.ts
|
|
@@ -9377,6 +9540,14 @@ async function memImportCommand(file) {
|
|
|
9377
9540
|
data: record.data,
|
|
9378
9541
|
tags: record.tags,
|
|
9379
9542
|
keys,
|
|
9543
|
+
// Carry `identity_keys` through (#128). Export writes the whole row, so
|
|
9544
|
+
// the JSONL has them; omitting them here only looked harmless because
|
|
9545
|
+
// re-importing over a store that still holds the record takes the union
|
|
9546
|
+
// branch in mem_upsert_by_keys and preserves what's already there. On the
|
|
9547
|
+
// INSERT path — restore into a wiped store, or a backend swap
|
|
9548
|
+
// (pglite → postgres), which is exactly what export|import is for —
|
|
9549
|
+
// dropping them here loses every participant association permanently.
|
|
9550
|
+
identity_keys: record.identity_keys,
|
|
9380
9551
|
sources: record.sources,
|
|
9381
9552
|
searchable_text: record.searchable_text ?? null,
|
|
9382
9553
|
content_hash: record.content_hash ?? null,
|
|
@@ -9563,10 +9734,10 @@ function registerMemoryCommands(program2) {
|
|
|
9563
9734
|
mem.command("vacuum").description("Run backend maintenance (VACUUM ANALYZE on tables)").action(memVacuumCommand);
|
|
9564
9735
|
mem.command("reindex").description("Backfill embeddings (default) or searchable_text (--searchable)").option("--type <type>", "Restrict to one record type, e.g. attio/attioPeople").option("--model <name>", "Override the embedding model").option("--force", "Re-embed even rows that already have a matching embedding", false).option("--batch <n>", "OpenAI calls per batch (default 50)", "50").option("--limit <n>", "Safety cap on total records to scan (default 100000)").option("--searchable", "Regenerate searchable_text (NULL or stale vs. data) instead of embeddings", false).option("--all", "With --searchable: also rewrite rows whose text is stale, not just NULL", false).action(memReindexCommand);
|
|
9565
9736
|
mem.command("sql <query>").description("Run a read-only SELECT / WITH / EXPLAIN against the memory store").action(memSqlCommand);
|
|
9566
|
-
mem.command("add <type> <data>").description("Add a new memory record (data is JSON)").option("--tags <csv>", "Comma-separated tags").option("--keys <csv>", "Comma-separated keys (prefixed, e.g. email:x@y.com)").option("--weight <n>", "Importance 1-10 (default 5)").option("--embed", "Force embedding for this record").option("--no-embed", "Skip embedding for this record").action(memAddCommand);
|
|
9737
|
+
mem.command("add <type> <data>").description("Add a new memory record (data is JSON)").option("--tags <csv>", "Comma-separated tags").option("--keys <csv>", "Comma-separated merge keys (`keys[]`, prefixed, e.g. email:x@y.com \u2014 unique across active records)").option("--weight <n>", "Importance 1-10 (default 5)").option("--embed", "Force embedding for this record").option("--no-embed", "Skip embedding for this record").action(memAddCommand);
|
|
9567
9738
|
mem.command("get <id>").description("Get a record by id").option("--links", "Include outgoing and incoming links", false).action(memGetCommand);
|
|
9568
9739
|
mem.command("update <id> <patch>").description("Update a record (patch is JSON merged into data; regenerates searchable_text)").action(memUpdateCommand);
|
|
9569
|
-
mem.command("key <id>").description("Manage a record's
|
|
9740
|
+
mem.command("key <id>").description("Manage a record's merge keys (`keys[]` \u2014 unique across active records; these drive upsert-merge). Not participant associations \u2014 those live in `identity_keys[]`, written by a sync profile's `identityKeys`.").option("--add <csv>", "Merge keys to add (comma-separated, e.g. email:x@y.com)").option("--remove <csv>", "Merge keys to remove (comma-separated)").option("--set <csv>", "Replace all merge keys with this comma-separated list").action((id, flags) => memKeyCommand(id, flags));
|
|
9570
9741
|
mem.command("archive <id>").description("Archive a record").option("--reason <text>", "Why it was archived (user_archived | deleted_upstream | superseded | \u2026)").action(memArchiveCommand);
|
|
9571
9742
|
mem.command("weight <id> <n>").description("Set record relevance weight (1-10)").action(memWeightCommand);
|
|
9572
9743
|
mem.command("flush <id>").description("Reset access count for a record").action(memFlushCommand);
|
|
@@ -9578,6 +9749,7 @@ function registerMemoryCommands(program2) {
|
|
|
9578
9749
|
mem.command("linked <id>").description("List linked records").option("--relation <name>", "Filter by relation").option("--direction <dir>", "outgoing | incoming | both", "outgoing").action(memLinkedCommand);
|
|
9579
9750
|
mem.command("sources <id>").description("List source entries on a record").action(memSourcesCommand);
|
|
9580
9751
|
mem.command("find-by-source <sourceKey>").description('Look up the record owning "<system>/<model>:<external_id>"').action(memFindBySourceCommand);
|
|
9752
|
+
mem.command("find-by-key <key> [secondKey]").description("Records carrying a key (e.g. email:jane@acme.com) in either `keys[]` or `identity_keys[]`, grouped by type. Two keys = intersection. Keys are matched case-insensitively.").option("--type <type>", "Only this record type (e.g. gmail/gmailThreads) \u2014 also the way past the 2000-match fetch cap").option("--limit <n>", "Max records shown per type (default 10)").action((key, secondKey, flags) => memFindByKeyCommand(key, secondKey, flags));
|
|
9581
9753
|
registerSyncCommands(mem, {
|
|
9582
9754
|
description: "Sync platform data into memory (alias of `one sync`)"
|
|
9583
9755
|
});
|
|
@@ -9587,7 +9759,7 @@ function registerMemoryCommands(program2) {
|
|
|
9587
9759
|
}
|
|
9588
9760
|
|
|
9589
9761
|
// src/commands/cache.ts
|
|
9590
|
-
import
|
|
9762
|
+
import pc12 from "picocolors";
|
|
9591
9763
|
async function cacheClearCommand(actionId) {
|
|
9592
9764
|
if (actionId) {
|
|
9593
9765
|
const deleted = clearEntry(actionId);
|
|
@@ -9596,9 +9768,9 @@ async function cacheClearCommand(actionId) {
|
|
|
9596
9768
|
return;
|
|
9597
9769
|
}
|
|
9598
9770
|
if (deleted) {
|
|
9599
|
-
console.log(`Cleared cache for ${
|
|
9771
|
+
console.log(`Cleared cache for ${pc12.cyan(actionId)}`);
|
|
9600
9772
|
} else {
|
|
9601
|
-
console.log(`No cache entry found for ${
|
|
9773
|
+
console.log(`No cache entry found for ${pc12.dim(actionId)}`);
|
|
9602
9774
|
}
|
|
9603
9775
|
} else {
|
|
9604
9776
|
const count = clearAll();
|
|
@@ -9635,7 +9807,7 @@ async function cacheListCommand(options) {
|
|
|
9635
9807
|
type: e.type,
|
|
9636
9808
|
key: e.entry.key,
|
|
9637
9809
|
age: formatAge(getAge(e.entry)),
|
|
9638
|
-
status: isFresh(e.entry) ?
|
|
9810
|
+
status: isFresh(e.entry) ? pc12.green("fresh") : pc12.yellow("expired")
|
|
9639
9811
|
}));
|
|
9640
9812
|
printTable(
|
|
9641
9813
|
[
|
|
@@ -9726,13 +9898,13 @@ async function cacheUpdateAllCommand() {
|
|
|
9726
9898
|
if (errors.length > 0) {
|
|
9727
9899
|
console.log();
|
|
9728
9900
|
for (const e of errors) {
|
|
9729
|
-
console.log(` ${
|
|
9901
|
+
console.log(` ${pc12.red("\u2717")} ${e.key}: ${pc12.dim(e.error)}`);
|
|
9730
9902
|
}
|
|
9731
9903
|
}
|
|
9732
9904
|
}
|
|
9733
9905
|
|
|
9734
9906
|
// src/commands/guide.ts
|
|
9735
|
-
import
|
|
9907
|
+
import pc13 from "picocolors";
|
|
9736
9908
|
|
|
9737
9909
|
// src/lib/guide-content.ts
|
|
9738
9910
|
var GUIDE_OVERVIEW = `# One CLI \u2014 Agent Guide
|
|
@@ -9777,7 +9949,7 @@ Search for actions, read their docs, and execute them. This is the core workflow
|
|
|
9777
9949
|
|
|
9778
9950
|
**Quick start:**
|
|
9779
9951
|
\`\`\`bash
|
|
9780
|
-
one --agent connection list # See connected platforms
|
|
9952
|
+
one --agent connection list # See connected platforms + your access on each
|
|
9781
9953
|
one --agent connection delete <connection-key> # Remove a connection
|
|
9782
9954
|
one --agent actions search <platform> "<query>" -t execute # Find an action
|
|
9783
9955
|
one --agent actions knowledge <platform> <actionId> # Read docs (REQUIRED)
|
|
@@ -9866,6 +10038,7 @@ Request specific sections:
|
|
|
9866
10038
|
- Always use the **exact action ID** from search results \u2014 don't guess
|
|
9867
10039
|
- Always read **knowledge** before executing any action
|
|
9868
10040
|
- Connection keys come from \`one connection list\` \u2014 don't hardcode them
|
|
10041
|
+
- \`connection list\` also reports an \`access\` field per connection (\`full\` / \`methods\` / \`actions\`) \u2014 read it before planning so you don't propose an action the access config will reject
|
|
9869
10042
|
- 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\`
|
|
9870
10043
|
`;
|
|
9871
10044
|
var GUIDE_ACTIONS = `# One Actions \u2014 Reference
|
|
@@ -9880,7 +10053,17 @@ Always follow this sequence. Never skip the knowledge step.
|
|
|
9880
10053
|
one --agent connection list
|
|
9881
10054
|
\`\`\`
|
|
9882
10055
|
|
|
9883
|
-
Returns platforms, status, connection keys, and
|
|
10056
|
+
Returns platforms, status, connection keys, tags, and an \`access\` field per connection describing what the current access config lets you run there:
|
|
10057
|
+
|
|
10058
|
+
| \`access\` | Meaning |
|
|
10059
|
+
|----------|---------|
|
|
10060
|
+
| \`{"policy": "full"}\` | Every action on the connection |
|
|
10061
|
+
| \`{"policy": "methods", "methods": ["GET"]}\` | Only actions with these HTTP methods will execute |
|
|
10062
|
+
| \`{"policy": "actions", "actions": [{"actionId", "title", "method"}]}\` | Only these exact actions \u2014 use them directly, skip \`actions search\` |
|
|
10063
|
+
|
|
10064
|
+
Also present when relevant: \`knowledgeOnly: true\` (execution disabled \u2014 read knowledge and write code instead), \`unresolvedActionIds\` (allowlisted ids that could not be looked up), and \`accessHint\` (a one-line summary of the restriction).
|
|
10065
|
+
|
|
10066
|
+
Read \`access\` before planning \u2014 it prevents proposing an action the config will reject. Change it with \`one config\`.
|
|
9884
10067
|
|
|
9885
10068
|
### 1b. Delete a Connection
|
|
9886
10069
|
|
|
@@ -10212,15 +10395,18 @@ one --agent mem get <id> --links
|
|
|
10212
10395
|
# Update (shallow merge into data; regenerates searchable_text from the merged data)
|
|
10213
10396
|
one --agent mem update <id> '{"status":"done"}'
|
|
10214
10397
|
|
|
10215
|
-
# Manage
|
|
10216
|
-
# Unique across ACTIVE records \u2014 a clear error names the record that
|
|
10398
|
+
# Manage MERGE KEYS after creation \u2014 the \`keys[]\` column (a first-class column,
|
|
10399
|
+
# NOT data). Unique across ACTIVE records \u2014 a clear error names the record that
|
|
10400
|
+
# owns a taken key. This is the column that collapses two records into one.
|
|
10217
10401
|
one --agent mem key <id> --add email:new@x.com
|
|
10218
10402
|
one --agent mem key <id> --remove email:old@x.com
|
|
10219
|
-
one --agent mem key <id> --set 'email:a@x.com,phone:+1555' # replace
|
|
10403
|
+
one --agent mem key <id> --set 'email:a@x.com,phone:+1555' # replace ALL merge keys
|
|
10220
10404
|
# NOTE: passing keys to 'mem update' is rejected \u2014 keys are not a data field.
|
|
10405
|
+
# NOTE: 'mem key' WRITES keys[]. To READ across records, use 'mem find-by-key'
|
|
10406
|
+
# (see Identity keys below) \u2014 different command, different column coverage.
|
|
10221
10407
|
|
|
10222
|
-
# Archive / unarchive. Archiving FREES a record's keys: an archived record
|
|
10223
|
-
# longer blocks a new active record from reusing the same key (uniqueness is
|
|
10408
|
+
# Archive / unarchive. Archiving FREES a record's merge keys: an archived record
|
|
10409
|
+
# no longer blocks a new active record from reusing the same key (uniqueness is
|
|
10224
10410
|
# scoped to active records).
|
|
10225
10411
|
one --agent mem archive <id> --reason superseded
|
|
10226
10412
|
|
|
@@ -10262,6 +10448,55 @@ one --agent mem sources <id> # list source entries
|
|
|
10262
10448
|
one --agent mem find-by-source attio/attioPeople:abc-123
|
|
10263
10449
|
\`\`\`
|
|
10264
10450
|
|
|
10451
|
+
\`find-by-source\` answers "which ONE record owns this external id" \u2014 a single record, preferring the active owner. For "everything about this person", use \`find-by-key\` below.
|
|
10452
|
+
|
|
10453
|
+
## Identity keys (cross-platform join)
|
|
10454
|
+
|
|
10455
|
+
Two different columns carry prefixed keys like \`email:jane@acme.com\`, and the difference is the whole point:
|
|
10456
|
+
|
|
10457
|
+
- **\`keys[]\` \u2014 merge keys.** "This record IS this entity." Unique across active records, and \`sync\` merges any incoming record that overlaps an existing one. Written by \`mem add --keys\`, \`mem key\`, and a profile's singular \`identityKey\`.
|
|
10458
|
+
- **\`identity_keys[]\` \u2014 association keys (#128).** "This record INVOLVES these people." Deliberately exempt from both the uniqueness constraint and the overlap-merge, so a Gmail thread with 12 participants stays one thread instead of collapsing into a contact. Written by a profile's plural \`identityKeys\` (see \`one guide sync\`).
|
|
10459
|
+
|
|
10460
|
+
\`mem find-by-key\` queries BOTH columns at once, so one call surfaces the CRM record, the email threads, the calendar events, and the meeting notes for the same person:
|
|
10461
|
+
|
|
10462
|
+
\`\`\`bash
|
|
10463
|
+
one --agent mem find-by-key email:jane@acme.com # every record carrying the key
|
|
10464
|
+
one --agent mem find-by-key email:jane@acme.com --type gmail/gmailThreads # one record type
|
|
10465
|
+
one --agent mem find-by-key email:a@x.com email:b@y.com # intersection \u2014 records with BOTH
|
|
10466
|
+
one --agent mem find-by-key email:jane@acme.com --limit 25 # show more per type (default 10)
|
|
10467
|
+
\`\`\`
|
|
10468
|
+
|
|
10469
|
+
Any prefix works (\`email:\`, \`domain:\`, \`phone:\`, even a raw source key) \u2014 matching is plain array containment over the two columns combined, so a two-key query still matches when one key sits in \`keys[]\` and the other in \`identity_keys[]\`. Only ACTIVE records are returned. Lookup keys are lowercased/trimmed to match how sync writes them, with a verbatim retry for hand-written mixed-case keys, so \`email:Jane@Acme.com\` and \`email:jane@acme.com\` find the same records.
|
|
10470
|
+
|
|
10471
|
+
Agent output is grouped by record type:
|
|
10472
|
+
|
|
10473
|
+
\`\`\`json
|
|
10474
|
+
{
|
|
10475
|
+
"keys": ["email:jane@acme.com"],
|
|
10476
|
+
"total": 13,
|
|
10477
|
+
"truncated": false,
|
|
10478
|
+
"fetchCap": 2000,
|
|
10479
|
+
"perTypeLimit": 10,
|
|
10480
|
+
"byType": {
|
|
10481
|
+
"attio/attioPeople": { "count": 1, "items": [ { "id": "...", "type": "...", "data": {}, "keys": ["attio/attioPeople:J1", "email:jane@acme.com"] } ] },
|
|
10482
|
+
"gmail/gmailThreads": { "count": 12, "items": [] }
|
|
10483
|
+
}
|
|
10484
|
+
}
|
|
10485
|
+
\`\`\`
|
|
10486
|
+
|
|
10487
|
+
\`items\` are whole mem records. \`keys\` and \`identity_keys\` are **omitted, not \`[]\`**, when the record has none \u2014 the contact above matched on \`keys[]\` and carries no \`identity_keys\` field at all. Read them as \`(item.identity_keys ?? [])\`.
|
|
10488
|
+
|
|
10489
|
+
There are TWO independent truncations, and conflating them gives wrong answers:
|
|
10490
|
+
|
|
10491
|
+
- **\`--limit\` (default 10, echoed as \`perTypeLimit\`)** is display only. It slices each \`items\` array; \`total\` and \`count\` stay full match counts. \`count > items.length\` just means "raise \`--limit\` to list the rest".
|
|
10492
|
+
- **\`truncated: true\`** means the query hit the \`fetchCap\` (2000 matches) before it ran out of records. Now \`total\` and every \`count\` are floors, and because rows arrive ordered by type, any type sorting after the cut is absent from \`byType\` altogether. One high-volume type \u2014 your own address on every synced Gmail thread, say \u2014 can consume the whole budget. Re-run with \`--type <type>\` to get an accurate answer for the type you care about; the interactive (non-\`--agent\`) output prints a \`\u26A0\` and a \`2000+\` style count in the same situation.
|
|
10493
|
+
|
|
10494
|
+
\`items\` entries are whole mem records, same fields as \`mem get\`, so no follow-up \`mem get\` is needed.
|
|
10495
|
+
|
|
10496
|
+
\`keys\` echoes the key form that actually matched, which is the normalized one unless the verbatim retry won.
|
|
10497
|
+
|
|
10498
|
+
Do not confuse \`mem key\` with \`mem find-by-key\`: \`mem key\` WRITES \`keys[]\` on a single record (\`--set\` replaces the array outright and can trigger a merge), while \`find-by-key\` is a read-only query across every record.
|
|
10499
|
+
|
|
10265
10500
|
## Sync into memory
|
|
10266
10501
|
|
|
10267
10502
|
\`one mem sync\` is a full alias of \`one sync\` \u2014 same handlers, same options. Use either.
|
|
@@ -10552,9 +10787,30 @@ The transform can be any command: \`jq\`, \`python3\`, a bash script, or \`one f
|
|
|
10552
10787
|
|
|
10553
10788
|
Transform, exclude, identityKey, and hooks all fire in **both** phases. In Phase 1 they run on the raw list page; in Phase 2 they run again on the merged (list + enriched) record so that transforms can extract columns from fields that only appear after enrichment. Phase 2 fires \`onUpdate\`/\`onChange\` for every row it writes \u2014 \`onInsert\` is Phase-1-only because the row already exists in SQL by the time enrichment runs.
|
|
10554
10789
|
|
|
10790
|
+
**Exception \u2014 plural \`identityKeys\` on an enriching profile.** When a profile declares \`enrich\`, Phase 1 deliberately writes *no opinion* about \`identity_keys[]\` (it leaves whatever is stored untouched) and only Phase 2 sets them. Phase 1 sees the list shape, where participant paths like \`messages[].payload.headers[name=From].value\` don't exist yet \u2014 if it wrote its empty result, every re-sync would erase the participants Phase 2 had resolved. Two consequences worth knowing: a row whose enrichment is skipped or rate-limited keeps \`identity_keys\` \`NULL\` until it enriches, and a participant removed upstream is only cleared once that row re-enriches.
|
|
10791
|
+
|
|
10792
|
+
### Phase 1 never degrades an already-enriched record
|
|
10793
|
+
|
|
10794
|
+
Phase 2 only visits rows \`WHERE _enriched_at IS NULL\`, so it never revisits a row it has already enriched. That makes Phase 1's write dangerous on every run after the first: it holds only the thin list shape, and an authoritative write would replace the enriched payload with it \u2014 permanently, since Phase 2 won't come back to repair it.
|
|
10795
|
+
|
|
10796
|
+
So for a row the mirror reports as already enriched, Phase 1 writes **non-authoritatively**:
|
|
10797
|
+
|
|
10798
|
+
| | Already-enriched row | Not yet enriched |
|
|
10799
|
+
|---|---|---|
|
|
10800
|
+
| \`data\` | merged \u2014 enrich-only fields survive, list fields still refresh | replaced |
|
|
10801
|
+
| \`searchable_text\` / \`content_hash\` | kept (thin text can't overwrite the enriched text) | recomputed |
|
|
10802
|
+
| \`identity_keys[]\` | kept | written |
|
|
10803
|
+
| \`keys[]\`, \`tags\`, \`sources.last_synced_at\`, embeddings | updated normally | updated normally |
|
|
10804
|
+
|
|
10805
|
+
The write still happens, so \`--embed\`, profile edits, and the store's un-archive self-heal all keep working on enriched rows. The one thing given up: a field that disappears from the *list* shape upstream no longer disappears from an enriched record, because a merge cannot delete. \`sync run\` reports the count as \`memPreserved\`.
|
|
10806
|
+
|
|
10807
|
+
**Known gap:** \`--full-refresh\` does **not** clear \`_enriched_at\`, so it does not re-enrich \u2014 and there is currently no way to re-enrich a record whose upstream detail content changed after its first enrichment. Delete the mirror (\`.one/sync/data/<platform>.db\`) to force a full re-enrich.
|
|
10808
|
+
|
|
10555
10809
|
## Cross-Platform Identity
|
|
10556
10810
|
|
|
10557
|
-
|
|
10811
|
+
Two ways to tag a record with a cross-platform identifier (e.g. email), depending on whether the record IS an entity or INVOLVES many:
|
|
10812
|
+
|
|
10813
|
+
**\`identityKey\` (singular)** \u2014 "this record IS this entity". The value goes into the record's \`keys[]\`, which the store uses to MERGE records for the same entity across platforms (Attio + HubSpot for the same person collapse into one):
|
|
10558
10814
|
|
|
10559
10815
|
\`\`\`json
|
|
10560
10816
|
{"platform": "hubspot", "model": "contacts", "identityKey": "properties.email"}
|
|
@@ -10562,14 +10818,34 @@ Add \`identityKey\` to a sync profile to extract a stable cross-platform identif
|
|
|
10562
10818
|
{"platform": "attio", "model": "attioPeople", "identityKey": "email_addresses[0].email_address"}
|
|
10563
10819
|
\`\`\`
|
|
10564
10820
|
|
|
10565
|
-
|
|
10821
|
+
**\`identityKeys\` (plural, #128)** \u2014 "this record INVOLVES these people". For records with N participants (Gmail thread From/To/Cc, calendar attendees, meeting invitees) where a single key can't capture everyone. These go into a separate \`identity_keys[]\` column that does NOT merge \u2014 so a thread with many participants stays its own record:
|
|
10822
|
+
|
|
10823
|
+
\`\`\`json
|
|
10824
|
+
{"platform": "google-calendar", "model": "events", "identityKeys": [
|
|
10825
|
+
{"prefix": "email", "path": "organizer.email"},
|
|
10826
|
+
{"prefix": "email", "path": "attendees[].email"}
|
|
10827
|
+
]}
|
|
10828
|
+
\`\`\`
|
|
10829
|
+
|
|
10830
|
+
Each \`path\` supports \`[]\` wildcards (one key per element) and a \`[name=From]\` equality filter (e.g. Gmail \`messages[].payload.headers[name=From].value\`). \`email\`-prefixed values are email-extracted, so display-name headers (\`"Jane <jane@acme.com>"\`) and comma-lists normalize cleanly. Values are lowercased/trimmed/deduped. \`sync test\` previews how many identity keys each record resolves.
|
|
10831
|
+
|
|
10832
|
+
Query everything sharing an identity key, grouped by type:
|
|
10566
10833
|
|
|
10567
10834
|
\`\`\`bash
|
|
10835
|
+
one --agent mem find-by-key email:jane@acme.com # all records carrying this key
|
|
10836
|
+
one --agent mem find-by-key email:jane@acme.com --type gmail/gmailThreads
|
|
10837
|
+
one --agent mem find-by-key email:jane@acme.com email:bob@acme.com # intersection (both keys)
|
|
10838
|
+
one --agent mem find-by-key email:jane@acme.com --limit 25 # show more records per type
|
|
10839
|
+
# Look up the single record owning a source key:
|
|
10568
10840
|
one --agent mem find-by-source hubspot/contacts:<id>
|
|
10569
|
-
# Or via the dotted --where path
|
|
10570
|
-
|
|
10841
|
+
# Or filter ONE model on the underlying field via the dotted --where path. Note
|
|
10842
|
+
# this is the raw data path, NOT the identity key \u2014 for the hubspot profile above
|
|
10843
|
+
# that's 'properties.email', the same path its identityKey extracts from.
|
|
10844
|
+
one --agent sync query hubspot/contacts --where 'properties.email=jane@acme.com'
|
|
10571
10845
|
\`\`\`
|
|
10572
10846
|
|
|
10847
|
+
\`find-by-key\` spans BOTH columns \u2014 entity keys in \`keys[]\` and association keys in \`identity_keys[]\`. \`--limit\` (default 10) caps only how many records are listed per type; the per-type \`count\` and overall \`total\` are still full match counts, so \`count\` exceeding the listed items is the signal to raise it. A separate \`truncated\` flag reports the very different case where the 2000-match fetch cap was hit and whole types are missing \u2014 see \`one guide memory\` for the full response shape.
|
|
10848
|
+
|
|
10573
10849
|
\`sync sql\` was retired in the unified memory cutover \u2014 a raw-SQL surface can't safely span the embedded Postgres, remote Postgres, and third-party backends without leaking specifics. Use \`mem search\` / \`sync search\` / \`sync query\` with dotted \`--where\` paths instead.
|
|
10574
10850
|
|
|
10575
10851
|
## Exclude Fields
|
|
@@ -10657,7 +10933,8 @@ Every \`sync X\` command is also exposed as \`mem sync X\` \u2014 same handlers,
|
|
|
10657
10933
|
| limitLocation | no | "query" (default) or "body" for POST endpoints |
|
|
10658
10934
|
| enrich | no | Detail endpoint config for record enrichment (actionId, pathVars, concurrency) |
|
|
10659
10935
|
| transform | no | Shell command to transform records (stdin: JSON array, stdout: JSON array) |
|
|
10660
|
-
| identityKey | no |
|
|
10936
|
+
| identityKey | no | "This record IS this entity" \u2014 dot-path to a cross-platform id (e.g. email) \u2192 \`keys[]\` (drives entity merge) |
|
|
10937
|
+
| identityKeys | no | "This record INVOLVES these people" \u2014 \`[{prefix, path}]\` for N participants (#128) \u2192 non-merging \`identity_keys[]\`; query with \`mem find-by-key\` |
|
|
10661
10938
|
| exclude | no | Dot-path fields to strip before storing (e.g. \`["messages[].body"]\`) |
|
|
10662
10939
|
| onInsert/onUpdate/onChange | no | Change hooks (shell command, "log", or flow) |
|
|
10663
10940
|
|
|
@@ -10738,14 +11015,14 @@ async function guideCommand(topic = "all") {
|
|
|
10738
11015
|
json({ topic, title, content, availableTopics });
|
|
10739
11016
|
return;
|
|
10740
11017
|
}
|
|
10741
|
-
intro(
|
|
11018
|
+
intro(pc13.bgCyan(pc13.black(" One Guide ")));
|
|
10742
11019
|
console.log();
|
|
10743
11020
|
console.log(content);
|
|
10744
|
-
console.log(
|
|
11021
|
+
console.log(pc13.dim("\u2500".repeat(60)));
|
|
10745
11022
|
console.log(
|
|
10746
|
-
|
|
11023
|
+
pc13.dim("Available topics: ") + availableTopics.map((t) => pc13.cyan(t.topic)).join(", ")
|
|
10747
11024
|
);
|
|
10748
|
-
console.log(
|
|
11025
|
+
console.log(pc13.dim(`Run ${pc13.cyan("one guide <topic>")} for a specific section.`));
|
|
10749
11026
|
}
|
|
10750
11027
|
|
|
10751
11028
|
// src/lib/platform-meta.ts
|
|
@@ -11068,20 +11345,20 @@ function buildWorkflowIdeas(connections) {
|
|
|
11068
11345
|
// src/commands/logout.ts
|
|
11069
11346
|
import fs12 from "fs";
|
|
11070
11347
|
import * as p9 from "@clack/prompts";
|
|
11071
|
-
function formatWhoami(config2, apiKey,
|
|
11348
|
+
function formatWhoami(config2, apiKey, pc15) {
|
|
11072
11349
|
const whoami = config2.whoami;
|
|
11073
11350
|
const env = getEnvFromApiKey(apiKey);
|
|
11074
|
-
const envLabel = env === "test" ?
|
|
11351
|
+
const envLabel = env === "test" ? pc15.yellow("test") : pc15.green("live");
|
|
11075
11352
|
const lines = [];
|
|
11076
11353
|
if (whoami) {
|
|
11077
11354
|
const contextParts = [];
|
|
11078
11355
|
if (whoami.organization) contextParts.push(whoami.organization.name);
|
|
11079
11356
|
if (whoami.project) contextParts.push(whoami.project.name);
|
|
11080
11357
|
const scopeDisplay = contextParts.length > 0 ? contextParts.join(" / ") : "Personal";
|
|
11081
|
-
lines.push(`${
|
|
11082
|
-
lines.push(`${whoami.user.name} ${
|
|
11358
|
+
lines.push(`${pc15.bold(scopeDisplay)} ${pc15.dim("\xB7")} ${envLabel}`);
|
|
11359
|
+
lines.push(`${whoami.user.name} ${pc15.dim(`(${whoami.user.email})`)}`);
|
|
11083
11360
|
} else {
|
|
11084
|
-
lines.push(`${
|
|
11361
|
+
lines.push(`${pc15.dim("Key:")} ${apiKey.slice(0, 8)}... ${pc15.dim("\xB7")} ${envLabel}`);
|
|
11085
11362
|
}
|
|
11086
11363
|
return lines;
|
|
11087
11364
|
}
|
|
@@ -11110,7 +11387,7 @@ async function logoutCommand() {
|
|
|
11110
11387
|
json({ status: cleared ? "logged_out" : "not_logged_in", message: cleared ? "Credentials cleared." : "No config found." });
|
|
11111
11388
|
return;
|
|
11112
11389
|
}
|
|
11113
|
-
const
|
|
11390
|
+
const pc15 = (await import("picocolors")).default;
|
|
11114
11391
|
const globalConfig = readGlobalConfig();
|
|
11115
11392
|
const projectConfig = readProjectConfig();
|
|
11116
11393
|
const hasGlobal = globalConfig?.apiKey != null;
|
|
@@ -11119,13 +11396,13 @@ async function logoutCommand() {
|
|
|
11119
11396
|
if (hasGlobal && hasProject) {
|
|
11120
11397
|
const infoLines = ["You are logged in with multiple configs.", ""];
|
|
11121
11398
|
if (projectConfig) {
|
|
11122
|
-
infoLines.push(`${
|
|
11123
|
-
infoLines.push(...formatWhoami(projectConfig, projectConfig.apiKey,
|
|
11399
|
+
infoLines.push(`${pc15.cyan("Local config:")}`);
|
|
11400
|
+
infoLines.push(...formatWhoami(projectConfig, projectConfig.apiKey, pc15));
|
|
11124
11401
|
infoLines.push("");
|
|
11125
11402
|
}
|
|
11126
11403
|
if (globalConfig) {
|
|
11127
|
-
infoLines.push(`${
|
|
11128
|
-
infoLines.push(...formatWhoami(globalConfig, globalConfig.apiKey,
|
|
11404
|
+
infoLines.push(`${pc15.magenta("Global config:")}`);
|
|
11405
|
+
infoLines.push(...formatWhoami(globalConfig, globalConfig.apiKey, pc15));
|
|
11129
11406
|
}
|
|
11130
11407
|
p9.note(infoLines.join("\n"));
|
|
11131
11408
|
const choice = await p9.select({
|
|
@@ -11143,14 +11420,14 @@ async function logoutCommand() {
|
|
|
11143
11420
|
targetScope = choice;
|
|
11144
11421
|
} else if (hasProject) {
|
|
11145
11422
|
const infoLines = ["You are logged in.", ""];
|
|
11146
|
-
infoLines.push(`${
|
|
11147
|
-
infoLines.push(...formatWhoami(projectConfig, projectConfig.apiKey,
|
|
11423
|
+
infoLines.push(`${pc15.dim("Stored in")} ${pc15.cyan("local config")}`);
|
|
11424
|
+
infoLines.push(...formatWhoami(projectConfig, projectConfig.apiKey, pc15));
|
|
11148
11425
|
p9.note(infoLines.join("\n"));
|
|
11149
11426
|
targetScope = "project";
|
|
11150
11427
|
} else if (hasGlobal) {
|
|
11151
11428
|
const infoLines = ["You are logged in.", ""];
|
|
11152
|
-
infoLines.push(`${
|
|
11153
|
-
infoLines.push(...formatWhoami(globalConfig, globalConfig.apiKey,
|
|
11429
|
+
infoLines.push(`${pc15.dim("Stored in")} ${pc15.magenta("global config")}`);
|
|
11430
|
+
infoLines.push(...formatWhoami(globalConfig, globalConfig.apiKey, pc15));
|
|
11154
11431
|
p9.note(infoLines.join("\n"));
|
|
11155
11432
|
targetScope = "global";
|
|
11156
11433
|
} else {
|
|
@@ -11187,7 +11464,7 @@ async function logoutCommand() {
|
|
|
11187
11464
|
// src/lib/analytics.ts
|
|
11188
11465
|
import { createRequire as createRequire2 } from "module";
|
|
11189
11466
|
import { randomUUID, createHash } from "crypto";
|
|
11190
|
-
import
|
|
11467
|
+
import pc14 from "picocolors";
|
|
11191
11468
|
var require3 = createRequire2(import.meta.url);
|
|
11192
11469
|
var DEFAULT_POSTHOG_HOST = "https://us.i.posthog.com";
|
|
11193
11470
|
var DEFAULT_POSTHOG_KEY = "phc_a9ok4w0uxiZcVoSWOISIlin85lHMXQD3vWPaYnuRlRV";
|
|
@@ -11433,7 +11710,7 @@ function maybeShowTelemetryNotice() {
|
|
|
11433
11710
|
if (telemetryNoticeShown()) return;
|
|
11434
11711
|
markTelemetryNoticeShown();
|
|
11435
11712
|
process.stderr.write(
|
|
11436
|
-
|
|
11713
|
+
pc14.dim(
|
|
11437
11714
|
"One CLI collects usage analytics (which commands run, linked to your One account) to improve the product.\nNo arguments, inputs, or secrets are ever collected. Opt out anytime with ONE_NO_TELEMETRY=1.\n"
|
|
11438
11715
|
)
|
|
11439
11716
|
);
|
|
@@ -11456,7 +11733,7 @@ program.name("one").option("--agent", "Machine-readable JSON output (no colors,
|
|
|
11456
11733
|
one whoami Show current user, organization, and project
|
|
11457
11734
|
|
|
11458
11735
|
Workflow (use these in order):
|
|
11459
|
-
1. one list List
|
|
11736
|
+
1. one list List connected platforms, keys, and your access on each
|
|
11460
11737
|
2. one actions search <platform> <q> Search for actions using natural language
|
|
11461
11738
|
3. one actions knowledge <plat> <id> Get full docs for an action (ALWAYS do this before execute)
|
|
11462
11739
|
4. one actions execute <p> <id> <key> Execute the action
|
|
@@ -11714,7 +11991,7 @@ var connection = program.command("connection").description("Manage connections")
|
|
|
11714
11991
|
connection.command("add [platform]").alias("a").description("Add a new connection").option("--tag <name>", "Tag the new connection (disambiguates multiple connections per platform in sync/flow profiles)").action(async (platform, options) => {
|
|
11715
11992
|
await connectionAddCommand(platform, { tag: options.tag });
|
|
11716
11993
|
});
|
|
11717
|
-
connection.command("list").alias("ls").description("List your connections").option("-s, --search <query>", "Filter connections by platform name").option("-l, --limit <n>", "Max connections to return (agent mode default: 20)").action(async (options) => {
|
|
11994
|
+
connection.command("list").alias("ls").description("List your connections and what your access config lets you run on each").option("-s, --search <query>", "Filter connections by platform name").option("-l, --limit <n>", "Max connections to return (agent mode default: 20)").action(async (options) => {
|
|
11718
11995
|
await connectionListCommand(options);
|
|
11719
11996
|
});
|
|
11720
11997
|
connection.command("delete <connection-key>").alias("rm").description("Delete a connection").option("-f, --force", "Skip confirmation prompt").action(async (connectionKey, options) => {
|
|
@@ -11862,21 +12139,21 @@ program.command("whoami").description("Show the user, organization, and project
|
|
|
11862
12139
|
});
|
|
11863
12140
|
return;
|
|
11864
12141
|
}
|
|
11865
|
-
const
|
|
12142
|
+
const pc15 = (await import("picocolors")).default;
|
|
11866
12143
|
const contextParts = [];
|
|
11867
12144
|
if (whoami.organization) contextParts.push(whoami.organization.name);
|
|
11868
12145
|
if (whoami.project) contextParts.push(whoami.project.name);
|
|
11869
12146
|
const scopeDisplay = contextParts.length > 0 ? contextParts.join(" / ") : "Personal";
|
|
11870
|
-
const envLabel = env === "test" ?
|
|
11871
|
-
const configLabel = configScope === "project" ?
|
|
12147
|
+
const envLabel = env === "test" ? pc15.yellow("test") : pc15.green("live");
|
|
12148
|
+
const configLabel = configScope === "project" ? pc15.cyan("project config") : pc15.magenta("global config");
|
|
11872
12149
|
console.log();
|
|
11873
|
-
console.log(` ${
|
|
11874
|
-
console.log(` ${whoami.user.name} ${
|
|
11875
|
-
if (whoami.organization) console.log(` ${
|
|
11876
|
-
if (whoami.project) console.log(` ${
|
|
12150
|
+
console.log(` ${pc15.bold(scopeDisplay)} ${pc15.dim("\xB7")} ${envLabel}`);
|
|
12151
|
+
console.log(` ${whoami.user.name} ${pc15.dim(`(${whoami.user.email})`)}`);
|
|
12152
|
+
if (whoami.organization) console.log(` ${pc15.dim("Org:")} ${whoami.organization.name} ${pc15.dim(`(${whoami.organization.id})`)}`);
|
|
12153
|
+
if (whoami.project) console.log(` ${pc15.dim("Project:")} ${whoami.project.name} ${pc15.dim(`(${whoami.project.id})`)}`);
|
|
11877
12154
|
console.log();
|
|
11878
|
-
console.log(` ${
|
|
11879
|
-
console.log(` ${
|
|
12155
|
+
console.log(` ${pc15.dim("Using")} ${configLabel}`);
|
|
12156
|
+
console.log(` ${pc15.dim("API:")} ${apiBase}`);
|
|
11880
12157
|
console.log();
|
|
11881
12158
|
});
|
|
11882
12159
|
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) => {
|