@momomemory/opencode-momo 0.1.3 → 0.1.5

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
@@ -115,6 +115,14 @@ The `momo` tool supports 6 operational modes:
115
115
  - **list**: List recent memories for a given scope.
116
116
  - **forget**: Remove a specific memory by its ID.
117
117
 
118
+ ### Ingestion Tools (RAG Pipeline)
119
+ For richer ingestion than plain `read`, the plugin also exposes dedicated tools that route through Momo's document pipeline:
120
+ - **momo_ingest**: Ingest text, URLs, or local files into documents + retrieval index, with optional memory extraction.
121
+ - **momo_ocr**: Ingest image files, run OCR, and optionally extract memories from recognized text.
122
+ - **momo_transcribe**: Ingest audio/video files, run transcription, and optionally extract memories.
123
+
124
+ These tools are especially useful with non-vision models: image/audio content is converted into searchable text in Momo.
125
+
118
126
  ### Codebase Indexing
119
127
  The `/momo-init` command provides a structured workflow for agents to explore a new codebase and store its architecture, conventions, and key facts into project memory.
120
128
 
@@ -138,6 +146,14 @@ Momo supports the `<private>` tag. Any content wrapped in `<private>` tags is au
138
146
  **Scopes**: `user` (cross-project), `project` (current project, default)
139
147
  **Memory Types**: `fact`, `preference`, `episode`
140
148
 
149
+ ### Ingestion Tool Arguments (high-level)
150
+
151
+ | Tool | Required Args | Optional Args |
152
+ |------|--------------|---------------|
153
+ | `momo_ingest` | `input` | `inputType`, `scope`, `extractMemories`, `metadataJson`, `contentType`, `wait`, `timeoutMs`, `pollIntervalMs` |
154
+ | `momo_ocr` | `filePath` | `scope`, `extractMemories`, `contentType`, `wait`, `timeoutMs`, `pollIntervalMs` |
155
+ | `momo_transcribe` | `filePath` | `scope`, `extractMemories`, `contentType`, `wait`, `timeoutMs`, `pollIntervalMs` |
156
+
141
157
  ## Memory Scoping
142
158
 
143
159
  Memories are isolated using container tags derived from your environment:
package/dist/cli.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AA2IA;;;GAGG;AACH,wBAAgB,sBAAsB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CA+CjE;AA0ID;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,SAAS,EAAE,MAAM,GAAG;IACpD,WAAW,EAAE,OAAO,CAAC;IACrB,gBAAgB,EAAE,OAAO,CAAC;CAC3B,CAWA;AAED;;GAEG;AACH,wBAAgB,eAAe,CAC7B,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE;IAAE,OAAO,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,GAC7C,IAAI,CA6CN"}
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AA2IA;;;GAGG;AACH,wBAAgB,sBAAsB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CA+CjE;AA0ID;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,SAAS,EAAE,MAAM,GAAG;IACpD,WAAW,EAAE,OAAO,CAAC;IACrB,gBAAgB,EAAE,OAAO,CAAC;CAC3B,CAWA;AAED;;GAEG;AACH,wBAAgB,eAAe,CAC7B,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE;IAAE,OAAO,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,GAC7C,IAAI,CAwDN"}
package/dist/cli.js CHANGED
@@ -11,10 +11,38 @@ import {
11
11
  } from "node:fs";
12
12
  import { join as join2 } from "node:path";
13
13
  import { createInterface } from "node:readline";
14
-
15
- // src/config.ts
14
+ // node_modules/@momomemory/sdk/dist/plugin-config.js
15
+ import { homedir, hostname } from "node:os";
16
16
  import { join } from "node:path";
17
- import { homedir } from "node:os";
17
+ var OPENCLAW_DEFAULTS = {
18
+ baseUrl: "http://localhost:3000",
19
+ containerTag: `oclw_${hostname()}`,
20
+ perAgentMemory: false,
21
+ autoRecall: true,
22
+ autoCapture: true,
23
+ maxRecallResults: 10,
24
+ profileFrequency: 50,
25
+ captureMode: "all",
26
+ debug: false
27
+ };
28
+ var OPENCODE_CONFIG_DIR = join(homedir(), ".config", "opencode");
29
+ var PI_DEFAULTS = {
30
+ baseUrl: "http://localhost:3000",
31
+ containerTag: `pi_${hostname()}`,
32
+ autoRecall: true,
33
+ autoCapture: true,
34
+ maxRecallResults: 10,
35
+ profileFrequency: 50,
36
+ debug: false
37
+ };
38
+ // src/config.ts
39
+ function getConfigDir() {
40
+ const xdg = process.env.XDG_CONFIG_HOME;
41
+ if (xdg) {
42
+ return `${xdg}/opencode`;
43
+ }
44
+ return `${process.env.HOME}/.config/opencode`;
45
+ }
18
46
 
19
47
  // src/services/jsonc.ts
20
48
  function stripJsoncComments(input) {
@@ -86,15 +114,6 @@ function stripJsoncComments(input) {
86
114
  return out.join("").replace(/,\s*([}\]])/g, "$1");
87
115
  }
88
116
 
89
- // src/config.ts
90
- function getConfigDir() {
91
- const xdg = process.env.XDG_CONFIG_HOME;
92
- if (xdg) {
93
- return join(xdg, "opencode");
94
- }
95
- return join(homedir(), ".config", "opencode");
96
- }
97
-
98
117
  // src/cli.ts
99
118
  var PLUGIN_NAME = "@momomemory/opencode-momo@latest";
100
119
  var MOMO_INIT_MD = `---
@@ -156,10 +175,10 @@ bunx @momomemory/opencode-momo configure
156
175
  \`\`\`
157
176
 
158
177
  Or set environment variables:
159
- - \`MOMO_BASE_URL\` - Momo server URL (default: http://localhost:3000)
160
- - \`MOMO_API_KEY\` - API key for authentication
161
- - \`MOMO_CONTAINER_TAG_USER\` - Optional user container tag override
162
- - \`MOMO_CONTAINER_TAG_PROJECT\` - Optional project container tag override
178
+ - \`MOMO_OPENCODE_BASE_URL\` - Momo server URL (default: http://localhost:3000)
179
+ - \`MOMO_OPENCODE_API_KEY\` - API key for authentication
180
+ - \`MOMO_OPENCODE_CONTAINER_TAG_USER\` - Optional user container tag override
181
+ - \`MOMO_OPENCODE_CONTAINER_TAG_PROJECT\` - Optional project container tag override
163
182
 
164
183
  After configuration, restart OpenCode to activate.
165
184
  `;
@@ -409,10 +428,11 @@ function writeMomoConfig(configDir, options) {
409
428
  existing = {};
410
429
  }
411
430
  }
