@withone/cli 1.47.11 → 1.49.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 CHANGED
@@ -329,9 +329,18 @@ One ships a local memory store (a real Postgres process bootstrapped on demand v
329
329
  ```bash
330
330
  # User memories — works immediately on a new install
331
331
  one mem add note '{"content":"Design review is Thursday"}' --tags work --weight 7
332
+ one mem update <id> '{"status":"done"}' # merges into data; refreshes searchable_text
332
333
  one mem search "design review" # hybrid FTS + semantic (if key set)
333
334
  one mem list note --limit 20
334
335
 
336
+ # Identity keys are a first-class column (unique across ACTIVE records; archiving
337
+ # frees them). Manage them after creation with `mem key` — NOT via `mem update`.
338
+ one mem key <id> --add email:x@y.com # also --remove / --set <csv>
339
+
340
+ # Backfill searchable_text for rows that landed NULL or with UUID-noise-heavy text
341
+ # (drops ids/timestamps, leads with name/title/email). No embedding provider needed.
342
+ one mem reindex --searchable --type attio/attioPeople
343
+
335
344
  # Listing synced platform rows — type is positional and namespaced as <platform>/<model>;
336
345
  # there is NO --platform flag and NO platform column in the schema.
337
346
  one mem list "gmail/threads"
@@ -4,7 +4,7 @@ import {
4
4
  } from "./chunk-44CV5IMX.js";
5
5
  import {
6
6
  getCacheTtl
7
- } from "./chunk-K6MWE2ZH.js";
7
+ } from "./chunk-SO323PZP.js";
8
8
 
9
9
  // src/lib/flow-runner.ts
10
10
  import fs3 from "fs";
@@ -2319,7 +2319,7 @@ async function executeSubflowStep(step, context, api, permissions, allowedAction
2319
2319
  if (flowStack.includes(resolvedKey)) {
2320
2320
  throw new Error(`Circular flow detected: ${[...flowStack, resolvedKey].join(" \u2192 ")}`);
2321
2321
  }
2322
- const { loadFlowWithMeta: loadFlowWithMeta2 } = await import("./flow-runner-KBTX5XYX.js");
2322
+ const { loadFlowWithMeta: loadFlowWithMeta2 } = await import("./flow-runner-IBB42ED2.js");
2323
2323
  const { flow: subFlow, rootDir: subRootDir } = loadFlowWithMeta2(resolvedKey);
2324
2324
  const subContext = await executeFlow(
2325
2325
  subFlow,
@@ -3,10 +3,10 @@ import {
3
3
  isAgentMode,
4
4
  json,
5
5
  requireMemoryInit
6
- } from "./chunk-HS5AHQ4V.js";
6
+ } from "./chunk-CLJFSFFM.js";
7
7
  import {
8
8
  getBackend
9
- } from "./chunk-OADHUAEU.js";
9
+ } from "./chunk-7RV7T5PX.js";
10
10
 
11
11
  // src/commands/mem/sql.ts
12
12
  async function memSqlCommand(sql) {
@@ -5,13 +5,13 @@ import {
5
5
  getMemoryConfig,
6
6
  getMemoryConfigOrDefault,
7
7
  updateMemoryConfig
8
- } from "./chunk-TXTRXV74.js";
8
+ } from "./chunk-C7DWZ7B5.js";
9
9
  import {
10
10
  getOpenAiApiKey
11
- } from "./chunk-K6MWE2ZH.js";
11
+ } from "./chunk-SO323PZP.js";
12
12
 
13
13
  // src/lib/memory/schema.ts
14
- var SCHEMA_VERSION = "2.1.0";
14
+ var SCHEMA_VERSION = "2.2.0";
15
15
  var EXTENSIONS_SQL = ``;
16
16
  var VECTOR_EXTENSION_SQL = `
17
17
  CREATE EXTENSION IF NOT EXISTS vector;
@@ -105,19 +105,31 @@ BEGIN
105
105
  END $$;
106
106
  `;
107
107
  var FUNCTIONS_SQL = `
108
- -- Enforce global uniqueness of the "keys" array across records.
108
+ -- Enforce uniqueness of the "keys" array across ACTIVE records only.
109
+ --
110
+ -- Scoping to active is deliberate: an archived record must not squat on a
111
+ -- key forever. If it did, re-adding the same identity (e.g. a person who
112
+ -- was archived and re-surfaced) would hard-fail with 23505 and the key
113
+ -- could never be reclaimed. So:
114
+ -- - Only ACTIVE incoming rows are checked (an archived NEW row can hold
115
+ -- any keys, including ones a live record also holds \u2014 provenance).
116
+ -- - Conflicts are only raised against other ACTIVE records.
117
+ -- Array-overlap uniqueness can't be expressed as a partial UNIQUE index
118
+ -- (btree can't index "arrays overlap"), so the invariant lives here in a
119
+ -- trigger. find-by-source, key lookups, and mem_upsert_by_keys all
120
+ -- prefer active rows to match this scoping (see below).
109
121
  CREATE OR REPLACE FUNCTION mem_enforce_key_uniqueness()
110
122
  RETURNS TRIGGER LANGUAGE plpgsql AS $$
111
123
  DECLARE
112
124
  conflicting_id UUID;
113
125
  BEGIN
114
- IF NEW.keys IS NULL THEN RETURN NEW; END IF;
126
+ IF NEW.keys IS NULL OR NEW.status <> 'active' THEN RETURN NEW; END IF;
115
127
  SELECT id INTO conflicting_id
116
128
  FROM mem_records
117
- WHERE keys && NEW.keys AND id != NEW.id
129
+ WHERE keys && NEW.keys AND id != NEW.id AND status = 'active'
118
130
  LIMIT 1;
119
131
  IF conflicting_id IS NOT NULL THEN
120
- RAISE EXCEPTION 'Key conflict: one or more keys in % already exist on record %',
132
+ RAISE EXCEPTION 'Key conflict: one or more keys in % already exist on active record %',
121
133
  NEW.keys, conflicting_id USING ERRCODE = 'unique_violation';
122
134
  END IF;
123
135
  RETURN NEW;
@@ -224,14 +236,15 @@ DECLARE
224
236
  BEGIN
225
237
  -- Deterministic pick when more than one row's keys[] overlap p_keys
226
238
  -- (e.g. the incoming row identity-merges with an old Attio-id row AND
227
- -- a newer email-keyed row from a separate sync). Newest-updated wins;
228
- -- ties broken by id. Without ORDER BY, PG returns whichever row the
229
- -- planner happens to surface first, leaving the loser persisted with
230
- -- overlapping keys and breaking the keys-are-unique invariant.
239
+ -- a newer email-keyed row from a separate sync). Active rows win over
240
+ -- archived (key uniqueness is scoped to active, so an archived dupe may
241
+ -- coexist \u2014 never merge into it when a live owner exists); then newest-
242
+ -- updated; ties broken by id. Without ORDER BY, PG returns whichever
243
+ -- row the planner surfaces first, breaking the keys-are-unique invariant.
231
244
  SELECT r.id INTO existing_id
232
245
  FROM mem_records r
233
246
  WHERE r.keys && p_keys
234
- ORDER BY r.updated_at DESC NULLS LAST, r.id ASC
247
+ ORDER BY (r.status = 'active') DESC, r.updated_at DESC NULLS LAST, r.id ASC
235
248
  LIMIT 1;
236
249
 
237
250
  IF existing_id IS NOT NULL THEN
@@ -308,12 +321,12 @@ DECLARE
308
321
  BEGIN
309
322
  v_embedding := CASE WHEN p_embedding IS NOT NULL THEN p_embedding::vector ELSE NULL END;
310
323
 
311
- -- See no-vector variant for ORDER BY rationale (deterministic pick
312
- -- when multiple rows' keys overlap p_keys).
324
+ -- See no-vector variant for ORDER BY rationale (active-preferred,
325
+ -- deterministic pick when multiple rows' keys overlap p_keys).
313
326
  SELECT r.id INTO existing_id
314
327
  FROM mem_records r
315
328
  WHERE r.keys && p_keys
316
- ORDER BY r.updated_at DESC NULLS LAST, r.id ASC
329
+ ORDER BY (r.status = 'active') DESC, r.updated_at DESC NULLS LAST, r.id ASC
317
330
  LIMIT 1;
318
331
 
319
332
  IF existing_id IS NOT NULL THEN
@@ -478,6 +491,13 @@ function hotColumnIndexName(type, jsonPath) {
478
491
 
479
492
  // src/lib/memory/plugins/postgres-core/backend.ts
480
493
  var SCHEMA_LOCK_ID = 7193095520723763200n.toString();
494
+ function isUniqueViolation(err) {
495
+ if (!err || typeof err !== "object") return false;
496
+ const e = err;
497
+ if (e.code === "23505") return true;
498
+ const msg = typeof e.message === "string" ? e.message : "";
499
+ return /23505|unique_violation|Key conflict/i.test(msg);
500
+ }
481
501
  function toRecord(row) {
482
502
  return {
483
503
  id: row.id,
@@ -512,6 +532,10 @@ var CoreBackend = class {
512
532
  }
513
533
  async ensureSchema() {
514
534
  const caps = this.caps;
535
+ try {
536
+ if (await this.getSchemaVersion() === SCHEMA_VERSION) return;
537
+ } catch {
538
+ }
515
539
  await this.client.transaction(async (tx) => {
516
540
  try {
517
541
  await tx.query(`SELECT pg_advisory_xact_lock(${SCHEMA_LOCK_ID}::bigint)`);
@@ -623,6 +647,9 @@ var CoreBackend = class {
623
647
  const searchable = patch.searchable_text ?? existing.searchable_text ?? null;
624
648
  const hash = patch.content_hash ?? existing.content_hash ?? null;
625
649
  const weight = patch.weight ?? existing.weight;
650
+ const textChanged = searchable !== (existing.searchable_text ?? null);
651
+ const clearEmbedding = textChanged && this.caps.vectorSearch;
652
+ const embeddingReset = clearEmbedding ? `, embedding = NULL, embedded_at = NULL, embedding_model = NULL` : "";
626
653
  const res = await this.client.query(
627
654
  `UPDATE mem_records
628
655
  SET data = $2::jsonb,
@@ -631,13 +658,46 @@ var CoreBackend = class {
631
658
  sources = $5::jsonb,
632
659
  searchable_text = $6,
633
660
  content_hash = $7,
634
- weight = $8
661
+ weight = $8${embeddingReset}
635
662
  WHERE id = $1
636
663
  RETURNING *`,
637
664
  [id, JSON.stringify(data), tags, keys, JSON.stringify(sources), searchable, hash, weight]
638
665
  );
639
666
  return res.rows[0] ? toRecord(res.rows[0]) : null;
640
667
  }
668
+ async updateKeys(id, keys) {
669
+ try {
670
+ return await this.client.transaction(async (tx) => {
671
+ const exists = await tx.query(
672
+ `SELECT id FROM mem_records WHERE id = $1`,
673
+ [id]
674
+ );
675
+ if (!exists.rows[0]) return { status: "not_found" };
676
+ const conflict = await tx.query(
677
+ `SELECT r.id, r.type,
678
+ (SELECT k FROM unnest($2::text[]) AS k WHERE k = ANY(r.keys) LIMIT 1) AS key
679
+ FROM mem_records r
680
+ WHERE r.keys && $2::text[] AND r.id != $1 AND r.status = 'active'
681
+ LIMIT 1`,
682
+ [id, keys]
683
+ );
684
+ const c = conflict.rows[0];
685
+ if (c) {
686
+ return { status: "conflict", key: c.key, recordId: c.id, recordType: c.type };
687
+ }
688
+ const res = await tx.query(
689
+ `UPDATE mem_records SET keys = $2::text[] WHERE id = $1 RETURNING *`,
690
+ [id, keys.length > 0 ? keys : null]
691
+ );
692
+ return { status: "ok", record: toRecord(res.rows[0]) };
693
+ });
694
+ } catch (err) {
695
+ if (isUniqueViolation(err)) {
696
+ return { status: "conflict", key: keys[0] ?? "", recordId: "unknown", recordType: "unknown" };
697
+ }
698
+ throw err;
699
+ }
700
+ }
641
701
  async remove(id) {
642
702
  const res = await this.client.query(`DELETE FROM mem_records WHERE id = $1`, [id]);
643
703
  return (res.rowCount ?? 0) > 0;
@@ -651,12 +711,30 @@ var CoreBackend = class {
651
711
  return (res.rowCount ?? 0) > 0;
652
712
  }
653
713
  async unarchive(id) {
654
- const res = await this.client.query(
655
- `UPDATE mem_records SET status = 'active', archived_reason = NULL
656
- WHERE id = $1 AND status = 'archived'`,
657
- [id]
658
- );
659
- return (res.rowCount ?? 0) > 0;
714
+ try {
715
+ const res = await this.client.query(
716
+ `UPDATE mem_records SET status = 'active', archived_reason = NULL
717
+ WHERE id = $1 AND status = 'archived'`,
718
+ [id]
719
+ );
720
+ return (res.rowCount ?? 0) > 0;
721
+ } catch (err) {
722
+ if (!isUniqueViolation(err)) throw err;
723
+ const owner = await this.client.query(
724
+ `SELECT r.id, r.type,
725
+ (SELECT k FROM unnest(a.keys) AS k
726
+ WHERE k = ANY(r.keys) LIMIT 1) AS key
727
+ FROM mem_records a
728
+ JOIN mem_records r
729
+ ON r.keys && a.keys AND r.id != a.id AND r.status = 'active'
730
+ WHERE a.id = $1
731
+ LIMIT 1`,
732
+ [id]
733
+ );
734
+ const o = owner.rows[0];
735
+ const detail = o ? `key "${o.key}" was reclaimed by active record ${o.id} (type ${o.type}) \u2014 re-key it or archive that record first` : `one of its keys was reclaimed by another active record`;
736
+ throw new Error(`Cannot unarchive ${id}: ${detail}`);
737
+ }
660
738
  }
661
739
  async list(type, opts = {}) {
662
740
  const limit = opts.limit ?? 100;
@@ -733,6 +811,48 @@ var CoreBackend = class {
733
811
  [id, literal, model]
734
812
  );
735
813
  }
814
+ async listForSearchableBackfill(opts = {}) {
815
+ const limit = opts.limit ?? 1e3;
816
+ const offset = opts.offset ?? 0;
817
+ const params = [];
818
+ const clauses = [`status = 'active'`];
819
+ if (opts.type) {
820
+ params.push(opts.type);
821
+ clauses.push(`type = $${params.length}`);
822
+ }
823
+ if (opts.onlyNull) {
824
+ clauses.push(`(searchable_text IS NULL OR searchable_text = '')`);
825
+ }
826
+ params.push(limit);
827
+ params.push(offset);
828
+ const res = await this.client.query(
829
+ `SELECT id, type, data, searchable_text
830
+ FROM mem_records
831
+ WHERE ${clauses.join(" AND ")}
832
+ ORDER BY id ASC
833
+ LIMIT $${params.length - 1} OFFSET $${params.length}`,
834
+ params
835
+ );
836
+ return res.rows;
837
+ }
838
+ async updateSearchableText(id, text) {
839
+ if (this.caps.vectorSearch) {
840
+ await this.client.query(
841
+ `UPDATE mem_records
842
+ SET searchable_text = $2,
843
+ embedding = NULL,
844
+ embedded_at = NULL,
845
+ embedding_model = NULL
846
+ WHERE id = $1`,
847
+ [id, text]
848
+ );
849
+ return;
850
+ }
851
+ await this.client.query(
852
+ `UPDATE mem_records SET searchable_text = $2 WHERE id = $1`,
853
+ [id, text]
854
+ );
855
+ }
736
856
  async listKeysByType(type) {
737
857
  const res = await this.client.query(
738
858
  `SELECT id, keys FROM mem_records WHERE type = $1 AND status = 'active'`,
@@ -871,7 +991,10 @@ var CoreBackend = class {
871
991
  }
872
992
  async findBySource(sourceKey) {
873
993
  const res = await this.client.query(
874
- `SELECT * FROM mem_records WHERE $1 = ANY(keys) LIMIT 1`,
994
+ `SELECT * FROM mem_records
995
+ WHERE $1 = ANY(keys)
996
+ ORDER BY (status = 'active') DESC, updated_at DESC
997
+ LIMIT 1`,
875
998
  [sourceKey]
876
999
  );
877
1000
  return res.rows[0] ? toRecord(res.rows[0]) : null;
@@ -1105,6 +1228,9 @@ var LazyPostgresBackend = class {
1105
1228
  async update(...a) {
1106
1229
  return (await this.ensure()).update(...a);
1107
1230
  }
1231
+ async updateKeys(...a) {
1232
+ return (await this.ensure()).updateKeys(...a);
1233
+ }
1108
1234
  async remove(...a) {
1109
1235
  return (await this.ensure()).remove(...a);
1110
1236
  }
@@ -1126,9 +1252,15 @@ var LazyPostgresBackend = class {
1126
1252
  async listKeysByType(...a) {
1127
1253
  return (await this.ensure()).listKeysByType(...a);
1128
1254
  }
1255
+ async listForSearchableBackfill(...a) {
1256
+ return (await this.ensure()).listForSearchableBackfill(...a);
1257
+ }
1129
1258
  async updateEmbedding(...a) {
1130
1259
  return (await this.ensure()).updateEmbedding(...a);
1131
1260
  }
1261
+ async updateSearchableText(...a) {
1262
+ return (await this.ensure()).updateSearchableText(...a);
1263
+ }
1132
1264
  async raw(sql, params) {
1133
1265
  const b = await this.ensure();
1134
1266
  if (!b.raw) throw new Error("Backend does not support raw SQL");
@@ -1451,6 +1583,9 @@ var LazyEmbeddedPostgresBackend = class {
1451
1583
  async update(...a) {
1452
1584
  return (await this.ensure()).update(...a);
1453
1585
  }
1586
+ async updateKeys(...a) {
1587
+ return (await this.ensure()).updateKeys(...a);
1588
+ }
1454
1589
  async remove(...a) {
1455
1590
  return (await this.ensure()).remove(...a);
1456
1591
  }
@@ -1472,9 +1607,15 @@ var LazyEmbeddedPostgresBackend = class {
1472
1607
  async listKeysByType(...a) {
1473
1608
  return (await this.ensure()).listKeysByType(...a);
1474
1609
  }
1610
+ async listForSearchableBackfill(...a) {
1611
+ return (await this.ensure()).listForSearchableBackfill(...a);
1612
+ }
1475
1613
  async updateEmbedding(...a) {
1476
1614
  return (await this.ensure()).updateEmbedding(...a);
1477
1615
  }
1616
+ async updateSearchableText(...a) {
1617
+ return (await this.ensure()).updateSearchableText(...a);
1618
+ }
1478
1619
  async raw(sql, params) {
1479
1620
  const b = await this.ensure();
1480
1621
  if (!b.raw) throw new Error("Backend does not support raw SQL");
@@ -1671,6 +1812,21 @@ async function addRecord(input, opts = {}) {
1671
1812
  };
1672
1813
  return backend.insert(prepared);
1673
1814
  }
1815
+ async function updateRecord(id, patch) {
1816
+ const backend = await getBackend();
1817
+ const existing = await backend.getById(id);
1818
+ if (!existing) return null;
1819
+ const mergedData = patch.data ? { ...existing.data, ...patch.data } : existing.data;
1820
+ const dataChanged = patch.data !== void 0;
1821
+ const searchable_text = patch.searchable_text ?? (dataChanged ? defaultSearchableText(mergedData) : existing.searchable_text ?? void 0);
1822
+ const content_hash = patch.content_hash ?? (dataChanged ? contentHash(mergedData) : existing.content_hash ?? void 0);
1823
+ return backend.update(id, {
1824
+ ...patch,
1825
+ data: mergedData,
1826
+ searchable_text,
1827
+ content_hash
1828
+ });
1829
+ }
1674
1830
  async function upsertRecord(input, opts = {}) {
1675
1831
  const backend = await getBackend();
1676
1832
  const { searchable_text, content_hash, embedding, embedding_model } = await prepareRecord(input, opts, "sync");
@@ -1706,5 +1862,6 @@ export {
1706
1862
  resetBackendSingleton,
1707
1863
  closeBackendIfCached,
1708
1864
  addRecord,
1865
+ updateRecord,
1709
1866
  upsertRecord
1710
1867
  };
@@ -3,7 +3,7 @@ import {
3
3
  readConfig,
4
4
  setOpenAiApiKey,
5
5
  writeConfig
6
- } from "./chunk-K6MWE2ZH.js";
6
+ } from "./chunk-SO323PZP.js";
7
7
 
8
8
  // src/lib/memory/config.ts
9
9
  var DEFAULT_MEMORY_CONFIG = {
@@ -172,29 +172,78 @@ async function embedBatch(texts, opts = {}) {
172
172
  function sleep(ms) {
173
173
  return new Promise((resolve) => setTimeout(resolve, ms));
174
174
  }
175
+ var UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
176
+ var TIMESTAMP_RE = /^\d{4}-\d{2}-\d{2}([T ]\d{2}:\d{2}(:\d{2})?(\.\d+)?(Z|[+-]\d{2}:?\d{2})?)?$/;
177
+ var NOISE_KEY_RE = /(^|_)(id|ids|uuid|guid|url|href|link|avatar|photo|icon|hash|token|slug)$/i;
178
+ var NOISE_KEY_EXACT = /* @__PURE__ */ new Set([
179
+ "attribute_type",
180
+ "actor_type",
181
+ "created_by_actor",
182
+ "active_from",
183
+ "active_until",
184
+ "created_at",
185
+ "updated_at",
186
+ "last_synced_at"
187
+ ]);
188
+ var CONTAINER_KEYS = /* @__PURE__ */ new Set([
189
+ "values",
190
+ "value",
191
+ "data",
192
+ "attributes",
193
+ "properties",
194
+ "items",
195
+ "records",
196
+ "result",
197
+ "results",
198
+ "fields"
199
+ ]);
200
+ var PRIORITY_KEY_RE = /(^|_)(name|full_name|first_name|last_name|display_name|title|job_title|role|position|email|email_address|company|organization|org|description|bio|summary|headline|label|subject|content|body|text_content)$/i;
175
201
  function defaultSearchableText(data, maxLen = 4e3) {
176
- const parts = [];
177
- const walk = (value, depth = 0) => {
202
+ const priority = [];
203
+ const normal = [];
204
+ const push = (key, text) => {
205
+ if (key && PRIORITY_KEY_RE.test(key)) priority.push(text);
206
+ else normal.push(text);
207
+ };
208
+ const walk = (value, key, depth) => {
178
209
  if (value === null || value === void 0) return;
179
- if (typeof value === "string" && value.trim()) {
180
- parts.push(value.trim());
210
+ if (typeof value === "string") {
211
+ const t = value.trim();
212
+ if (!t) return;
213
+ if (UUID_RE.test(t) || TIMESTAMP_RE.test(t)) return;
214
+ if (key && NOISE_KEY_RE.test(key)) return;
215
+ push(key, t);
181
216
  return;
182
217
  }
183
218
  if (typeof value === "number" || typeof value === "boolean") {
184
- parts.push(String(value));
219
+ if (key && NOISE_KEY_RE.test(key)) return;
220
+ push(key, String(value));
185
221
  return;
186
222
  }
187
- if (depth > 4) return;
223
+ if (depth > 6) return;
188
224
  if (Array.isArray(value)) {
189
- for (const v of value) walk(v, depth + 1);
225
+ for (const v of value) walk(v, key, depth + 1);
190
226
  return;
191
227
  }
192
228
  if (typeof value === "object") {
193
- for (const v of Object.values(value)) walk(v, depth + 1);
229
+ for (const [k, v] of Object.entries(value)) {
230
+ const lower = k.toLowerCase();
231
+ if (NOISE_KEY_EXACT.has(lower) || NOISE_KEY_RE.test(k)) continue;
232
+ const nextKey = CONTAINER_KEYS.has(lower) ? key : k;
233
+ walk(v, nextKey, depth + 1);
234
+ }
194
235
  }
195
236
  };
196
- walk(data);
197
- const joined = parts.join(" ").replace(/\s+/g, " ").trim();
237
+ walk(data, void 0, 0);
238
+ const seen = /* @__PURE__ */ new Set();
239
+ const ordered = [];
240
+ for (const t of [...priority, ...normal]) {
241
+ const norm = t.toLowerCase();
242
+ if (seen.has(norm)) continue;
243
+ seen.add(norm);
244
+ ordered.push(t);
245
+ }
246
+ const joined = ordered.join(" ").replace(/\s+/g, " ").trim();
198
247
  return joined.length > maxLen ? joined.slice(0, maxLen) : joined;
199
248
  }
200
249
 
@@ -1,10 +1,10 @@
1
1
  import {
2
2
  getMemoryConfigOrDefault
3
- } from "./chunk-TXTRXV74.js";
3
+ } from "./chunk-C7DWZ7B5.js";
4
4
  import {
5
5
  getOpenAiApiKey,
6
6
  readConfig
7
- } from "./chunk-K6MWE2ZH.js";
7
+ } from "./chunk-SO323PZP.js";
8
8
 
9
9
  // src/lib/output.ts
10
10
  import * as p from "@clack/prompts";
@@ -6,11 +6,11 @@ import {
6
6
  note,
7
7
  okJson,
8
8
  requireMemoryInit
9
- } from "./chunk-HS5AHQ4V.js";
9
+ } from "./chunk-CLJFSFFM.js";
10
10
  import {
11
11
  getBackend,
12
12
  upsertRecord
13
- } from "./chunk-OADHUAEU.js";
13
+ } from "./chunk-7RV7T5PX.js";
14
14
 
15
15
  // src/commands/mem/migrate.ts
16
16
  import fs4 from "fs";
@@ -318,13 +318,6 @@ function appendUsageLog(line) {
318
318
  } catch {
319
319
  }
320
320
  }
321
- function readUsageLog() {
322
- try {
323
- return fs.readFileSync(usageLogFile(), "utf-8").split("\n").filter(Boolean).slice(-USAGE_LOG_MAX);
324
- } catch {
325
- return [];
326
- }
327
- }
328
321
  function writeUsageLog(lines) {
329
322
  try {
330
323
  if (lines.length === 0) {
@@ -337,6 +330,25 @@ function writeUsageLog(lines) {
337
330
  } catch {
338
331
  }
339
332
  }
333
+ function claimUsageLog() {
334
+ const src = usageLogFile();
335
+ const tmp = `${src}.claim.${process.pid}.${randomUUID()}`;
336
+ try {
337
+ fs.renameSync(src, tmp);
338
+ } catch {
339
+ return null;
340
+ }
341
+ try {
342
+ return fs.readFileSync(tmp, "utf-8").split("\n").filter(Boolean).slice(-USAGE_LOG_MAX);
343
+ } catch {
344
+ return [];
345
+ } finally {
346
+ try {
347
+ fs.rmSync(tmp, { force: true });
348
+ } catch {
349
+ }
350
+ }
351
+ }
340
352
  function readUsageState() {
341
353
  try {
342
354
  return JSON.parse(fs.readFileSync(usageStateFile(), "utf-8"));
@@ -384,8 +396,8 @@ export {
384
396
  readAnalyticsQueue,
385
397
  writeAnalyticsQueue,
386
398
  appendUsageLog,
387
- readUsageLog,
388
399
  writeUsageLog,
400
+ claimUsageLog,
389
401
  readUsageState,
390
402
  writeUsageState
391
403
  };
@@ -2,8 +2,8 @@ import {
2
2
  defaultSearchableText,
3
3
  embed,
4
4
  embedBatch
5
- } from "./chunk-TXTRXV74.js";
6
- import "./chunk-K6MWE2ZH.js";
5
+ } from "./chunk-C7DWZ7B5.js";
6
+ import "./chunk-SO323PZP.js";
7
7
  export {
8
8
  defaultSearchableText,
9
9
  embed,
@@ -12,9 +12,9 @@ import {
12
12
  stripStepsAlias,
13
13
  summarizeFlowInputs,
14
14
  walkSteps
15
- } from "./chunk-VW7J2RQW.js";
15
+ } from "./chunk-2TWFL3CS.js";
16
16
  import "./chunk-44CV5IMX.js";
17
- import "./chunk-K6MWE2ZH.js";
17
+ import "./chunk-SO323PZP.js";
18
18
  export {
19
19
  FlowRunner,
20
20
  collectStepTypes,
package/dist/index.js CHANGED
@@ -31,10 +31,10 @@ import {
31
31
  validateActionInput,
32
32
  walkSteps,
33
33
  writeCache
34
- } from "./chunk-VW7J2RQW.js";
34
+ } from "./chunk-2TWFL3CS.js";
35
35
  import {
36
36
  memSqlCommand
37
- } from "./chunk-QV3Y5N5G.js";
37
+ } from "./chunk-4WWK3GO5.js";
38
38
  import {
39
39
  countRecords,
40
40
  deleteDatabase,
@@ -59,7 +59,7 @@ import {
59
59
  upsertRecords,
60
60
  writeDraftProfile,
61
61
  writeProfile
62
- } from "./chunk-PLMFRFTT.js";
62
+ } from "./chunk-GXOIR3GF.js";
63
63
  import {
64
64
  getByDotPath
65
65
  } from "./chunk-44CV5IMX.js";
@@ -83,7 +83,7 @@ import {
83
83
  semanticSearchUpgradeLine,
84
84
  setAgentMode,
85
85
  silenceWarningsInAgentMode
86
- } from "./chunk-HS5AHQ4V.js";
86
+ } from "./chunk-CLJFSFFM.js";
87
87
  import {
88
88
  SCHEMA_VERSION,
89
89
  addRecord,
@@ -92,8 +92,9 @@ import {
92
92
  getBackendPlugin,
93
93
  listBackendPlugins,
94
94
  loadBackendFromConfig,
95
+ updateRecord,
95
96
  upsertRecord
96
- } from "./chunk-OADHUAEU.js";
97
+ } from "./chunk-7RV7T5PX.js";
97
98
  import {
98
99
  DEFAULT_MEMORY_CONFIG,
99
100
  defaultSearchableText,
@@ -103,10 +104,11 @@ import {
103
104
  memoryConfigExists,
104
105
  setOpenAiApiKey,
105
106
  updateMemoryConfig
106
- } from "./chunk-TXTRXV74.js";
107
+ } from "./chunk-C7DWZ7B5.js";
107
108
  import {
108
109
  appendAnalyticsQueue,
109
110
  appendUsageLog,
111
+ claimUsageLog,
110
112
  configExists,
111
113
  ensureWhoAmI,
112
114
  getAccessControl,
@@ -127,7 +129,6 @@ import {
127
129
  readConfig,
128
130
  readGlobalConfig,
129
131
  readProjectConfig,
130
- readUsageLog,
131
132
  readUsageState,
132
133
  resolveConfig,
133
134
  telemetryNoticeShown,
@@ -138,7 +139,7 @@ import {
138
139
  writeConfig,
139
140
  writeUsageLog,
140
141
  writeUsageState
141
- } from "./chunk-K6MWE2ZH.js";
142
+ } from "./chunk-SO323PZP.js";
142
143
 
143
144
  // src/cli.ts
144
145
  import { createRequire as createRequire3 } from "module";
@@ -6062,7 +6063,7 @@ async function syncModel(api, profile, options) {
6062
6063
  updateModelState(platform, model, { status: "failed", pagesProcessed, lastCursor }),
6063
6064
  (async () => {
6064
6065
  try {
6065
- const { getBackend: getBackend2 } = await import("./runtime-B5GCQ34P.js");
6066
+ const { getBackend: getBackend2 } = await import("./runtime-2PRVL2NO.js");
6066
6067
  const backend = await getBackend2();
6067
6068
  await Promise.race([
6068
6069
  backend.close(),
@@ -6417,7 +6418,7 @@ async function syncModel(api, profile, options) {
6417
6418
  db.exec(`DROP TABLE IF EXISTS _seen_ids`);
6418
6419
  }
6419
6420
  if (options.toMemory !== false) {
6420
- const backend = await (await import("./runtime-B5GCQ34P.js")).getBackend();
6421
+ const backend = await (await import("./runtime-2PRVL2NO.js")).getBackend();
6421
6422
  const type = `${platform}/${model}`;
6422
6423
  const existing = await backend.listKeysByType(type);
6423
6424
  const sourcePrefix = `${type}:`;
@@ -6497,7 +6498,7 @@ async function syncModel(api, profile, options) {
6497
6498
  let statusCounts;
6498
6499
  if (options.toMemory !== false) {
6499
6500
  try {
6500
- const backend = await (await import("./runtime-B5GCQ34P.js")).getBackend();
6501
+ const backend = await (await import("./runtime-2PRVL2NO.js")).getBackend();
6501
6502
  const typeName = `${platform}/${model}`;
6502
6503
  const [active, archived] = await Promise.all([
6503
6504
  backend.count(typeName, { status: "active" }),
@@ -8130,7 +8131,7 @@ ${result.total} results`);
8130
8131
  }
8131
8132
  }
8132
8133
  async function syncSqlCommand(platformModel, sql) {
8133
- const { syncSqlCommand: runSyncSql } = await import("./sql-W3ZUOUNR.js");
8134
+ const { syncSqlCommand: runSyncSql } = await import("./sql-HG7R2JML.js");
8134
8135
  await runSyncSql(platformModel, sql);
8135
8136
  }
8136
8137
  async function syncDeleteCommand(platformModel, options) {
@@ -8208,7 +8209,7 @@ async function syncDeleteCommand(platformModel, options) {
8208
8209
  async function maybeAutoMigrateLegacy(platform, models) {
8209
8210
  const dbSize = getDatabaseSize(platform);
8210
8211
  if (!dbSize || dbSize === "0 B") return;
8211
- const { getBackend: getBackend2 } = await import("./runtime-B5GCQ34P.js");
8212
+ const { getBackend: getBackend2 } = await import("./runtime-2PRVL2NO.js");
8212
8213
  const backend = await getBackend2();
8213
8214
  let memoryHasData = false;
8214
8215
  for (const model of models) {
@@ -8224,7 +8225,7 @@ async function maybeAutoMigrateLegacy(platform, models) {
8224
8225
  ` detected legacy .one/sync/data/${platform}.db (${dbSize}) \u2014 auto-migrating into memory before sync.
8225
8226
  `
8226
8227
  );
8227
- const { memMigrateCommand: memMigrateCommand3 } = await import("./migrate-UDEANXEZ.js");
8228
+ const { memMigrateCommand: memMigrateCommand3 } = await import("./migrate-TRUZVAT5.js");
8228
8229
  await memMigrateCommand3({ platform, yes: true });
8229
8230
  return;
8230
8231
  }
@@ -8233,7 +8234,7 @@ async function maybeAutoMigrateLegacy(platform, models) {
8233
8234
  initialValue: true
8234
8235
  });
8235
8236
  if (p7.isCancel(shouldMigrate) || !shouldMigrate) return;
8236
- const { memMigrateCommand: memMigrateCommand2 } = await import("./migrate-UDEANXEZ.js");
8237
+ const { memMigrateCommand: memMigrateCommand2 } = await import("./migrate-TRUZVAT5.js");
8237
8238
  await memMigrateCommand2({ platform, yes: true });
8238
8239
  }
8239
8240
  async function syncSuggestSearchableCommand(platformModel, options = {}) {
@@ -8294,7 +8295,7 @@ async function syncSuggestSearchableCommand(platformModel, options = {}) {
8294
8295
  async function syncListCommand(platform) {
8295
8296
  const profiles = listProfiles(platform);
8296
8297
  const state = await readSyncState();
8297
- const { getBackend: getBackend2 } = await import("./runtime-B5GCQ34P.js");
8298
+ const { getBackend: getBackend2 } = await import("./runtime-2PRVL2NO.js");
8298
8299
  const backend = await getBackend2();
8299
8300
  const syncs = await Promise.all(profiles.map(async (p10) => {
8300
8301
  const modelState = state[p10.platform]?.[p10.model];
@@ -8569,7 +8570,7 @@ function registerSyncSubcommands(sync) {
8569
8570
  await syncSqlCommand(platformModel, sql);
8570
8571
  });
8571
8572
  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) => {
8572
- const { syncSchemaCommand } = await import("./schema-DXHEU47V.js");
8573
+ const { syncSchemaCommand } = await import("./schema-LCRWSFD5.js");
8573
8574
  await syncSchemaCommand(platformModel);
8574
8575
  });
8575
8576
  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) => {
@@ -9015,11 +9016,41 @@ async function memGetCommand(id, flags) {
9015
9016
  async function memUpdateCommand(id, patchRaw) {
9016
9017
  requireMemoryInit();
9017
9018
  const patch = parseJsonArg(patchRaw, "patch");
9018
- const backend = await getBackend();
9019
- const updated = await backend.update(id, { type: "", data: patch });
9019
+ if (patch && typeof patch === "object" && !Array.isArray(patch) && "keys" in patch) {
9020
+ error("`keys` is not a data field. Use `one mem key <id> --set <csv>` (or --add/--remove) to change a record's keys.");
9021
+ }
9022
+ const updated = await updateRecord(id, { data: patch });
9020
9023
  if (!updated) error(`Record ${id} not found`);
9021
9024
  printRecord(updated);
9022
9025
  }
9026
+ async function memKeyCommand(id, flags) {
9027
+ requireMemoryInit();
9028
+ const backend = await getBackend();
9029
+ const setList = parseCsv(flags.set);
9030
+ const addList = parseCsv(flags.add) ?? [];
9031
+ const removeList = parseCsv(flags.remove) ?? [];
9032
+ if (!setList && addList.length === 0 && removeList.length === 0) {
9033
+ error("Pass at least one of --add <csv>, --remove <csv>, or --set <csv>.");
9034
+ }
9035
+ const existing = await backend.getById(id);
9036
+ if (!existing) error(`Record ${id} not found`);
9037
+ let next;
9038
+ if (setList) {
9039
+ next = setList;
9040
+ } else {
9041
+ const removeSet = new Set(removeList);
9042
+ next = [...existing.keys ?? [], ...addList].filter((k) => !removeSet.has(k));
9043
+ }
9044
+ next = [...new Set(next)];
9045
+ const outcome = await backend.updateKeys(id, next);
9046
+ if (outcome.status === "not_found") error(`Record ${id} not found`);
9047
+ if (outcome.status === "conflict") {
9048
+ error(
9049
+ `Key "${outcome.key}" is already owned by active record ${outcome.recordId} (type ${outcome.recordType}). Keys must be unique across active records \u2014 archive or re-key that record first.`
9050
+ );
9051
+ }
9052
+ printRecord(outcome.record);
9053
+ }
9023
9054
  async function memArchiveCommand(id, flags) {
9024
9055
  requireMemoryInit();
9025
9056
  const backend = await getBackend();
@@ -9216,7 +9247,7 @@ async function memDoctorCommand() {
9216
9247
  }
9217
9248
  if (cfg.embedding.provider === "openai") {
9218
9249
  try {
9219
- const { embed: embed2 } = await import("./embedding-NVFDR5PK.js");
9250
+ const { embed: embed2 } = await import("./embedding-2YB6CGBA.js");
9220
9251
  const result = await embed2("connectivity check");
9221
9252
  checks.push({
9222
9253
  name: "OpenAI embedding provider reachable",
@@ -9375,6 +9406,9 @@ async function memVacuumCommand() {
9375
9406
  }
9376
9407
  async function memReindexCommand(flags) {
9377
9408
  requireMemoryInit();
9409
+ if (flags.searchable) {
9410
+ return memReindexSearchableCommand(flags);
9411
+ }
9378
9412
  const backend = await getBackend();
9379
9413
  const cfg = getMemoryConfigOrDefault();
9380
9414
  if (cfg.embedding.provider !== "openai") {
@@ -9437,6 +9471,58 @@ async function memReindexCommand(flags) {
9437
9471
  force: !!flags.force
9438
9472
  });
9439
9473
  }
9474
+ async function memReindexSearchableCommand(flags) {
9475
+ const backend = await getBackend();
9476
+ const onlyNull = !flags.all;
9477
+ const totalCap = flags.limit ? parsePositiveInt(flags.limit, 1e6, "limit") : 1e6;
9478
+ const PAGE = 500;
9479
+ let considered = 0;
9480
+ let updated = 0;
9481
+ let unchanged = 0;
9482
+ let emptied = 0;
9483
+ let offset = 0;
9484
+ while (considered < totalCap) {
9485
+ const pageSize = Math.min(PAGE, totalCap - considered);
9486
+ const rows = await backend.listForSearchableBackfill({
9487
+ type: flags.type,
9488
+ onlyNull,
9489
+ limit: pageSize,
9490
+ offset
9491
+ });
9492
+ if (rows.length === 0) break;
9493
+ considered += rows.length;
9494
+ let updatedThisPage = 0;
9495
+ for (const r of rows) {
9496
+ const next = defaultSearchableText(r.data);
9497
+ if (!next) {
9498
+ emptied++;
9499
+ continue;
9500
+ }
9501
+ if (next === (r.searchable_text ?? "")) {
9502
+ unchanged++;
9503
+ continue;
9504
+ }
9505
+ await backend.updateSearchableText(r.id, next);
9506
+ updated++;
9507
+ updatedThisPage++;
9508
+ }
9509
+ offset += onlyNull ? rows.length - updatedThisPage : rows.length;
9510
+ if (!isAgentMode()) {
9511
+ process.stderr.write(` searchable: updated ${updated} / considered ${considered}\r`);
9512
+ }
9513
+ }
9514
+ if (!isAgentMode()) process.stderr.write("\n");
9515
+ okJson({
9516
+ status: "ok",
9517
+ mode: "searchable",
9518
+ type: flags.type ?? null,
9519
+ scope: onlyNull ? "null_only" : "all",
9520
+ considered,
9521
+ updated,
9522
+ unchanged,
9523
+ emptied
9524
+ });
9525
+ }
9440
9526
 
9441
9527
  // src/commands/mem.ts
9442
9528
  function memStatusCommand() {
@@ -9475,11 +9561,12 @@ function registerMemoryCommands(program2) {
9475
9561
  );
9476
9562
  mem.command("doctor").description("Diagnose schema, indexes, plugin resolution, connectivity, embeddings").action(memDoctorCommand);
9477
9563
  mem.command("vacuum").description("Run backend maintenance (VACUUM ANALYZE on tables)").action(memVacuumCommand);
9478
- mem.command("reindex").description("Backfill embeddings: re-embed records missing an embedding or under a different model").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)").action(memReindexCommand);
9564
+ 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);
9479
9565
  mem.command("sql <query>").description("Run a read-only SELECT / WITH / EXPLAIN against the memory store").action(memSqlCommand);
9480
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);
9481
9567
  mem.command("get <id>").description("Get a record by id").option("--links", "Include outgoing and incoming links", false).action(memGetCommand);
9482
- mem.command("update <id> <patch>").description("Update a record (patch is JSON merged into data)").action(memUpdateCommand);
9568
+ 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 identity keys (unique across active records)").option("--add <csv>", "Keys to add (comma-separated, e.g. email:x@y.com)").option("--remove <csv>", "Keys to remove (comma-separated)").option("--set <csv>", "Replace all keys with this comma-separated list").action((id, flags) => memKeyCommand(id, flags));
9483
9570
  mem.command("archive <id>").description("Archive a record").option("--reason <text>", "Why it was archived (user_archived | deleted_upstream | superseded | \u2026)").action(memArchiveCommand);
9484
9571
  mem.command("weight <id> <n>").description("Set record relevance weight (1-10)").action(memWeightCommand);
9485
9572
  mem.command("flush <id>").description("Reset access count for a record").action(memFlushCommand);
@@ -10122,10 +10209,19 @@ one --agent mem add note '{"content":"..."}' --tags work,urgent --weight 8 --key
10122
10209
  # Get (optionally with links)
10123
10210
  one --agent mem get <id> --links
10124
10211
 
10125
- # Update (shallow merge into data)
10212
+ # Update (shallow merge into data; regenerates searchable_text from the merged data)
10126
10213
  one --agent mem update <id> '{"status":"done"}'
10127
10214
 
10128
- # Archive / unarchive
10215
+ # Manage identity keys AFTER creation (keys are a first-class column, NOT data).
10216
+ # Unique across ACTIVE records \u2014 a clear error names the record that owns a taken key.
10217
+ one --agent mem key <id> --add email:new@x.com
10218
+ one --agent mem key <id> --remove email:old@x.com
10219
+ one --agent mem key <id> --set 'email:a@x.com,phone:+1555' # replace all keys
10220
+ # NOTE: passing keys to 'mem update' is rejected \u2014 keys are not a data field.
10221
+
10222
+ # Archive / unarchive. Archiving FREES a record's keys: an archived record no
10223
+ # longer blocks a new active record from reusing the same key (uniqueness is
10224
+ # scoped to active records).
10129
10225
  one --agent mem archive <id> --reason superseded
10130
10226
 
10131
10227
  # List by type \u2014 type is positional. Synced rows are namespaced as <platform>/<model>.
@@ -10191,8 +10287,12 @@ one --agent mem status # backend, provider, _upgrade hint
10191
10287
  one --agent mem doctor # 7-check health report
10192
10288
  one --agent mem vacuum # backend maintenance (VACUUM ANALYZE)
10193
10289
  one --agent mem reindex # re-embed records under current model
10290
+ one --agent mem reindex --searchable # backfill searchable_text where NULL
10291
+ one --agent mem reindex --searchable --type attio/attioPeople --all # also rewrite stale text
10194
10292
  \`\`\`
10195
10293
 
10294
+ \`mem reindex --searchable\` regenerates \`searchable_text\` from each record's own \`data\` (no embedding provider needed). It fixes rows that landed with NULL searchable_text and, thanks to the cleaned-up text builder, drops UUID/timestamp noise and leads with name/title/email fields so FTS ranks on what humans search. Regenerated rows are re-queued for embedding on the next \`mem reindex\`.
10295
+
10196
10296
  ## Admin
10197
10297
 
10198
10298
  \`\`\`bash
@@ -11086,7 +11186,7 @@ async function logoutCommand() {
11086
11186
 
11087
11187
  // src/lib/analytics.ts
11088
11188
  import { createRequire as createRequire2 } from "module";
11089
- import { randomUUID } from "crypto";
11189
+ import { randomUUID, createHash } from "crypto";
11090
11190
  import pc13 from "picocolors";
11091
11191
  var require3 = createRequire2(import.meta.url);
11092
11192
  var DEFAULT_POSTHOG_HOST = "https://us.i.posthog.com";
@@ -11129,6 +11229,9 @@ function isTelemetryDisabled() {
11129
11229
  function distinctId() {
11130
11230
  return getWhoAmI()?.user?.id ?? getDeviceId();
11131
11231
  }
11232
+ function isAuthenticated() {
11233
+ return !!getWhoAmI()?.user || !!getApiKey();
11234
+ }
11132
11235
  function baseProperties() {
11133
11236
  return {
11134
11237
  $lib: "one-cli",
@@ -11137,7 +11240,8 @@ function baseProperties() {
11137
11240
  env: envName(),
11138
11241
  os: process.platform,
11139
11242
  arch: process.arch,
11140
- node_version: process.versions.node
11243
+ node_version: process.versions.node,
11244
+ authenticated: isAuthenticated()
11141
11245
  };
11142
11246
  }
11143
11247
  function personSet() {
@@ -11182,7 +11286,8 @@ function capture(event, properties = {}, opts = {}) {
11182
11286
  return;
11183
11287
  }
11184
11288
  const did = opts.distinctId ?? distinctId();
11185
- const props = { ...baseProperties(), ...properties, $insert_id: randomUUID() };
11289
+ const props = { ...baseProperties(), ...properties };
11290
+ if (props.$insert_id === void 0) props.$insert_id = randomUUID();
11186
11291
  if (did === distinctId()) {
11187
11292
  const set = personSet();
11188
11293
  if (set) props.$set = set;
@@ -11200,13 +11305,30 @@ var ROLLUP_MAX_BATCH = 500;
11200
11305
  function utcDay(ts) {
11201
11306
  return new Date(ts).toISOString().slice(0, 10);
11202
11307
  }
11308
+ var PRE_AUTH_COMMANDS = /* @__PURE__ */ new Set([
11309
+ "init",
11310
+ "login",
11311
+ "logout",
11312
+ "guide",
11313
+ "platforms",
11314
+ "onboard",
11315
+ "config",
11316
+ "update",
11317
+ "help"
11318
+ ]);
11319
+ function shouldRecord(commandPath2) {
11320
+ if (isAuthenticated()) return true;
11321
+ return PRE_AUTH_COMMANDS.has(commandPath2.split(" ")[0]);
11322
+ }
11203
11323
  function recordCommand(command) {
11204
11324
  if (isTelemetryDisabled()) {
11205
11325
  writeUsageLog([]);
11206
11326
  return;
11207
11327
  }
11328
+ const cmdPath = commandPath(command);
11329
+ if (!shouldRecord(cmdPath)) return;
11208
11330
  const did = distinctId();
11209
- const entry = { ts: Date.now(), command: commandPath(command), agent: isAgentMode(), did };
11331
+ const entry = { ts: Date.now(), command: cmdPath, agent: isAgentMode(), did };
11210
11332
  appendUsageLog(JSON.stringify(entry));
11211
11333
  const today = utcDay(entry.ts);
11212
11334
  const state = readUsageState();
@@ -11219,8 +11341,10 @@ function flushUsageRollups(opts = {}) {
11219
11341
  writeUsageLog([]);
11220
11342
  return;
11221
11343
  }
11344
+ const lines = claimUsageLog();
11345
+ if (!lines || lines.length === 0) return;
11222
11346
  const entries = [];
11223
- for (const line of readUsageLog()) {
11347
+ for (const line of lines) {
11224
11348
  try {
11225
11349
  const e = JSON.parse(line);
11226
11350
  if (e && typeof e.ts === "number" && typeof e.command === "string" && typeof e.did === "string") {
@@ -11229,10 +11353,7 @@ function flushUsageRollups(opts = {}) {
11229
11353
  } catch {
11230
11354
  }
11231
11355
  }
11232
- if (entries.length === 0) {
11233
- writeUsageLog([]);
11234
- return;
11235
- }
11356
+ if (entries.length === 0) return;
11236
11357
  const currentDid = entries[entries.length - 1].did;
11237
11358
  const now = Date.now();
11238
11359
  const groups = /* @__PURE__ */ new Map();
@@ -11248,7 +11369,7 @@ function flushUsageRollups(opts = {}) {
11248
11369
  if (due) emitRollup(did, group);
11249
11370
  else kept.push(...group);
11250
11371
  }
11251
- writeUsageLog(kept.map((e) => JSON.stringify(e)));
11372
+ for (const e of kept) appendUsageLog(JSON.stringify(e));
11252
11373
  }
11253
11374
  function emitRollup(did, group) {
11254
11375
  const byCommand = {};
@@ -11257,6 +11378,7 @@ function emitRollup(did, group) {
11257
11378
  byCommand[e.command] = (byCommand[e.command] ?? 0) + 1;
11258
11379
  if (e.agent) agentCount += 1;
11259
11380
  }
11381
+ const insertId = createHash("sha1").update(`${did}|${group.map((e) => `${e.ts}:${e.command}:${e.agent ? 1 : 0}`).join("|")}`).digest("hex");
11260
11382
  capture(
11261
11383
  "CLI Usage Rollup",
11262
11384
  {
@@ -11265,7 +11387,8 @@ function emitRollup(did, group) {
11265
11387
  agent_count: agentCount,
11266
11388
  human_count: group.length - agentCount,
11267
11389
  window_start: new Date(group[0].ts).toISOString(),
11268
- window_end: new Date(group[group.length - 1].ts).toISOString()
11390
+ window_end: new Date(group[group.length - 1].ts).toISOString(),
11391
+ $insert_id: insertId
11269
11392
  },
11270
11393
  { distinctId: did, timestamp: new Date(group[group.length - 1].ts).toISOString() }
11271
11394
  );
@@ -3,12 +3,12 @@ import {
3
3
  dotPathToJsonbExpr,
4
4
  memMigrateCommand,
5
5
  reviveStringifiedJson
6
- } from "./chunk-PLMFRFTT.js";
6
+ } from "./chunk-GXOIR3GF.js";
7
7
  import "./chunk-44CV5IMX.js";
8
- import "./chunk-HS5AHQ4V.js";
9
- import "./chunk-OADHUAEU.js";
10
- import "./chunk-TXTRXV74.js";
11
- import "./chunk-K6MWE2ZH.js";
8
+ import "./chunk-CLJFSFFM.js";
9
+ import "./chunk-7RV7T5PX.js";
10
+ import "./chunk-C7DWZ7B5.js";
11
+ import "./chunk-SO323PZP.js";
12
12
  export {
13
13
  buildIdentityMap,
14
14
  dotPathToJsonbExpr,
@@ -3,14 +3,16 @@ import {
3
3
  closeBackendIfCached,
4
4
  getBackend,
5
5
  resetBackendSingleton,
6
+ updateRecord,
6
7
  upsertRecord
7
- } from "./chunk-OADHUAEU.js";
8
- import "./chunk-TXTRXV74.js";
9
- import "./chunk-K6MWE2ZH.js";
8
+ } from "./chunk-7RV7T5PX.js";
9
+ import "./chunk-C7DWZ7B5.js";
10
+ import "./chunk-SO323PZP.js";
10
11
  export {
11
12
  addRecord,
12
13
  closeBackendIfCached,
13
14
  getBackend,
14
15
  resetBackendSingleton,
16
+ updateRecord,
15
17
  upsertRecord
16
18
  };
@@ -3,12 +3,12 @@ import {
3
3
  note,
4
4
  okJson,
5
5
  requireMemoryInit
6
- } from "./chunk-HS5AHQ4V.js";
6
+ } from "./chunk-CLJFSFFM.js";
7
7
  import {
8
8
  getBackend
9
- } from "./chunk-OADHUAEU.js";
10
- import "./chunk-TXTRXV74.js";
11
- import "./chunk-K6MWE2ZH.js";
9
+ } from "./chunk-7RV7T5PX.js";
10
+ import "./chunk-C7DWZ7B5.js";
11
+ import "./chunk-SO323PZP.js";
12
12
 
13
13
  // src/lib/memory/sync/schema.ts
14
14
  import pc from "picocolors";
@@ -0,0 +1,12 @@
1
+ import {
2
+ memSqlCommand,
3
+ syncSqlCommand
4
+ } from "./chunk-4WWK3GO5.js";
5
+ import "./chunk-CLJFSFFM.js";
6
+ import "./chunk-7RV7T5PX.js";
7
+ import "./chunk-C7DWZ7B5.js";
8
+ import "./chunk-SO323PZP.js";
9
+ export {
10
+ memSqlCommand,
11
+ syncSqlCommand
12
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@withone/cli",
3
- "version": "1.47.11",
3
+ "version": "1.49.0",
4
4
  "description": "CLI for managing One",
5
5
  "type": "module",
6
6
  "files": [
@@ -170,10 +170,19 @@ Underneath, the store has no `platform` column — `type` is the only platform-s
170
170
  ```bash
171
171
  # User memories
172
172
  one --agent mem add note '{"content":"..."}' --tags work --weight 7
173
+ one --agent mem update <id> '{"status":"done"}' # merges into data; refreshes searchable_text
173
174
  one --agent mem search "deadline" # hybrid if key set, else FTS
174
175
  one --agent mem list note --limit 20
175
176
  one --agent mem link <from-id> <to-id> relates_to --bi
176
177
 
178
+ # Identity keys (first-class column, NOT data). Unique across ACTIVE records;
179
+ # archiving a record frees its keys. `mem update '{"keys":[...]}'` is rejected.
180
+ one --agent mem key <id> --add email:x@y.com # add/--remove/--set
181
+ one --agent mem find-by-source email:x@y.com # prefers the active owner
182
+
183
+ # Backfill searchable_text (no embedding provider needed) — fixes NULL/noisy text
184
+ one --agent mem reindex --searchable --type attio/attioPeople
185
+
177
186
  # Status + diagnostics
178
187
  one --agent mem status # backend, provider, _upgrade hint
179
188
  one --agent mem doctor # full health report
@@ -1,12 +0,0 @@
1
- import {
2
- memSqlCommand,
3
- syncSqlCommand
4
- } from "./chunk-QV3Y5N5G.js";
5
- import "./chunk-HS5AHQ4V.js";
6
- import "./chunk-OADHUAEU.js";
7
- import "./chunk-TXTRXV74.js";
8
- import "./chunk-K6MWE2ZH.js";
9
- export {
10
- memSqlCommand,
11
- syncSqlCommand
12
- };