@withone/cli 1.42.0 → 1.43.3
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 +57 -17
- package/dist/chunk-3ZJVO4GP.js +59 -0
- package/dist/chunk-44CV5IMX.js +38 -0
- package/dist/chunk-AU2ZEEMS.js +477 -0
- package/dist/chunk-C3ORS3RG.js +762 -0
- package/dist/{chunk-PK2RAVAF.js → chunk-EMAQVPRD.js} +6 -36
- package/dist/chunk-IAFQVFCB.js +1708 -0
- package/dist/chunk-MRGKKO54.js +158 -0
- package/dist/embedding-O4XWBL6T.js +10 -0
- package/dist/{flow-runner-Y2CXXU3U.js → flow-runner-XSJ4US5S.js} +2 -1
- package/dist/index.js +2856 -1691
- package/dist/migrate-BEARUBLO.js +16 -0
- package/dist/runtime-NVC7KAJZ.js +15 -0
- package/dist/sql-EGTWQIU5.js +11 -0
- package/package.json +6 -2
- package/profiles/attio/attioCompanies.json +5 -3
- package/profiles/attio/attioPeople.json +5 -4
- package/skills/one/SKILL.md +83 -10
package/README.md
CHANGED
|
@@ -303,24 +303,61 @@ Default TTL is 1 hour. Configure via `ONE_CACHE_TTL` environment variable or `ca
|
|
|
303
303
|
|
|
304
304
|
Note: `actions execute` is never cached — it always hits the API fresh.
|
|
305
305
|
|
|
306
|
-
### `one
|
|
306
|
+
### `one mem` — unified memory store
|
|
307
307
|
|
|
308
|
-
|
|
308
|
+
One ships a local memory store (a real Postgres process bootstrapped on demand via the bundled `embedded-postgres` plugin, with a `postgres` plugin for remote/self-hosted) that backs both user-authored notes and synced platform data. **Zero-config** — the first `one mem` call on a fresh machine auto-initializes the cluster at `~/.one/pg/cluster/` and writes a daemon PID file so subsequent CLI invocations reuse it.
|
|
309
309
|
|
|
310
310
|
```bash
|
|
311
|
-
|
|
311
|
+
# User memories — works immediately on a new install
|
|
312
|
+
one mem add note '{"content":"Design review is Thursday"}' --tags work --weight 7
|
|
313
|
+
one mem search "design review" # hybrid FTS + semantic (if key set)
|
|
314
|
+
one mem list note --limit 20
|
|
315
|
+
|
|
316
|
+
# Listing synced platform rows — type is positional and namespaced as <platform>/<model>;
|
|
317
|
+
# there is NO --platform flag and NO platform column in the schema.
|
|
318
|
+
one mem list "gmail/threads"
|
|
319
|
+
one mem list "attio/attioPeople" --limit 5
|
|
320
|
+
|
|
321
|
+
# Enable semantic search (optional)
|
|
322
|
+
one init # re-run — prompts for OpenAI key
|
|
323
|
+
# OR
|
|
324
|
+
one mem config set embedding.apiKey sk-... # writes top-level openaiApiKey
|
|
325
|
+
# OR
|
|
326
|
+
export OPENAI_API_KEY=sk-... # no persistence
|
|
327
|
+
|
|
328
|
+
# Status + diagnostics
|
|
329
|
+
one mem status # backend, provider, _upgrade hint
|
|
330
|
+
one mem doctor # 7-check health report
|
|
312
331
|
```
|
|
313
332
|
|
|
333
|
+
Key surfaces: `add`, `get`, `update`, `archive`, `list`, `search` (`--deep` forces semantic), `context`, `link`/`linked`/`unlink`, `sources`, `find-by-source`, `export`, `import`, `migrate`, `vacuum`, `reindex`. Run `one guide memory` for the full reference.
|
|
334
|
+
|
|
335
|
+
### `one sync` (and `one mem sync` alias)
|
|
336
|
+
|
|
337
|
+
Sync platform data into the unified memory store for instant queries, hybrid FTS + semantic search, scheduled refresh, and change-driven automation. Memory is always written; pass `--no-memory` to skip (rare).
|
|
338
|
+
|
|
314
339
|
```bash
|
|
315
|
-
# Discover → init (
|
|
340
|
+
# Discover → init (seeds from built-in, auto-tests) → declare searchable → preview → run
|
|
316
341
|
one sync models stripe
|
|
317
|
-
one sync init stripe balanceTransactions
|
|
342
|
+
one sync init stripe balanceTransactions
|
|
343
|
+
|
|
344
|
+
# Optional: declare clean fields for embedding, then preview
|
|
345
|
+
one sync init stripe balanceTransactions --config '{
|
|
346
|
+
"memory": {
|
|
347
|
+
"embed": true,
|
|
348
|
+
"searchable": ["description","type","amount","currency"]
|
|
349
|
+
}
|
|
350
|
+
}'
|
|
351
|
+
one sync test stripe/balanceTransactions --show-searchable
|
|
352
|
+
|
|
353
|
+
# Run — every row lands in memory (SQLite also written for enrich-phase compat)
|
|
318
354
|
one sync run stripe --since 90d
|
|
355
|
+
one mem sync run stripe # identical (alias)
|
|
319
356
|
|
|
320
|
-
# Query
|
|
357
|
+
# Query + search (reads from memory)
|
|
321
358
|
one sync query stripe/balanceTransactions --where "status=available" --limit 20
|
|
322
|
-
one sync
|
|
323
|
-
one sync
|
|
359
|
+
one sync query stripe/customers --where 'address.city=SF' # dotted paths supported
|
|
360
|
+
one sync search "refund" # hybrid, per-type
|
|
324
361
|
|
|
325
362
|
# Schedule unattended syncs + change hooks
|
|
326
363
|
one sync schedule add stripe --every 1h
|
|
@@ -334,20 +371,23 @@ one sync run stripe --full-refresh
|
|
|
334
371
|
|
|
335
372
|
> **Connections are late-bound.** Profiles use `"connection": { "platform": "<name>", "tag"?: "..." }` instead of literal `connectionKey` strings. The key is resolved at sync time, so `one add <platform>` (re-auth) doesn't break the profile. `tag` only needed for multi-account platforms (e.g. two Gmail accounts).
|
|
336
373
|
|
|
374
|
+
> **`memory.searchable` paths.** Drives what gets embedded + FTS-indexed. Supports numeric indexes (`values.name[0].full_name`) and `[]` wildcards (`messages[].snippet`, `messages[].payload.parts[].body.data`). Without declared paths the default walker concatenates every string — correct but noisy for hierarchical APIs. Always declare when `embed: true`.
|
|
375
|
+
|
|
337
376
|
| Subcommand | What it does |
|
|
338
377
|
|------------|-------------|
|
|
339
|
-
| `
|
|
378
|
+
| `profiles [platform]` | List built-in pre-validated profiles |
|
|
379
|
+
| `doctor` | Verify sync engine health |
|
|
340
380
|
| `models <platform>` | Discover available data models |
|
|
341
|
-
| `init <platform> <model>` | Create profile (
|
|
342
|
-
| `test <platform>/<model>` | Validate
|
|
343
|
-
| `run <platform>` | Sync data (`--full-refresh`, `--since`, `--dry-run`) |
|
|
344
|
-
| `query <platform>/<model>` | Query with `--where
|
|
345
|
-
| `search <query>` |
|
|
346
|
-
| `
|
|
381
|
+
| `init <platform> <model>` | Create/patch profile (seeds from built-in, auto-tests) |
|
|
382
|
+
| `test <platform>/<model>` | Validate profile. `--show-searchable` previews embedded text |
|
|
383
|
+
| `run <platform>` | Sync data (`--full-refresh`, `--since`, `--dry-run`, `--no-memory`) |
|
|
384
|
+
| `query <platform>/<model>` | Query memory with `--where` (dotted paths), `--after/before` |
|
|
385
|
+
| `search <query>` | Hybrid FTS + semantic across all synced data |
|
|
386
|
+
| `list [platform]` | Show profiles, record counts, freshness |
|
|
347
387
|
| `schedule add/list/status/remove/repair` | Cron-backed scheduled syncs with drift detection |
|
|
348
|
-
| `remove <platform>` | Delete
|
|
388
|
+
| `remove <platform>` | Delete synced data (`--dry-run` to preview) |
|
|
349
389
|
|
|
350
|
-
Change hooks (`onInsert`, `onUpdate`, `onChange`) fire per-page during sync — pipe to a shell command, a flow, or an event log. Root-array responses (e.g. Hacker News `/v0/topstories.json` → `[9129911, 9129199, ...]`) are supported by setting `resultsPath` to `""`, `"$"`, or `"."`; primitive elements are auto-wrapped as `{ [idField]: value }`. Run `one guide sync` for the full reference.
|
|
390
|
+
Change hooks (`onInsert`, `onUpdate`, `onChange`) fire per-page during sync — pipe to a shell command, a flow, or an event log. Root-array responses (e.g. Hacker News `/v0/topstories.json` → `[9129911, 9129199, ...]`) are supported by setting `resultsPath` to `""`, `"$"`, or `"."`; primitive elements are auto-wrapped as `{ [idField]: value }`. Run `one guide sync` or `one guide memory` for the full reference.
|
|
351
391
|
|
|
352
392
|
### `one relay`
|
|
353
393
|
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import {
|
|
2
|
+
error,
|
|
3
|
+
isAgentMode,
|
|
4
|
+
json,
|
|
5
|
+
requireMemoryInit
|
|
6
|
+
} from "./chunk-MRGKKO54.js";
|
|
7
|
+
import {
|
|
8
|
+
getBackend
|
|
9
|
+
} from "./chunk-IAFQVFCB.js";
|
|
10
|
+
|
|
11
|
+
// src/commands/mem/sql.ts
|
|
12
|
+
async function memSqlCommand(sql) {
|
|
13
|
+
requireMemoryInit();
|
|
14
|
+
const backend = await getBackend();
|
|
15
|
+
if (!backend.capabilities().rawSql || !backend.raw) {
|
|
16
|
+
error(
|
|
17
|
+
"This memory backend does not support raw SQL (capabilities.rawSql = false). Use `mem list` / `mem search` / `mem find-by-source` for high-level queries."
|
|
18
|
+
);
|
|
19
|
+
}
|
|
20
|
+
try {
|
|
21
|
+
const result = await backend.raw(sql);
|
|
22
|
+
if (isAgentMode()) {
|
|
23
|
+
json({
|
|
24
|
+
columns: result.columns,
|
|
25
|
+
rows: result.rows,
|
|
26
|
+
rowCount: result.rowCount
|
|
27
|
+
});
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
if (result.rows.length === 0) {
|
|
31
|
+
console.log("(0 rows)");
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
console.log(JSON.stringify(result.rows, null, 2));
|
|
35
|
+
console.log(`
|
|
36
|
+
${result.rowCount} row(s)`);
|
|
37
|
+
} catch (err) {
|
|
38
|
+
error(err instanceof Error ? err.message : String(err));
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
async function syncSqlCommand(platformModel, sql) {
|
|
42
|
+
const [platform, model] = platformModel.split("/");
|
|
43
|
+
if (!platform || !model) {
|
|
44
|
+
error(`Usage: one sync sql <platform>/<model> "<SELECT ...>". Example: one sync sql attio/attioPeople "SELECT data->>'id' FROM mem_records WHERE type = 'attio/attioPeople'"`);
|
|
45
|
+
}
|
|
46
|
+
const type = `${platform}/${model}`;
|
|
47
|
+
if (!isAgentMode() && !sql.includes(`'${type}'`) && !sql.includes(`"${type}"`)) {
|
|
48
|
+
process.stderr.write(
|
|
49
|
+
` note: this query does not filter to type = '${type}'. Results span all types. Add \`WHERE type = '${type}'\` to scope.
|
|
50
|
+
`
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
await memSqlCommand(sql);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export {
|
|
57
|
+
memSqlCommand,
|
|
58
|
+
syncSqlCommand
|
|
59
|
+
};
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
// src/lib/dot-path.ts
|
|
2
|
+
function getByDotPath(obj, dotPath) {
|
|
3
|
+
const parts = dotPath.split(".").flatMap((part) => {
|
|
4
|
+
const bracketMatch = part.match(/^([^[]+)\[(\d+)\]$/);
|
|
5
|
+
if (bracketMatch) {
|
|
6
|
+
return [bracketMatch[1], bracketMatch[2]];
|
|
7
|
+
}
|
|
8
|
+
return [part];
|
|
9
|
+
});
|
|
10
|
+
let current = obj;
|
|
11
|
+
for (const part of parts) {
|
|
12
|
+
if (current === null || current === void 0) return void 0;
|
|
13
|
+
if (Array.isArray(current) && /^\d+$/.test(part)) {
|
|
14
|
+
current = current[parseInt(part, 10)];
|
|
15
|
+
} else if (typeof current === "object") {
|
|
16
|
+
current = current[part];
|
|
17
|
+
} else {
|
|
18
|
+
return void 0;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
return current;
|
|
22
|
+
}
|
|
23
|
+
function setByDotPath(obj, dotPath, value) {
|
|
24
|
+
const parts = dotPath.split(".");
|
|
25
|
+
let current = obj;
|
|
26
|
+
for (let i = 0; i < parts.length - 1; i++) {
|
|
27
|
+
if (current[parts[i]] === void 0 || current[parts[i]] === null) {
|
|
28
|
+
current[parts[i]] = {};
|
|
29
|
+
}
|
|
30
|
+
current = current[parts[i]];
|
|
31
|
+
}
|
|
32
|
+
current[parts[parts.length - 1]] = value;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export {
|
|
36
|
+
getByDotPath,
|
|
37
|
+
setByDotPath
|
|
38
|
+
};
|
|
@@ -0,0 +1,477 @@
|
|
|
1
|
+
// src/lib/config.ts
|
|
2
|
+
import fs from "fs";
|
|
3
|
+
import path from "path";
|
|
4
|
+
import os from "os";
|
|
5
|
+
function configDir() {
|
|
6
|
+
return path.join(os.homedir(), ".one");
|
|
7
|
+
}
|
|
8
|
+
function configFile() {
|
|
9
|
+
return path.join(configDir(), "config.json");
|
|
10
|
+
}
|
|
11
|
+
function projectsDir() {
|
|
12
|
+
return path.join(configDir(), "projects");
|
|
13
|
+
}
|
|
14
|
+
function getProjectRoot(cwd = process.cwd()) {
|
|
15
|
+
let dir = path.resolve(cwd);
|
|
16
|
+
const root = path.parse(dir).root;
|
|
17
|
+
while (dir !== root) {
|
|
18
|
+
if (fs.existsSync(path.join(dir, ".git")) || fs.existsSync(path.join(dir, "package.json"))) {
|
|
19
|
+
return dir;
|
|
20
|
+
}
|
|
21
|
+
dir = path.dirname(dir);
|
|
22
|
+
}
|
|
23
|
+
return path.resolve(cwd);
|
|
24
|
+
}
|
|
25
|
+
function getProjectSlug(projectRoot = getProjectRoot()) {
|
|
26
|
+
return projectRoot.replace(/[\\/]/g, "-");
|
|
27
|
+
}
|
|
28
|
+
function getProjectConfigDir(projectRoot = getProjectRoot()) {
|
|
29
|
+
return path.join(projectsDir(), getProjectSlug(projectRoot));
|
|
30
|
+
}
|
|
31
|
+
function getProjectConfigPath(projectRoot = getProjectRoot()) {
|
|
32
|
+
return path.join(getProjectConfigDir(projectRoot), "config.json");
|
|
33
|
+
}
|
|
34
|
+
function getGlobalConfigPath() {
|
|
35
|
+
return configFile();
|
|
36
|
+
}
|
|
37
|
+
function resolveConfig() {
|
|
38
|
+
const projectRoot = getProjectRoot();
|
|
39
|
+
const projectSlug = getProjectSlug(projectRoot);
|
|
40
|
+
const projectPath = getProjectConfigPath(projectRoot);
|
|
41
|
+
if (fs.existsSync(projectPath)) {
|
|
42
|
+
const config = readConfigFile(projectPath);
|
|
43
|
+
if (config) {
|
|
44
|
+
return { config, scope: "project", path: projectPath, projectRoot, projectSlug };
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
const root = path.parse(process.cwd()).root;
|
|
48
|
+
let dir = path.resolve(process.cwd());
|
|
49
|
+
while (dir !== root) {
|
|
50
|
+
dir = path.dirname(dir);
|
|
51
|
+
const slug = getProjectSlug(dir);
|
|
52
|
+
const configPath = path.join(projectsDir(), slug, "config.json");
|
|
53
|
+
if (fs.existsSync(configPath)) {
|
|
54
|
+
const config = readConfigFile(configPath);
|
|
55
|
+
if (config) {
|
|
56
|
+
return { config, scope: "project", path: configPath, projectRoot: dir, projectSlug: slug };
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
if (fs.existsSync(configFile())) {
|
|
61
|
+
const config = readConfigFile(configFile());
|
|
62
|
+
if (config) {
|
|
63
|
+
return { config, scope: "global", path: configFile(), projectRoot, projectSlug };
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
return { config: null, scope: null, path: configFile(), projectRoot, projectSlug };
|
|
67
|
+
}
|
|
68
|
+
function readConfigFile(filePath) {
|
|
69
|
+
try {
|
|
70
|
+
const content = fs.readFileSync(filePath, "utf-8");
|
|
71
|
+
return JSON.parse(content);
|
|
72
|
+
} catch {
|
|
73
|
+
return null;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
function configExists() {
|
|
77
|
+
return resolveConfig().config !== null;
|
|
78
|
+
}
|
|
79
|
+
function globalConfigExists() {
|
|
80
|
+
return fs.existsSync(configFile());
|
|
81
|
+
}
|
|
82
|
+
function projectConfigExists(projectRoot = getProjectRoot()) {
|
|
83
|
+
return fs.existsSync(getProjectConfigPath(projectRoot));
|
|
84
|
+
}
|
|
85
|
+
function readConfig() {
|
|
86
|
+
return resolveConfig().config;
|
|
87
|
+
}
|
|
88
|
+
function readGlobalConfig() {
|
|
89
|
+
if (!fs.existsSync(configFile())) return null;
|
|
90
|
+
return readConfigFile(configFile());
|
|
91
|
+
}
|
|
92
|
+
function readProjectConfig() {
|
|
93
|
+
const projectPath = getProjectConfigPath();
|
|
94
|
+
if (!fs.existsSync(projectPath)) return null;
|
|
95
|
+
return readConfigFile(projectPath);
|
|
96
|
+
}
|
|
97
|
+
function writeConfig(config, scope) {
|
|
98
|
+
const targetScope = scope ?? resolveConfig().scope ?? "global";
|
|
99
|
+
if (targetScope === "project") {
|
|
100
|
+
const dir = getProjectConfigDir();
|
|
101
|
+
if (!fs.existsSync(dir)) {
|
|
102
|
+
fs.mkdirSync(dir, { recursive: true, mode: 448 });
|
|
103
|
+
}
|
|
104
|
+
const filePath = getProjectConfigPath();
|
|
105
|
+
fs.writeFileSync(filePath, JSON.stringify(config, null, 2), { mode: 384 });
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
if (!fs.existsSync(configDir())) {
|
|
109
|
+
fs.mkdirSync(configDir(), { mode: 448 });
|
|
110
|
+
}
|
|
111
|
+
fs.writeFileSync(configFile(), JSON.stringify(config, null, 2), { mode: 384 });
|
|
112
|
+
}
|
|
113
|
+
function readOneRc() {
|
|
114
|
+
const rcPath = path.join(process.cwd(), ".onerc");
|
|
115
|
+
if (!fs.existsSync(rcPath)) return {};
|
|
116
|
+
try {
|
|
117
|
+
const content = fs.readFileSync(rcPath, "utf-8");
|
|
118
|
+
const result = {};
|
|
119
|
+
for (const line of content.split("\n")) {
|
|
120
|
+
const trimmed = line.trim();
|
|
121
|
+
if (!trimmed || trimmed.startsWith("#")) continue;
|
|
122
|
+
const eqIndex = trimmed.indexOf("=");
|
|
123
|
+
if (eqIndex === -1) continue;
|
|
124
|
+
const key = trimmed.slice(0, eqIndex).trim();
|
|
125
|
+
const value = trimmed.slice(eqIndex + 1).trim();
|
|
126
|
+
result[key] = value;
|
|
127
|
+
}
|
|
128
|
+
return result;
|
|
129
|
+
} catch {
|
|
130
|
+
return {};
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
function getApiKey() {
|
|
134
|
+
if (process.env.ONE_SECRET) return process.env.ONE_SECRET;
|
|
135
|
+
const rc = readOneRc();
|
|
136
|
+
if (rc.ONE_SECRET) return rc.ONE_SECRET;
|
|
137
|
+
return readConfig()?.apiKey ?? null;
|
|
138
|
+
}
|
|
139
|
+
function getOpenAiApiKey() {
|
|
140
|
+
if (process.env.OPENAI_API_KEY) return process.env.OPENAI_API_KEY;
|
|
141
|
+
const rc = readOneRc();
|
|
142
|
+
if (rc.OPENAI_API_KEY) return rc.OPENAI_API_KEY;
|
|
143
|
+
return readConfig()?.openaiApiKey ?? null;
|
|
144
|
+
}
|
|
145
|
+
function setOpenAiApiKey(key) {
|
|
146
|
+
const resolved = resolveConfig();
|
|
147
|
+
if (!resolved.config) {
|
|
148
|
+
throw new Error("No One config found. Run `one init` first.");
|
|
149
|
+
}
|
|
150
|
+
if (key === "") {
|
|
151
|
+
delete resolved.config.openaiApiKey;
|
|
152
|
+
} else {
|
|
153
|
+
resolved.config.openaiApiKey = key;
|
|
154
|
+
}
|
|
155
|
+
writeConfig(resolved.config, resolved.scope ?? "global");
|
|
156
|
+
}
|
|
157
|
+
function getAccessControlFromAllSources() {
|
|
158
|
+
const rc = readOneRc();
|
|
159
|
+
const fileAc = getAccessControl();
|
|
160
|
+
const merged = { ...fileAc };
|
|
161
|
+
if (rc.ONE_PERMISSIONS) {
|
|
162
|
+
merged.permissions = rc.ONE_PERMISSIONS;
|
|
163
|
+
}
|
|
164
|
+
if (rc.ONE_CONNECTION_KEYS) {
|
|
165
|
+
merged.connectionKeys = rc.ONE_CONNECTION_KEYS.split(",").map((s) => s.trim()).filter(Boolean);
|
|
166
|
+
}
|
|
167
|
+
if (rc.ONE_ACTION_IDS) {
|
|
168
|
+
merged.actionIds = rc.ONE_ACTION_IDS.split(",").map((s) => s.trim()).filter(Boolean);
|
|
169
|
+
}
|
|
170
|
+
if (rc.ONE_KNOWLEDGE_AGENT) {
|
|
171
|
+
merged.knowledgeAgent = rc.ONE_KNOWLEDGE_AGENT === "true";
|
|
172
|
+
}
|
|
173
|
+
return merged;
|
|
174
|
+
}
|
|
175
|
+
function getAccessControl() {
|
|
176
|
+
return readConfig()?.accessControl ?? {};
|
|
177
|
+
}
|
|
178
|
+
var DEFAULT_API_BASE = "https://api.withone.ai/v1";
|
|
179
|
+
function getApiBase() {
|
|
180
|
+
const config = readConfig();
|
|
181
|
+
if (config?.apiBase) return `${config.apiBase}/v1`;
|
|
182
|
+
return DEFAULT_API_BASE;
|
|
183
|
+
}
|
|
184
|
+
function updateApiBase(url) {
|
|
185
|
+
const config = readConfig();
|
|
186
|
+
if (!config) return;
|
|
187
|
+
if (url) {
|
|
188
|
+
config.apiBase = url;
|
|
189
|
+
} else {
|
|
190
|
+
delete config.apiBase;
|
|
191
|
+
}
|
|
192
|
+
delete config.whoami;
|
|
193
|
+
writeConfig(config);
|
|
194
|
+
}
|
|
195
|
+
function getCacheTtl() {
|
|
196
|
+
if (process.env.ONE_CACHE_TTL) {
|
|
197
|
+
const val = parseInt(process.env.ONE_CACHE_TTL, 10);
|
|
198
|
+
if (!isNaN(val) && val > 0) return val;
|
|
199
|
+
}
|
|
200
|
+
const config = readConfig();
|
|
201
|
+
if (config?.cacheTtl && config.cacheTtl > 0) return config.cacheTtl;
|
|
202
|
+
return 3600;
|
|
203
|
+
}
|
|
204
|
+
function updateAccessControl(settings) {
|
|
205
|
+
const config = readConfig();
|
|
206
|
+
if (!config) return;
|
|
207
|
+
const cleaned = {};
|
|
208
|
+
if (settings.permissions && settings.permissions !== "admin") {
|
|
209
|
+
cleaned.permissions = settings.permissions;
|
|
210
|
+
}
|
|
211
|
+
if (settings.connectionKeys && !(settings.connectionKeys.length === 1 && settings.connectionKeys[0] === "*")) {
|
|
212
|
+
cleaned.connectionKeys = settings.connectionKeys;
|
|
213
|
+
}
|
|
214
|
+
if (settings.actionIds && !(settings.actionIds.length === 1 && settings.actionIds[0] === "*")) {
|
|
215
|
+
cleaned.actionIds = settings.actionIds;
|
|
216
|
+
}
|
|
217
|
+
if (settings.knowledgeAgent) {
|
|
218
|
+
cleaned.knowledgeAgent = true;
|
|
219
|
+
}
|
|
220
|
+
if (Object.keys(cleaned).length === 0) {
|
|
221
|
+
delete config.accessControl;
|
|
222
|
+
} else {
|
|
223
|
+
config.accessControl = cleaned;
|
|
224
|
+
}
|
|
225
|
+
writeConfig(config);
|
|
226
|
+
}
|
|
227
|
+
function getWhoAmI() {
|
|
228
|
+
return readConfig()?.whoami ?? null;
|
|
229
|
+
}
|
|
230
|
+
function updateWhoAmI(whoami) {
|
|
231
|
+
const config = readConfig();
|
|
232
|
+
if (!config) return;
|
|
233
|
+
config.whoami = whoami;
|
|
234
|
+
writeConfig(config);
|
|
235
|
+
}
|
|
236
|
+
async function ensureWhoAmI(api) {
|
|
237
|
+
const cached = getWhoAmI();
|
|
238
|
+
if (cached) return cached;
|
|
239
|
+
try {
|
|
240
|
+
const whoami = await api.whoami();
|
|
241
|
+
updateWhoAmI(whoami);
|
|
242
|
+
return whoami;
|
|
243
|
+
} catch {
|
|
244
|
+
return null;
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
function getEnvFromApiKey(apiKey) {
|
|
248
|
+
return apiKey.startsWith("sk_test_") ? "test" : "live";
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// src/lib/memory/config.ts
|
|
252
|
+
var DEFAULT_MEMORY_CONFIG = {
|
|
253
|
+
backend: "embedded-postgres",
|
|
254
|
+
plugins: [],
|
|
255
|
+
embedding: {
|
|
256
|
+
provider: "none",
|
|
257
|
+
model: "text-embedding-3-small",
|
|
258
|
+
dimensions: 1536
|
|
259
|
+
},
|
|
260
|
+
defaults: {
|
|
261
|
+
trackAccessOnSearch: true,
|
|
262
|
+
embedOnAdd: true,
|
|
263
|
+
embedOnSync: false
|
|
264
|
+
}
|
|
265
|
+
};
|
|
266
|
+
function getMemoryConfig() {
|
|
267
|
+
const config = readConfig();
|
|
268
|
+
return config?.memory ?? null;
|
|
269
|
+
}
|
|
270
|
+
function getMemoryConfigOrDefault() {
|
|
271
|
+
return getMemoryConfig() ?? DEFAULT_MEMORY_CONFIG;
|
|
272
|
+
}
|
|
273
|
+
function memoryConfigExists() {
|
|
274
|
+
return getMemoryConfig() !== null;
|
|
275
|
+
}
|
|
276
|
+
function updateMemoryConfig(patch, opts = {}) {
|
|
277
|
+
const config = readConfig();
|
|
278
|
+
if (!config) {
|
|
279
|
+
throw new Error("No One config found. Run `one init` first.");
|
|
280
|
+
}
|
|
281
|
+
const current = config.memory ?? DEFAULT_MEMORY_CONFIG;
|
|
282
|
+
const next = opts.replace ? patch : { ...current, ...patch };
|
|
283
|
+
config.memory = next;
|
|
284
|
+
writeConfig(config);
|
|
285
|
+
return next;
|
|
286
|
+
}
|
|
287
|
+
function getEmbeddingApiKey() {
|
|
288
|
+
const fromCore = getOpenAiApiKey();
|
|
289
|
+
if (fromCore) return fromCore;
|
|
290
|
+
const mem = getMemoryConfig();
|
|
291
|
+
return mem?.embedding.apiKey ?? null;
|
|
292
|
+
}
|
|
293
|
+
function setOpenAiApiKey2(key) {
|
|
294
|
+
setOpenAiApiKey(key);
|
|
295
|
+
if (key === "") return;
|
|
296
|
+
const mem = getMemoryConfig();
|
|
297
|
+
if (!mem) return;
|
|
298
|
+
if (mem.embedding.provider === "openai") return;
|
|
299
|
+
updateMemoryConfig({
|
|
300
|
+
...mem,
|
|
301
|
+
embedding: { ...mem.embedding, provider: "openai" }
|
|
302
|
+
});
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
// src/lib/memory/embedding.ts
|
|
306
|
+
var FETCH_TIMEOUT_MS = 3e4;
|
|
307
|
+
function fetchWithTimeout(url, init, timeoutMs) {
|
|
308
|
+
const ctrl = new AbortController();
|
|
309
|
+
const t = setTimeout(() => ctrl.abort(), timeoutMs);
|
|
310
|
+
return fetch(url, { ...init, signal: ctrl.signal }).finally(() => clearTimeout(t));
|
|
311
|
+
}
|
|
312
|
+
async function embed(text, opts = {}) {
|
|
313
|
+
const clean = text?.trim();
|
|
314
|
+
if (!clean) return null;
|
|
315
|
+
const cfg = getMemoryConfigOrDefault();
|
|
316
|
+
if (cfg.embedding.provider !== "openai") return null;
|
|
317
|
+
const apiKey = getEmbeddingApiKey();
|
|
318
|
+
if (!apiKey) return null;
|
|
319
|
+
const model = opts.model ?? cfg.embedding.model;
|
|
320
|
+
const dimensions = cfg.embedding.dimensions;
|
|
321
|
+
for (let attempt = 0; attempt < 3; attempt++) {
|
|
322
|
+
try {
|
|
323
|
+
const res = await fetchWithTimeout("https://api.openai.com/v1/embeddings", {
|
|
324
|
+
method: "POST",
|
|
325
|
+
headers: {
|
|
326
|
+
"Content-Type": "application/json",
|
|
327
|
+
Authorization: `Bearer ${apiKey}`
|
|
328
|
+
},
|
|
329
|
+
body: JSON.stringify({
|
|
330
|
+
model,
|
|
331
|
+
input: clean.slice(0, 8e3),
|
|
332
|
+
dimensions
|
|
333
|
+
})
|
|
334
|
+
}, FETCH_TIMEOUT_MS);
|
|
335
|
+
if (!res.ok) {
|
|
336
|
+
if (res.status === 429 || res.status >= 500) {
|
|
337
|
+
await sleep(500 * (attempt + 1));
|
|
338
|
+
continue;
|
|
339
|
+
}
|
|
340
|
+
const body2 = await res.text();
|
|
341
|
+
throw new Error(`OpenAI embeddings ${res.status}: ${body2}`);
|
|
342
|
+
}
|
|
343
|
+
const body = await res.json();
|
|
344
|
+
const vector = body.data[0]?.embedding;
|
|
345
|
+
if (!vector || vector.length !== dimensions) {
|
|
346
|
+
throw new Error(`Unexpected embedding shape (got length ${vector?.length})`);
|
|
347
|
+
}
|
|
348
|
+
return { vector, model: `openai:${model}` };
|
|
349
|
+
} catch (err) {
|
|
350
|
+
if (attempt === 2) {
|
|
351
|
+
process.stderr.write(`[mem] embedding failed: ${err instanceof Error ? err.message : String(err)}
|
|
352
|
+
`);
|
|
353
|
+
return null;
|
|
354
|
+
}
|
|
355
|
+
await sleep(500 * (attempt + 1));
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
return null;
|
|
359
|
+
}
|
|
360
|
+
async function embedBatch(texts, opts = {}) {
|
|
361
|
+
if (texts.length === 0) return [];
|
|
362
|
+
const cfg = getMemoryConfigOrDefault();
|
|
363
|
+
if (cfg.embedding.provider !== "openai") return texts.map(() => null);
|
|
364
|
+
const apiKey = getEmbeddingApiKey();
|
|
365
|
+
if (!apiKey) return texts.map(() => null);
|
|
366
|
+
const model = opts.model ?? cfg.embedding.model;
|
|
367
|
+
const dimensions = cfg.embedding.dimensions;
|
|
368
|
+
const active = [];
|
|
369
|
+
texts.forEach((t, i) => {
|
|
370
|
+
const clean = t?.trim();
|
|
371
|
+
if (clean) active.push({ index: i, input: clean.slice(0, 8e3) });
|
|
372
|
+
});
|
|
373
|
+
if (active.length === 0) return texts.map(() => null);
|
|
374
|
+
const result = texts.map(() => null);
|
|
375
|
+
for (let attempt = 0; attempt < 3; attempt++) {
|
|
376
|
+
try {
|
|
377
|
+
const res = await fetchWithTimeout("https://api.openai.com/v1/embeddings", {
|
|
378
|
+
method: "POST",
|
|
379
|
+
headers: {
|
|
380
|
+
"Content-Type": "application/json",
|
|
381
|
+
Authorization: `Bearer ${apiKey}`
|
|
382
|
+
},
|
|
383
|
+
body: JSON.stringify({
|
|
384
|
+
model,
|
|
385
|
+
input: active.map((a) => a.input),
|
|
386
|
+
dimensions
|
|
387
|
+
})
|
|
388
|
+
}, FETCH_TIMEOUT_MS);
|
|
389
|
+
if (!res.ok) {
|
|
390
|
+
if (res.status === 429 || res.status >= 500) {
|
|
391
|
+
await sleep(500 * (attempt + 1));
|
|
392
|
+
continue;
|
|
393
|
+
}
|
|
394
|
+
const body2 = await res.text();
|
|
395
|
+
throw new Error(`OpenAI embeddings ${res.status}: ${body2}`);
|
|
396
|
+
}
|
|
397
|
+
const body = await res.json();
|
|
398
|
+
for (const item of body.data) {
|
|
399
|
+
const slot = active[item.index];
|
|
400
|
+
if (!slot) continue;
|
|
401
|
+
result[slot.index] = { vector: item.embedding, model: `openai:${model}` };
|
|
402
|
+
}
|
|
403
|
+
return result;
|
|
404
|
+
} catch (err) {
|
|
405
|
+
if (attempt === 2) {
|
|
406
|
+
process.stderr.write(`[mem] batch embedding failed: ${err instanceof Error ? err.message : String(err)}
|
|
407
|
+
`);
|
|
408
|
+
return result;
|
|
409
|
+
}
|
|
410
|
+
await sleep(500 * (attempt + 1));
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
return result;
|
|
414
|
+
}
|
|
415
|
+
function sleep(ms) {
|
|
416
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
417
|
+
}
|
|
418
|
+
function defaultSearchableText(data, maxLen = 4e3) {
|
|
419
|
+
const parts = [];
|
|
420
|
+
const walk = (value, depth = 0) => {
|
|
421
|
+
if (value === null || value === void 0) return;
|
|
422
|
+
if (typeof value === "string" && value.trim()) {
|
|
423
|
+
parts.push(value.trim());
|
|
424
|
+
return;
|
|
425
|
+
}
|
|
426
|
+
if (typeof value === "number" || typeof value === "boolean") {
|
|
427
|
+
parts.push(String(value));
|
|
428
|
+
return;
|
|
429
|
+
}
|
|
430
|
+
if (depth > 4) return;
|
|
431
|
+
if (Array.isArray(value)) {
|
|
432
|
+
for (const v of value) walk(v, depth + 1);
|
|
433
|
+
return;
|
|
434
|
+
}
|
|
435
|
+
if (typeof value === "object") {
|
|
436
|
+
for (const v of Object.values(value)) walk(v, depth + 1);
|
|
437
|
+
}
|
|
438
|
+
};
|
|
439
|
+
walk(data);
|
|
440
|
+
const joined = parts.join(" ").replace(/\s+/g, " ").trim();
|
|
441
|
+
return joined.length > maxLen ? joined.slice(0, maxLen) : joined;
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
export {
|
|
445
|
+
getProjectRoot,
|
|
446
|
+
getProjectConfigPath,
|
|
447
|
+
getGlobalConfigPath,
|
|
448
|
+
resolveConfig,
|
|
449
|
+
configExists,
|
|
450
|
+
globalConfigExists,
|
|
451
|
+
projectConfigExists,
|
|
452
|
+
readConfig,
|
|
453
|
+
readGlobalConfig,
|
|
454
|
+
readProjectConfig,
|
|
455
|
+
writeConfig,
|
|
456
|
+
getApiKey,
|
|
457
|
+
getOpenAiApiKey,
|
|
458
|
+
getAccessControlFromAllSources,
|
|
459
|
+
getAccessControl,
|
|
460
|
+
getApiBase,
|
|
461
|
+
updateApiBase,
|
|
462
|
+
getCacheTtl,
|
|
463
|
+
updateAccessControl,
|
|
464
|
+
getWhoAmI,
|
|
465
|
+
updateWhoAmI,
|
|
466
|
+
ensureWhoAmI,
|
|
467
|
+
getEnvFromApiKey,
|
|
468
|
+
DEFAULT_MEMORY_CONFIG,
|
|
469
|
+
getMemoryConfig,
|
|
470
|
+
getMemoryConfigOrDefault,
|
|
471
|
+
memoryConfigExists,
|
|
472
|
+
updateMemoryConfig,
|
|
473
|
+
setOpenAiApiKey2 as setOpenAiApiKey,
|
|
474
|
+
embed,
|
|
475
|
+
embedBatch,
|
|
476
|
+
defaultSearchableText
|
|
477
|
+
};
|