412
- const priorBaseUrl = typeof existing.baseUrl === "string" ? existing.baseUrl : undefined;
413
- const priorApiKey = typeof existing.apiKey === "string" ? existing.apiKey : undefined;
414
- const priorContainerTagUser = typeof existing.containerTagUser === "string" ? existing.containerTagUser : undefined;
415
- const priorContainerTagProject = typeof existing.containerTagProject === "string" ? existing.containerTagProject : undefined;
431
+ const existingOpenCode = existing.opencode && typeof existing.opencode === "object" && !Array.isArray(existing.opencode) ? existing.opencode : {};
432
+ const priorBaseUrl = typeof existingOpenCode.baseUrl === "string" ? existingOpenCode.baseUrl : undefined;
433
+ const priorApiKey = typeof existingOpenCode.apiKey === "string" ? existingOpenCode.apiKey : undefined;
434
+ const priorContainerTagUser = typeof existingOpenCode.containerTagUser === "string" ? existingOpenCode.containerTagUser : undefined;
435
+ const priorContainerTagProject = typeof existingOpenCode.containerTagProject === "string" ? existingOpenCode.containerTagProject : undefined;
416
436
  const config = {
417
437
  baseUrl: options.baseUrl ?? priorBaseUrl ?? "http://localhost:3000",
418
438
  apiKey: options.apiKey ?? priorApiKey ?? "",
@@ -420,16 +440,19 @@ function writeMomoConfig(configDir, options) {
420
440
  containerTagProject: priorContainerTagProject ?? ""
421
441
  };
422
442
  const lines = ["{"];
423
- lines.push(" // Momo server URL (default: http://localhost:3000)");
424
- lines.push(` "baseUrl": ${JSON.stringify(config.baseUrl)},`);
425
- lines.push(" // API key for authentication (leave empty if your server has auth disabled)");
426
- lines.push(` "apiKey": ${JSON.stringify(config.apiKey)},`);
427
- lines.push(" // Optional override for user memory container tag (default: auto-derived from username)");
428
- lines.push(` "containerTagUser": ${JSON.stringify(config.containerTagUser)},`);
429
- lines.push(" // Optional override for project memory container tag (default: auto-derived from project directory)");
430
- lines.push(` "containerTagProject": ${JSON.stringify(config.containerTagProject)}`);
431
- lines.push(" // Note: project-local config files (.momo.jsonc / momo.jsonc) override this global file,");
432
- lines.push(" // and MOMO_* environment variables override both.");
443
+ lines.push(" // OpenCode plugin config for Momo");
444
+ lines.push(' "opencode": {');
445
+ lines.push(" // Momo server URL (default: http://localhost:3000)");
446
+ lines.push(` "baseUrl": ${JSON.stringify(config.baseUrl)},`);
447
+ lines.push(" // API key for authentication (leave empty if your server has auth disabled)");
448
+ lines.push(` "apiKey": ${JSON.stringify(config.apiKey)},`);
449
+ lines.push(" // Optional override for user memory container tag (default: auto-derived from username)");
450
+ lines.push(` "containerTagUser": ${JSON.stringify(config.containerTagUser)},`);
451
+ lines.push(" // Optional override for project memory container tag (default: auto-derived from project directory)");
452
+ lines.push(` "containerTagProject": ${JSON.stringify(config.containerTagProject)}`);
453
+ lines.push(" }");
454
+ lines.push(" // Note: project-local config file (.momo.jsonc) overrides this global file,");
455
+ lines.push(" // and MOMO_OPENCODE_* environment variables override both.");
433
456
  lines.push("}");
434
457
  lines.push("");
435
458
  writeFileSync(configPath, lines.join(`
@@ -483,8 +506,8 @@ Next steps:
483
506
  $ opencode-momo configure --base-url http://localhost:3000 --api-key YOUR_KEY
484
507
 
485
508
  Or set environment variables:
486
- $ export MOMO_API_KEY=your-key
487
- $ export MOMO_BASE_URL=http://localhost:3000
509
+ $ export MOMO_OPENCODE_API_KEY=your-key
510
+ $ export MOMO_OPENCODE_BASE_URL=http://localhost:3000
488
511
 
489
512
  2. Restart OpenCode to activate the plugin.
490
513
 
package/dist/config.d.ts CHANGED
@@ -1,9 +1,8 @@
1
- export interface MomoConfig {
2
- apiKey?: string;
1
+ import { type ResolvedOpenCodePluginConfig } from "@momomemory/sdk";
2
+ export type { ResolvedOpenCodePluginConfig };
3
+ export type MomoConfig = ResolvedOpenCodePluginConfig & {
3
4
  baseUrl: string;
4
- containerTagUser?: string;
5
- containerTagProject?: string;
6
- }
5
+ };
7
6
  export declare function getConfigDir(): string;
8
7
  export declare function loadConfig(directory?: string): MomoConfig;
9
8
  export declare function isConfigured(directory?: string): boolean;
@@ -1 +1 @@
1
- {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAKA,MAAM,WAAW,UAAU;IACzB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,mBAAmB,CAAC,EAAE,MAAM,CAAC;CAC9B;AAWD,wBAAgB,YAAY,IAAI,MAAM,CAMrC;AAiCD,wBAAgB,UAAU,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,UAAU,CAmBzD;AAED,wBAAgB,YAAY,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,OAAO,CAGxD"}
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA,OAAO,EAA4B,KAAK,4BAA4B,EAAE,MAAM,iBAAiB,CAAC;AAE9F,YAAY,EAAE,4BAA4B,EAAE,CAAC;AAE7C,MAAM,MAAM,UAAU,GAAG,4BAA4B,GAAG;IAAE,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC;AAE5E,wBAAgB,YAAY,IAAI,MAAM,CAMrC;AAED,wBAAgB,UAAU,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,UAAU,CAMzD;AAED,wBAAgB,YAAY,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,OAAO,CAGxD"}
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAe,MAAM,qBAAqB,CAAC;AAoC/D,eAAO,MAAM,UAAU,EAAE,MAmSxB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAe,MAAM,qBAAqB,CAAC;AAuG/D,eAAO,MAAM,UAAU,EAAE,MAqpBxB,CAAC"}
package/dist/index.js CHANGED
@@ -12329,6 +12329,10 @@ function tool(input) {
12329
12329
  return input;
12330
12330
  }
12331
12331
  tool.schema = exports_external;
12332
+ // src/index.ts
12333
+ import { existsSync as existsSync2 } from "node:fs";
12334
+ import { basename as basename2 } from "node:path";
12335
+
12332
12336
  // node_modules/openapi-fetch/dist/index.js
12333
12337
  var PATH_PARAM_RE = /\{[^{}]+\}/g;
12334
12338
  var supportsRequestInitExt = () => {
@@ -12354,7 +12358,7 @@ function createClient(clientOptions) {
12354
12358
  async function coreFetch(schemaPath, fetchOptions) {
12355
12359
  const {
12356
12360
  baseUrl: localBaseUrl,
12357
- fetch = baseFetch,
12361
+ fetch: fetch2 = baseFetch,
12358
12362
  Request = CustomRequest,
12359
12363
  headers,
12360
12364
  params = {},
@@ -12399,7 +12403,7 @@ function createClient(clientOptions) {
12399
12403
  id = randomID();
12400
12404
  options = Object.freeze({
12401
12405
  baseUrl: finalBaseUrl,
12402
- fetch,
12406
+ fetch: fetch2,
12403
12407
  parseAs,
12404
12408
  querySerializer,
12405
12409
  bodySerializer
@@ -12428,7 +12432,7 @@ function createClient(clientOptions) {
12428
12432
  }
12429
12433
  if (!response) {
12430
12434
  try {
12431
- response = await fetch(request, requestInitExt);
12435
+ response = await fetch2(request, requestInitExt);
12432
12436
  } catch (error46) {
12433
12437
  let errorAfterMiddleware = error46;
12434
12438
  if (middlewares.length) {
@@ -12859,6 +12863,11 @@ class DocumentsGroup {
12859
12863
  formData.append("containerTag", containerTag);
12860
12864
  if (body?.metadata)
12861
12865
  formData.append("metadata", body.metadata);
12866
+ if (body?.extractMemories !== undefined) {
12867
+ formData.append("extractMemories", String(body.extractMemories));
12868
+ }
12869
+ if (body?.contentType)
12870
+ formData.append("contentType", body.contentType);
12862
12871
  const headers = { ...opts?.headers ?? {} };
12863
12872
  const apiKey = this.config.getApiKey ? await this.config.getApiKey() : this.config.apiKey;
12864
12873
  if (apiKey)
@@ -13415,26 +13424,24 @@ class MomoClient {
13415
13424
  this.health = new HealthGroup(this.raw, config2);
13416
13425
  }
13417
13426
  }
13418
- // src/config.ts
13419
- import { readFileSync, existsSync } from "node:fs";
13427
+ // node_modules/@momomemory/sdk/dist/plugin-config.js
13428
+ import { homedir, hostname as hostname3 } from "node:os";
13420
13429
  import { join } from "node:path";
13421
- import { homedir } from "node:os";
13422
-
13423
- // src/services/jsonc.ts
13424
- function stripJsoncComments(input) {
13425
- var Mode;
13426
- ((Mode2) => {
13430
+ import { existsSync, readFileSync } from "node:fs";
13431
+ function stripJsoncComments(content) {
13432
+ let Mode;
13433
+ (function(Mode2) {
13427
13434
  Mode2[Mode2["Code"] = 0] = "Code";
13428
13435
  Mode2[Mode2["String"] = 1] = "String";
13429
13436
  Mode2[Mode2["LineComment"] = 2] = "LineComment";
13430
13437
  Mode2[Mode2["BlockComment"] = 3] = "BlockComment";
13431
- })(Mode ||= {});
13438
+ })(Mode || (Mode = {}));
13432
13439
  const out = [];
13433
- let mode = 0 /* Code */;
13434
- for (let i = 0;i < input.length; i++) {
13435
- const ch = input[i];
13436
- const next = input[i + 1];
13437
- if (mode === 1 /* String */) {
13440
+ let mode = Mode.Code;
13441
+ for (let i = 0;i < content.length; i++) {
13442
+ const ch = content[i];
13443
+ const next = content[i + 1];
13444
+ if (mode === Mode.String) {
13438
13445
  out.push(ch);
13439
13446
  if (ch === "\\") {
13440
13447
  if (next !== undefined) {
@@ -13444,22 +13451,22 @@ function stripJsoncComments(input) {
13444
13451
  continue;
13445
13452
  }
13446
13453
  if (ch === '"') {
13447
- mode = 0 /* Code */;
13454
+ mode = Mode.Code;
13448
13455
  }
13449
13456
  continue;
13450
13457
  }
13451
- if (mode === 2 /* LineComment */) {
13458
+ if (mode === Mode.LineComment) {
13452
13459
  if (ch === `
13453
13460
  `) {
13454
- mode = 0 /* Code */;
13461
+ mode = Mode.Code;
13455
13462
  out.push(`
13456
13463
  `);
13457
13464
  }
13458
13465
  continue;
13459
13466
  }
13460
- if (mode === 3 /* BlockComment */) {
13467
+ if (mode === Mode.BlockComment) {
13461
13468
  if (ch === "*" && next === "/") {
13462
- mode = 0 /* Code */;
13469
+ mode = Mode.Code;
13463
13470
  i++;
13464
13471
  continue;
13465
13472
  }
@@ -13471,17 +13478,17 @@ function stripJsoncComments(input) {
13471
13478
  continue;
13472
13479
  }
13473
13480
  if (ch === '"') {
13474
- mode = 1 /* String */;
13481
+ mode = Mode.String;
13475
13482
  out.push(ch);
13476
13483
  continue;
13477
13484
  }
13478
13485
  if (ch === "/" && next === "/") {
13479
- mode = 2 /* LineComment */;
13486
+ mode = Mode.LineComment;
13480
13487
  i++;
13481
13488
  continue;
13482
13489
  }
13483
13490
  if (ch === "/" && next === "*") {
13484
- mode = 3 /* BlockComment */;
13491
+ mode = Mode.BlockComment;
13485
13492
  i++;
13486
13493
  continue;
13487
13494
  }
@@ -13489,56 +13496,248 @@ function stripJsoncComments(input) {
13489
13496
  }
13490
13497
  return out.join("").replace(/,\s*([}\]])/g, "$1");
13491
13498
  }
13492
-
13493
- // src/config.ts
13494
- var DEFAULT_BASE_URL = "http://localhost:3000";
13495
- function getConfigDir() {
13496
- const xdg = process.env.XDG_CONFIG_HOME;
13497
- if (xdg) {
13498
- return join(xdg, "opencode");
13499
- }
13500
- return join(homedir(), ".config", "opencode");
13501
- }
13502
- function readConfigFile() {
13503
- const configPath = join(getConfigDir(), "momo.jsonc");
13504
- return readJsoncConfigFile(configPath);
13505
- }
13506
- function readJsoncConfigFile(configPath) {
13499
+ function readJsoncFile(path) {
13507
13500
  try {
13508
- const raw = readFileSync(configPath, "utf-8");
13501
+ if (!existsSync(path))
13502
+ return null;
13503
+ const raw = readFileSync(path, "utf-8");
13509
13504
  const stripped = stripJsoncComments(raw);
13510
13505
  return JSON.parse(stripped);
13511
13506
  } catch {
13512
- return {};
13507
+ return null;
13513
13508
  }
13514
13509
  }
13515
- function readProjectConfigFile(directory) {
13516
- if (!directory)
13517
- return {};
13518
- const dotPath = join(directory, ".momo.jsonc");
13519
- if (existsSync(dotPath)) {
13520
- return readJsoncConfigFile(dotPath);
13510
+ function hasOwn(obj, key) {
13511
+ return Object.prototype.hasOwnProperty.call(obj, key);
13512
+ }
13513
+ function sanitizeContainerTag(value) {
13514
+ return value.replace(/[^A-Za-z0-9_]+/g, "_").replace(/_+/g, "_").replace(/^_+|_+$/g, "");
13515
+ }
13516
+ function interpolateEnvVars(value) {
13517
+ return value.replace(/\$\{([^}]+)\}/g, (_chunk, key) => {
13518
+ const envValue = process.env[key];
13519
+ if (!envValue) {
13520
+ throw new Error(`Environment variable ${key} is not set`);
13521
+ }
13522
+ return envValue;
13523
+ });
13524
+ }
13525
+ function toBoolean(value, fallback, trueValues = ["true", "1", "yes"], falseValues = ["false", "0", "no"]) {
13526
+ if (typeof value === "boolean")
13527
+ return value;
13528
+ if (typeof value === "string") {
13529
+ const lower = value.toLowerCase();
13530
+ if (trueValues.includes(lower))
13531
+ return true;
13532
+ if (falseValues.includes(lower))
13533
+ return false;
13521
13534
  }
13522
- const plainPath = join(directory, "momo.jsonc");
13523
- if (existsSync(plainPath)) {
13524
- return readJsoncConfigFile(plainPath);
13535
+ return fallback;
13536
+ }
13537
+ function toBoundedInt(value, fallback, min, max) {
13538
+ let n;
13539
+ if (typeof value === "number" && Number.isFinite(value)) {
13540
+ n = Math.round(value);
13541
+ } else if (typeof value === "string") {
13542
+ const parsed = parseInt(value, 10);
13543
+ if (Number.isFinite(parsed)) {
13544
+ n = Math.round(parsed);
13545
+ } else {
13546
+ return fallback;
13547
+ }
13548
+ } else {
13549
+ return fallback;
13550
+ }
13551
+ if (n < min)
13552
+ return min;
13553
+ if (n > max)
13554
+ return max;
13555
+ return n;
13556
+ }
13557
+ function pickString(key, envKey, config2, fallback) {
13558
+ const envValue = process.env[envKey];
13559
+ if (envValue !== undefined)
13560
+ return interpolateEnvVars(envValue);
13561
+ if (hasOwn(config2, key) && typeof config2[key] === "string") {
13562
+ return interpolateEnvVars(config2[key]);
13563
+ }
13564
+ return fallback;
13565
+ }
13566
+ function pickOptionalString(key, envKey, config2) {
13567
+ const envValue = process.env[envKey];
13568
+ if (envValue !== undefined)
13569
+ return interpolateEnvVars(envValue);
13570
+ if (hasOwn(config2, key) && typeof config2[key] === "string") {
13571
+ return interpolateEnvVars(config2[key]);
13572
+ }
13573
+ return;
13574
+ }
13575
+ function pickBoolean(key, envKey, config2, fallback) {
13576
+ const envValue = process.env[envKey];
13577
+ if (envValue !== undefined) {
13578
+ return toBoolean(envValue, fallback);
13579
+ }
13580
+ return toBoolean(config2[key], fallback);
13581
+ }
13582
+ function pickBoundedInt(key, envKey, config2, fallback, min, max) {
13583
+ const envValue = process.env[envKey];
13584
+ if (envValue !== undefined) {
13585
+ return toBoundedInt(envValue, fallback, min, max);
13586
+ }
13587
+ return toBoundedInt(config2[key], fallback, min, max);
13588
+ }
13589
+ var OPENCLAW_DEFAULTS = {
13590
+ baseUrl: "http://localhost:3000",
13591
+ containerTag: `oclw_${hostname3()}`,
13592
+ perAgentMemory: false,
13593
+ autoRecall: true,
13594
+ autoCapture: true,
13595
+ maxRecallResults: 10,
13596
+ profileFrequency: 50,
13597
+ captureMode: "all",
13598
+ debug: false
13599
+ };
13600
+ var OPENCLAW_ENV_PREFIX = "MOMO_OPENCLAW_";
13601
+ function loadOpenClawConfig(config2) {
13602
+ const pluginConfig = config2.openclaw ?? {};
13603
+ const prefix = OPENCLAW_ENV_PREFIX;
13604
+ return {
13605
+ baseUrl: pickString("baseUrl", `${prefix}BASE_URL`, pluginConfig, OPENCLAW_DEFAULTS.baseUrl),
13606
+ apiKey: pickOptionalString("apiKey", `${prefix}API_KEY`, pluginConfig),
13607
+ containerTag: sanitizeContainerTag(pickString("containerTag", `${prefix}CONTAINER_TAG`, pluginConfig, OPENCLAW_DEFAULTS.containerTag)),
13608
+ perAgentMemory: toBoolean(pluginConfig.perAgentMemory, OPENCLAW_DEFAULTS.perAgentMemory),
13609
+ autoRecall: toBoolean(pluginConfig.autoRecall, OPENCLAW_DEFAULTS.autoRecall),
13610
+ autoCapture: toBoolean(pluginConfig.autoCapture, OPENCLAW_DEFAULTS.autoCapture),
13611
+ maxRecallResults: toBoundedInt(pluginConfig.maxRecallResults, OPENCLAW_DEFAULTS.maxRecallResults, 1, 20),
13612
+ profileFrequency: toBoundedInt(pluginConfig.profileFrequency, OPENCLAW_DEFAULTS.profileFrequency, 1, 500),
13613
+ captureMode: pluginConfig.captureMode === "everything" ? "everything" : OPENCLAW_DEFAULTS.captureMode,
13614
+ debug: toBoolean(pluginConfig.debug, OPENCLAW_DEFAULTS.debug)
13615
+ };
13616
+ }
13617
+ var OPENCODE_DEFAULTS = {
13618
+ baseUrl: "http://localhost:3000"
13619
+ };
13620
+ var OPENCODE_ENV_PREFIX = "MOMO_OPENCODE_";
13621
+ var OPENCODE_CONFIG_DIR = join(homedir(), ".config", "opencode");
13622
+ function loadOpenCodeConfig(config2) {
13623
+ const pluginConfig = config2.opencode ?? {};
13624
+ const prefix = OPENCODE_ENV_PREFIX;
13625
+ return {
13626
+ baseUrl: pickString("baseUrl", `${prefix}BASE_URL`, pluginConfig, OPENCODE_DEFAULTS.baseUrl),
13627
+ apiKey: pickOptionalString("apiKey", `${prefix}API_KEY`, pluginConfig),
13628
+ containerTagUser: pickOptionalString("containerTagUser", `${prefix}CONTAINER_TAG_USER`, pluginConfig),
13629
+ containerTagProject: pickOptionalString("containerTagProject", `${prefix}CONTAINER_TAG_PROJECT`, pluginConfig)
13630
+ };
13631
+ }
13632
+ var PI_DEFAULTS = {
13633
+ baseUrl: "http://localhost:3000",
13634
+ containerTag: `pi_${hostname3()}`,
13635
+ autoRecall: true,
13636
+ autoCapture: true,
13637
+ maxRecallResults: 10,
13638
+ profileFrequency: 50,
13639
+ debug: false
13640
+ };
13641
+ var PI_ENV_PREFIX = "MOMO_PI_";
13642
+ function loadPiConfig(config2) {
13643
+ const pluginConfig = config2.pi ?? {};
13644
+ const record2 = pluginConfig;
13645
+ const prefix = PI_ENV_PREFIX;
13646
+ return {
13647
+ baseUrl: pickString("baseUrl", `${prefix}BASE_URL`, record2, PI_DEFAULTS.baseUrl),
13648
+ apiKey: pickOptionalString("apiKey", `${prefix}API_KEY`, record2),
13649
+ containerTag: sanitizeContainerTag(pickString("containerTag", `${prefix}CONTAINER_TAG`, record2, PI_DEFAULTS.containerTag)),
13650
+ autoRecall: pickBoolean("autoRecall", `${prefix}AUTO_RECALL`, record2, PI_DEFAULTS.autoRecall),
13651
+ autoCapture: pickBoolean("autoCapture", `${prefix}AUTO_CAPTURE`, record2, PI_DEFAULTS.autoCapture),
13652
+ maxRecallResults: pickBoundedInt("maxRecallResults", `${prefix}MAX_RECALL_RESULTS`, record2, PI_DEFAULTS.maxRecallResults, 1, 20),
13653
+ profileFrequency: pickBoundedInt("profileFrequency", `${prefix}PROFILE_FREQUENCY`, record2, PI_DEFAULTS.profileFrequency, 1, 500),
13654
+ debug: pickBoolean("debug", `${prefix}DEBUG`, record2, PI_DEFAULTS.debug)
13655
+ };
13656
+ }
13657
+ function getConfigPaths(plugin, cwd = process.cwd(), globalConfigDir) {
13658
+ switch (plugin) {
13659
+ case "opencode":
13660
+ return {
13661
+ project: join(cwd, ".momo.jsonc"),
13662
+ global: join(globalConfigDir ?? OPENCODE_CONFIG_DIR, "momo.jsonc")
13663
+ };
13664
+ case "openclaw":
13665
+ return {
13666
+ project: join(cwd, ".momo.jsonc"),
13667
+ global: join(globalConfigDir ?? join(homedir(), ".openclaw"), "momo.jsonc")
13668
+ };
13669
+ case "pi":
13670
+ return {
13671
+ project: join(cwd, ".momo.jsonc"),
13672
+ global: join(globalConfigDir ?? join(homedir(), ".pi"), "momo.jsonc")
13673
+ };
13525
13674
  }
13526
- return {};
13527
13675
  }
13528
- function loadConfig(directory) {
13529
- const globalConfig2 = readConfigFile();
13530
- const projectConfig = readProjectConfigFile(directory);
13531
- const envApiKey = process.env.MOMO_API_KEY;
13532
- const envBaseUrl = process.env.MOMO_BASE_URL;
13533
- const envContainerTagUser = process.env.MOMO_CONTAINER_TAG_USER;
13534
- const envContainerTagProject = process.env.MOMO_CONTAINER_TAG_PROJECT;
13676
+ function mergeConfigs(...configs) {
13677
+ const result = {};
13678
+ for (const config2 of configs) {
13679
+ if (!config2)
13680
+ continue;
13681
+ for (const [key, value] of Object.entries(config2)) {
13682
+ if (value !== undefined) {
13683
+ result[key] = value;
13684
+ }
13685
+ }
13686
+ }
13687
+ return result;
13688
+ }
13689
+ function loadPluginConfig(plugin, options) {
13690
+ const cwd = options?.cwd ?? process.cwd();
13691
+ const globalConfigDir = options?.globalConfigDir;
13692
+ const paths = getConfigPaths(plugin, cwd, globalConfigDir);
13693
+ const projectConfig = readJsoncFile(paths.project ?? "");
13694
+ const globalConfig2 = readJsoncFile(paths.global ?? "");
13695
+ const mergedConfig = {
13696
+ openclaw: mergeConfigs(globalConfig2?.openclaw, projectConfig?.openclaw),
13697
+ opencode: mergeConfigs(globalConfig2?.opencode, projectConfig?.opencode),
13698
+ pi: mergeConfigs(globalConfig2?.pi, projectConfig?.pi)
13699
+ };
13700
+ let config2;
13701
+ switch (plugin) {
13702
+ case "openclaw":
13703
+ config2 = loadOpenClawConfig(mergedConfig);
13704
+ break;
13705
+ case "opencode":
13706
+ config2 = loadOpenCodeConfig(mergedConfig);
13707
+ break;
13708
+ case "pi":
13709
+ config2 = loadPiConfig(mergedConfig);
13710
+ break;
13711
+ }
13535
13712
  return {
13536
- apiKey: envApiKey ?? projectConfig.apiKey ?? globalConfig2.apiKey,
13537
- baseUrl: envBaseUrl ?? projectConfig.baseUrl ?? globalConfig2.baseUrl ?? DEFAULT_BASE_URL,
13538
- containerTagUser: envContainerTagUser ?? projectConfig.containerTagUser ?? globalConfig2.containerTagUser,
13539
- containerTagProject: envContainerTagProject ?? projectConfig.containerTagProject ?? globalConfig2.containerTagProject
13713
+ config: config2,
13714
+ meta: {
13715
+ cwd,
13716
+ files: {
13717
+ project: paths.project && existsSync(paths.project) ? paths.project : undefined,
13718
+ global: paths.global && existsSync(paths.global) ? paths.global : undefined
13719
+ }
13720
+ }
13540
13721
  };
13541
13722
  }
13723
+ function loadOpenCodePluginConfig(options) {
13724
+ return loadPluginConfig("opencode", options);
13725
+ }
13726
+ // src/config.ts
13727
+ function getConfigDir() {
13728
+ const xdg = process.env.XDG_CONFIG_HOME;
13729
+ if (xdg) {
13730
+ return `${xdg}/opencode`;
13731
+ }
13732
+ return `${process.env.HOME}/.config/opencode`;
13733
+ }
13734
+ function loadConfig(directory) {
13735
+ const { config: config2 } = loadOpenCodePluginConfig({
13736
+ cwd: directory ?? process.cwd(),
13737
+ globalConfigDir: getConfigDir()
13738
+ });
13739
+ return config2;
13740
+ }
13542
13741
  function isConfigured(directory) {
13543
13742
  const config2 = loadConfig(directory);
13544
13743
  return config2.apiKey != null && config2.apiKey.length > 0;
@@ -13697,6 +13896,8 @@ async function handleCompactionEvent(event, momo, containerTag) {
13697
13896
 
13698
13897
  // src/index.ts
13699
13898
  var z = tool.schema;
13899
+ var DEFAULT_INGEST_TIMEOUT_MS = 120000;
13900
+ var DEFAULT_POLL_INTERVAL_MS = 1500;
13700
13901
  var injectedSessions = new Set;
13701
13902
  var HELP_TEXT = `**Momo Memory Commands**
13702
13903
 
@@ -13718,6 +13919,57 @@ var HELP_TEXT = `**Momo Memory Commands**
13718
13919
  - \`momo({ mode: "search", query: "database schema" })\`
13719
13920
  - \`momo({ mode: "forget", memoryId: "mem_abc123" })\`
13720
13921
  `;
13922
+ function truncate(value, max = 600) {
13923
+ if (value.length <= max)
13924
+ return value;
13925
+ return `${value.slice(0, max)}...`;
13926
+ }
13927
+ function inferInputType(input, inputType) {
13928
+ if (inputType && inputType !== "auto")
13929
+ return inputType;
13930
+ if (input.startsWith("http://") || input.startsWith("https://"))
13931
+ return "url";
13932
+ if (existsSync2(input))
13933
+ return "file";
13934
+ return "text";
13935
+ }
13936
+ function parseMetadataJson(metadataJson) {
13937
+ if (!metadataJson)
13938
+ return {};
13939
+ try {
13940
+ const parsed = JSON.parse(metadataJson);
13941
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
13942
+ return { error: "metadataJson must be a JSON object" };
13943
+ }
13944
+ return parsed;
13945
+ } catch (error45) {
13946
+ const message = error45 instanceof Error ? error45.message : String(error45);
13947
+ return { error: `Invalid metadataJson: ${message}` };
13948
+ }
13949
+ }
13950
+ async function delay(ms) {
13951
+ await new Promise((resolve) => setTimeout(resolve, ms));
13952
+ }
13953
+ function formatSearchContent(result) {
13954
+ if (result.type === "memory") {
13955
+ if (result.content?.trim())
13956
+ return result.content;
13957
+ return result.memoryId ? `[memory:${result.memoryId}]` : "(empty memory)";
13958
+ }
13959
+ const fromContent = result.content?.trim();
13960
+ if (fromContent)
13961
+ return fromContent;
13962
+ const fromChunk = result.chunks?.find((c) => c.content?.trim())?.content;
13963
+ if (fromChunk)
13964
+ return fromChunk;
13965
+ const fromTitle = result.title?.trim();
13966
+ if (fromTitle)
13967
+ return fromTitle;
13968
+ const fromSummary = result.summary?.trim();
13969
+ if (fromSummary)
13970
+ return fromSummary;
13971
+ return result.documentId ? `[document:${result.documentId}]` : "(empty document match)";
13972
+ }
13721
13973
  var MomoPlugin = async (ctx) => {
13722
13974
  const config2 = loadConfig(ctx.directory);
13723
13975
  const configured = isConfigured(ctx.directory);
@@ -13734,7 +13986,7 @@ var MomoPlugin = async (ctx) => {
13734
13986
  });
13735
13987
  function requireMomo() {
13736
13988
  if (!momo) {
13737
- throw new Error("Momo is not configured. Set MOMO_API_KEY or configure ~/.config/opencode/momo.jsonc (or project-local .momo.jsonc).");
13989
+ throw new Error("Momo is not configured. Set MOMO_OPENCODE_API_KEY or configure ~/.config/opencode/momo.jsonc (or project-local .momo.jsonc).");
13738
13990
  }
13739
13991
  return momo;
13740
13992
  }
@@ -13743,6 +13995,100 @@ var MomoPlugin = async (ctx) => {
13743
13995
  return tags.user;
13744
13996
  return tags.project;
13745
13997
  }
13998
+ async function waitForIngestion(client, ingestionId, timeoutMs, pollIntervalMs) {
13999
+ const start = Date.now();
14000
+ let transientErrors = 0;
14001
+ while (Date.now() - start < timeoutMs) {
14002
+ let status;
14003
+ try {
14004
+ status = await client.documents.getIngestionStatus(ingestionId, {
14005
+ timeoutMs
14006
+ });
14007
+ } catch (error45) {
14008
+ const message = error45 instanceof Error ? error45.message.toLowerCase() : String(error45).toLowerCase();
14009
+ const isTransient = message.includes("database is locked") || message.includes("internal error") || message.includes("timeout") || message.includes("503") || message.includes("502");
14010
+ if (!isTransient)
14011
+ throw error45;
14012
+ transientErrors += 1;
14013
+ if (transientErrors > 5) {
14014
+ throw error45;
14015
+ }
14016
+ await delay(pollIntervalMs);
14017
+ continue;
14018
+ }
14019
+ if (status.status === "completed" || status.status === "failed") {
14020
+ return {
14021
+ status: status.status,
14022
+ title: status.title ?? undefined,
14023
+ documentId: status.documentId
14024
+ };
14025
+ }
14026
+ await delay(pollIntervalMs);
14027
+ }
14028
+ return { status: "processing" };
14029
+ }
14030
+ async function formatDocumentResult(client, documentId, scope, extractedBy) {
14031
+ try {
14032
+ const doc2 = await client.documents.get(documentId);
14033
+ const lines = [
14034
+ `Document ready (scope: ${scope}).`,
14035
+ `- documentId: ${doc2.documentId}`,
14036
+ `- docType: ${doc2.docType}`,
14037
+ `- ingestionStatus: ${doc2.ingestionStatus}`
14038
+ ];
14039
+ if (extractedBy)
14040
+ lines.push(`- extractor: ${extractedBy}`);
14041
+ if (doc2.title)
14042
+ lines.push(`- title: ${doc2.title}`);
14043
+ if (doc2.errorMessage)
14044
+ lines.push(`- notes: ${doc2.errorMessage}`);
14045
+ if (doc2.content)
14046
+ lines.push(`- extractedTextPreview: ${truncate(doc2.content, 800)}`);
14047
+ return lines.join(`
14048
+ `);
14049
+ } catch {
14050
+ return [
14051
+ `Document ready (scope: ${scope}).`,
14052
+ `- documentId: ${documentId}`,
14053
+ extractedBy ? `- extractor: ${extractedBy}` : undefined
14054
+ ].filter(Boolean).join(`
14055
+ `);
14056
+ }
14057
+ }
14058
+ async function uploadDocumentFromPath(filePath, params) {
14059
+ const fs = await Function("return import('fs/promises')")();
14060
+ const { buffer } = await fs.readFile(filePath);
14061
+ const file2 = new File([new Blob([new Uint8Array(buffer)])], basename2(filePath));
14062
+ const formData = new FormData;
14063
+ formData.append("file", file2);
14064
+ formData.append("containerTag", params.containerTag);
14065
+ formData.append("extractMemories", String(params.extractMemories));
14066
+ if (params.contentType)
14067
+ formData.append("contentType", params.contentType);
14068
+ if (params.metadata && Object.keys(params.metadata).length > 0) {
14069
+ formData.append("metadata", JSON.stringify(params.metadata));
14070
+ }
14071
+ const headers = {};
14072
+ if (config2.apiKey)
14073
+ headers.Authorization = `Bearer ${config2.apiKey}`;
14074
+ const response = await fetch(`${config2.baseUrl}/api/v1/documents:upload`, {
14075
+ method: "POST",
14076
+ body: formData,
14077
+ headers,
14078
+ signal: AbortSignal.timeout(params.timeoutMs)
14079
+ });
14080
+ const json2 = await response.json();
14081
+ if (!response.ok || json2.error) {
14082
+ const message = json2.error?.message ?? `Upload failed with status ${response.status}`;
14083
+ throw new Error(message);
14084
+ }
14085
+ const documentId = json2.data?.documentId;
14086
+ const ingestionId = json2.data?.ingestionId;
14087
+ if (!documentId || !ingestionId) {
14088
+ throw new Error("Upload response missing documentId/ingestionId");
14089
+ }
14090
+ return { documentId, ingestionId };
14091
+ }
13746
14092
  const momoTool = tool({
13747
14093
  description: "Interact with Momo long-term memory. Modes: help, add, search, profile, list, forget.",
13748
14094
  args: {
@@ -13789,18 +14135,23 @@ var MomoPlugin = async (ctx) => {
13789
14135
  q: args.query,
13790
14136
  containerTags,
13791
14137
  limit: args.limit ?? 10,
13792
- scope: "hybrid"
14138
+ scope: "hybrid",
14139
+ include: {
14140
+ documents: true,
14141
+ chunks: true
14142
+ }
13793
14143
  });
13794
14144
  if (!results.results || results.results.length === 0) {
13795
- return "No memories found matching your query.";
14145
+ return "No results found matching your query.";
13796
14146
  }
13797
14147
  const formatted = results.results.map((r, i) => {
13798
14148
  const relevance = r.type === "memory" ? r.similarity : r.score;
13799
14149
  const scoreStr = ` (score: ${relevance.toFixed(2)})`;
13800
- return `${i + 1}. ${r.content ?? "(no content)"}${scoreStr}`;
14150
+ const content = formatSearchContent(r);
14151
+ return `${i + 1}. ${content}${scoreStr}`;
13801
14152
  }).join(`
13802
14153
  `);
13803
- return `Found ${results.results.length} memories:
14154
+ return `Found ${results.results.length} results:
13804
14155
 
13805
14156
  ${formatted}`;
13806
14157
  }
@@ -13866,6 +14217,166 @@ ${formatted}`;
13866
14217
  }
13867
14218
  }
13868
14219
  });
14220
+ const momoIngestTool = tool({
14221
+ description: "Ingest text, URLs, and files through Momo's document pipeline (RAG + optional memory extraction).",
14222
+ args: {
14223
+ input: z.string().describe("Text, URL, or local file path to ingest"),
14224
+ inputType: z.enum(["auto", "text", "url", "file"]).optional().describe("How to interpret input (default: auto)"),
14225
+ scope: z.enum(["user", "project"]).optional().describe("Memory/document scope (default: project)"),
14226
+ extractMemories: z.boolean().optional().describe("Whether to extract memories from ingested content (default: true)"),
14227
+ metadataJson: z.string().optional().describe("Optional JSON object string attached as metadata"),
14228
+ contentType: z.string().optional().describe("Optional content type hint (e.g. audio/mpeg, video/webm)"),
14229
+ wait: z.boolean().optional().describe("Wait for ingestion to finish before returning (default: true)"),
14230
+ timeoutMs: z.number().optional().describe("Max wait timeout in milliseconds (default: 120000)"),
14231
+ pollIntervalMs: z.number().optional().describe("Polling interval while waiting (default: 1500)")
14232
+ },
14233
+ async execute(args, _context) {
14234
+ const client = requireMomo();
14235
+ const scope = args.scope ?? "project";
14236
+ const containerTag = resolveContainerTag(scope);
14237
+ const extractMemories = args.extractMemories ?? true;
14238
+ const wait = args.wait ?? true;
14239
+ const timeoutMs = args.timeoutMs ?? DEFAULT_INGEST_TIMEOUT_MS;
14240
+ const pollIntervalMs = args.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS;
14241
+ const parsedMetadata = parseMetadataJson(args.metadataJson);
14242
+ if ("error" in parsedMetadata)
14243
+ return `Error: ${parsedMetadata.error}`;
14244
+ const inferred = inferInputType(args.input, args.inputType);
14245
+ const ingestResult = inferred === "file" ? await uploadDocumentFromPath(args.input, {
14246
+ containerTag,
14247
+ metadata: parsedMetadata,
14248
+ extractMemories,
14249
+ contentType: args.contentType,
14250
+ timeoutMs
14251
+ }) : await client.documents.create({
14252
+ content: args.input,
14253
+ containerTag,
14254
+ extractMemories,
14255
+ contentType: args.contentType,
14256
+ metadata: parsedMetadata
14257
+ }, { timeoutMs });
14258
+ const documentId = ingestResult.documentId;
14259
+ const ingestionId = ingestResult.ingestionId;
14260
+ if (!wait) {
14261
+ return [
14262
+ `Ingestion queued (scope: ${scope}).`,
14263
+ `- inputType: ${inferred}`,
14264
+ `- documentId: ${documentId}`,
14265
+ `- ingestionId: ${ingestionId}`,
14266
+ `- extractMemories: ${extractMemories}`
14267
+ ].join(`
14268
+ `);
14269
+ }
14270
+ const status = await waitForIngestion(client, ingestionId, timeoutMs, pollIntervalMs);
14271
+ if (status.status !== "completed") {
14272
+ return [
14273
+ `Ingestion status: ${status.status} (scope: ${scope}).`,
14274
+ `- inputType: ${inferred}`,
14275
+ `- documentId: ${documentId}`,
14276
+ `- ingestionId: ${ingestionId}`,
14277
+ status.title ? `- title: ${status.title}` : undefined
14278
+ ].filter(Boolean).join(`
14279
+ `);
14280
+ }
14281
+ return formatDocumentResult(client, documentId, scope);
14282
+ }
14283
+ });
14284
+ const momoOcrTool = tool({
14285
+ description: "OCR an image file via Momo ingestion, then return extracted text preview and memory extraction status.",
14286
+ args: {
14287
+ filePath: z.string().describe("Local path to an image file"),
14288
+ scope: z.enum(["user", "project"]).optional().describe("Memory/document scope (default: project)"),
14289
+ extractMemories: z.boolean().optional().describe("Extract memories from OCR text (default: true)"),
14290
+ wait: z.boolean().optional().describe("Wait for OCR ingestion to finish (default: true)"),
14291
+ timeoutMs: z.number().optional().describe("Max wait timeout in milliseconds"),
14292
+ pollIntervalMs: z.number().optional().describe("Polling interval in milliseconds"),
14293
+ contentType: z.string().optional().describe("Optional content type hint (e.g. image/png)")
14294
+ },
14295
+ async execute(args, _context) {
14296
+ const client = requireMomo();
14297
+ const scope = args.scope ?? "project";
14298
+ const containerTag = resolveContainerTag(scope);
14299
+ const extractMemories = args.extractMemories ?? true;
14300
+ const wait = args.wait ?? true;
14301
+ const timeoutMs = args.timeoutMs ?? DEFAULT_INGEST_TIMEOUT_MS;
14302
+ const pollIntervalMs = args.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS;
14303
+ const ingest = await uploadDocumentFromPath(args.filePath, {
14304
+ containerTag,
14305
+ metadata: undefined,
14306
+ extractMemories,
14307
+ contentType: args.contentType,
14308
+ timeoutMs
14309
+ });
14310
+ if (!wait) {
14311
+ return [
14312
+ `OCR ingestion queued (scope: ${scope}).`,
14313
+ `- documentId: ${ingest.documentId}`,
14314
+ `- ingestionId: ${ingest.ingestionId}`,
14315
+ `- extractMemories: ${extractMemories}`
14316
+ ].join(`
14317
+ `);
14318
+ }
14319
+ const status = await waitForIngestion(client, ingest.ingestionId, timeoutMs, pollIntervalMs);
14320
+ if (status.status !== "completed") {
14321
+ return [
14322
+ `OCR ingestion status: ${status.status} (scope: ${scope}).`,
14323
+ `- documentId: ${ingest.documentId}`,
14324
+ `- ingestionId: ${ingest.ingestionId}`,
14325
+ status.title ? `- title: ${status.title}` : undefined
14326
+ ].filter(Boolean).join(`
14327
+ `);
14328
+ }
14329
+ return formatDocumentResult(client, ingest.documentId, scope, "ocr");
14330
+ }
14331
+ });
14332
+ const momoTranscribeTool = tool({
14333
+ description: "Transcribe audio/video files via Momo ingestion, then return transcript preview and memory extraction status.",
14334
+ args: {
14335
+ filePath: z.string().describe("Local path to an audio or video file"),
14336
+ scope: z.enum(["user", "project"]).optional().describe("Memory/document scope (default: project)"),
14337
+ extractMemories: z.boolean().optional().describe("Extract memories from transcript (default: true)"),
14338
+ wait: z.boolean().optional().describe("Wait for transcription ingestion to finish (default: true)"),
14339
+ timeoutMs: z.number().optional().describe("Max wait timeout in milliseconds"),
14340
+ pollIntervalMs: z.number().optional().describe("Polling interval in milliseconds"),
14341
+ contentType: z.string().optional().describe("Optional content type hint (e.g. audio/mpeg, video/mp4)")
14342
+ },
14343
+ async execute(args, _context) {
14344
+ const client = requireMomo();
14345
+ const scope = args.scope ?? "project";
14346
+ const containerTag = resolveContainerTag(scope);
14347
+ const extractMemories = args.extractMemories ?? true;
14348
+ const wait = args.wait ?? true;
14349
+ const timeoutMs = args.timeoutMs ?? DEFAULT_INGEST_TIMEOUT_MS;
14350
+ const pollIntervalMs = args.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS;
14351
+ const ingest = await uploadDocumentFromPath(args.filePath, {
14352
+ containerTag,
14353
+ metadata: undefined,
14354
+ extractMemories,
14355
+ contentType: args.contentType,
14356
+ timeoutMs
14357
+ });
14358
+ if (!wait) {
14359
+ return [
14360
+ `Transcription queued (scope: ${scope}).`,
14361
+ `- documentId: ${ingest.documentId}`,
14362
+ `- ingestionId: ${ingest.ingestionId}`,
14363
+ `- extractMemories: ${extractMemories}`
14364
+ ].join(`
14365
+ `);
14366
+ }
14367
+ const status = await waitForIngestion(client, ingest.ingestionId, timeoutMs, pollIntervalMs);
14368
+ if (status.status !== "completed") {
14369
+ return [
14370
+ `Transcription status: ${status.status} (scope: ${scope}).`,
14371
+ `- documentId: ${ingest.documentId}`,
14372
+ `- ingestionId: ${ingest.ingestionId}`,
14373
+ status.title ? `- title: ${status.title}` : undefined
14374
+ ].filter(Boolean).join(`
14375
+ `);
14376
+ }
14377
+ return formatDocumentResult(client, ingest.documentId, scope, "transcription");
14378
+ }
14379
+ });
13869
14380
  const chatMessageHook = async (input, output) => {
13870
14381
  if (injectedSessions.has(input.sessionID))
13871
14382
  return;
@@ -13921,7 +14432,10 @@ ${formatted}`;
13921
14432
  };
13922
14433
  return {
13923
14434
  tool: {
13924
- momo: momoTool
14435
+ momo: momoTool,
14436
+ momo_ingest: momoIngestTool,
14437
+ momo_ocr: momoOcrTool,
14438
+ momo_transcribe: momoTranscribeTool
13925
14439
  },
13926
14440
  "chat.message": chatMessageHook,
13927
14441
  event: eventHook
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@momomemory/opencode-momo",
3
- "version": "0.1.3",
3
+ "version": "0.1.5",
4
4
  "description": "OpenCode plugin that gives coding agents persistent memory using Momo",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -29,7 +29,8 @@
29
29
  },
30
30
  "dependencies": {
31
31
  "@opencode-ai/plugin": "^1.1.53",
32
- "@momomemory/sdk": "^0.2.0"
32
+ "@opencode-ai/sdk": "^1.1.53",
33
+ "@momomemory/sdk": "^0.2.1"
33
34
  },
34
35
  "devDependencies": {
35
36
  "@types/bun": "latest",