diff --git a/cli/node/AGENTS.md b/cli/node/AGENTS.md new file mode 100644 index 000000000..06d51f1be --- /dev/null +++ b/cli/node/AGENTS.md @@ -0,0 +1,41 @@ +# Node CLI (`cli/node/`) + +The `@mem0/cli` package on npm. Commander-based, entry point `mem0`. + +## Commands + +```bash +pnpm install +pnpm run build # tsup (ESM) +pnpm run lint # biome check src/ +pnpm run lint:fix # biome check --write src/ +pnpm run typecheck # tsc --noEmit +pnpm run test # vitest run +pnpm run test:watch +pnpm run dev # tsx src/index.ts +``` + +pnpm only. Never npm, never yarn. + +## Conventions + +> **Biome, not ESLint. vitest, not jest.** `mem0-ts/` uses Prettier + jest and +> `integrations/vercel-ai-sdk/` uses ESLint + jest. Running those tools here produces +> spurious diffs. Every toolchain in this repo is per-package. + +- **Node 18+** required. +- **Build:** tsup, ESM output only. +- **Linter and formatter:** Biome, configured in `biome.json`. +- **Tests:** vitest. +- **TypeScript strict mode.** ES module `import` syntax only, never `require()`. + +Run `pnpm run typecheck` after every change. + +## Dependencies + +Commander + Chalk + ora + cli-table3, and `mem0ai` (npm) for API calls. + +## CI and release + +- CI: `cli-node-ci.yml`, Biome + tsc + vitest + tsup build on Node 20 and 22. +- Release: tag prefix `cli-node-v*` dispatches `cli-node-cd.yml`, publishing to npm over OIDC. diff --git a/cli/node/CLAUDE.md b/cli/node/CLAUDE.md new file mode 120000 index 000000000..47dc3e3d8 --- /dev/null +++ b/cli/node/CLAUDE.md @@ -0,0 +1 @@ +AGENTS.md \ No newline at end of file diff --git a/cli/node/src/backend/base.ts b/cli/node/src/backend/base.ts index d66a3519d..073c9c5a4 100644 --- a/cli/node/src/backend/base.ts +++ b/cli/node/src/backend/base.ts @@ -15,6 +15,7 @@ export interface AddOptions { infer?: boolean; expires?: string; customInstructions?: string; + agentCustomInstructions?: string; customCategories?: Record[]; structuredDataSchema?: Record; timestamp?: number; diff --git a/cli/node/src/backend/platform.ts b/cli/node/src/backend/platform.ts index f53b5dddc..705a3d470 100644 --- a/cli/node/src/backend/platform.ts +++ b/cli/node/src/backend/platform.ts @@ -153,6 +153,8 @@ export class PlatformBackend implements Backend { if (opts.expires) payload.expiration_date = opts.expires; if (opts.customInstructions) payload.custom_instructions = opts.customInstructions; + if (opts.agentCustomInstructions) + payload.agent_custom_instructions = opts.agentCustomInstructions; if (opts.customCategories) payload.custom_categories = opts.customCategories; if (opts.structuredDataSchema) diff --git a/cli/node/src/commands/memory.ts b/cli/node/src/commands/memory.ts index 3f5e5e621..5f1bc984b 100644 --- a/cli/node/src/commands/memory.ts +++ b/cli/node/src/commands/memory.ts @@ -53,6 +53,7 @@ export async function cmdAdd( expires?: string; categories?: string; customInstructions?: string; + agentCustomInstructions?: string; customCategories?: string; structuredDataSchema?: string; timestamp?: number; @@ -153,6 +154,7 @@ export async function cmdAdd( infer: opts.infer !== false, expires: opts.expires, customInstructions: opts.customInstructions, + agentCustomInstructions: opts.agentCustomInstructions, customCategories: customCats, structuredDataSchema: schema, timestamp: opts.timestamp, diff --git a/cli/node/src/index.ts b/cli/node/src/index.ts index d102b5a33..bb6462a37 100644 --- a/cli/node/src/index.ts +++ b/cli/node/src/index.ts @@ -328,6 +328,10 @@ program "--custom-instructions ", "Custom instructions for fact extraction.", ) + .option( + "--agent-custom-instructions ", + "Extraction instructions for agent-scoped memories, overriding the project setting.", + ) .option( "--custom-categories ", "Custom categories as a JSON array of {name: description} objects.", diff --git a/cli/node/tests/option-parity.test.ts b/cli/node/tests/option-parity.test.ts index 4a0ff9b90..be8e653e0 100644 --- a/cli/node/tests/option-parity.test.ts +++ b/cli/node/tests/option-parity.test.ts @@ -50,6 +50,7 @@ const ADD_MAPPING: Record = { metadata: ["--metadata"], expiration_date: ["--expires"], custom_instructions: ["--custom-instructions"], + agent_custom_instructions: ["--agent-custom-instructions"], custom_categories: ["--custom-categories"], infer: ["--no-infer"], immutable: ["--immutable"], diff --git a/cli/node/tests/platform-backend.test.ts b/cli/node/tests/platform-backend.test.ts index 62c71305a..f186b24bb 100644 --- a/cli/node/tests/platform-backend.test.ts +++ b/cli/node/tests/platform-backend.test.ts @@ -84,6 +84,7 @@ describe("PlatformBackend option-parity payloads (MEM-5893)", () => { metadata: { source: "test" }, expires: "2099-01-01", customInstructions: "Extract only preferences.", + agentCustomInstructions: "Extract only tool outcomes.", customCategories: [{ prefs: "user preferences" }], structuredDataSchema: { type: "object" }, timestamp: 1700000000, @@ -91,6 +92,9 @@ describe("PlatformBackend option-parity payloads (MEM-5893)", () => { const payload = spy.mock.calls[0][2].json; expect(payload.custom_instructions).toBe("Extract only preferences."); + expect(payload.agent_custom_instructions).toBe( + "Extract only tool outcomes.", + ); expect(payload.custom_categories).toEqual([{ prefs: "user preferences" }]); expect(payload.structured_data_schema).toEqual({ type: "object" }); expect(payload.timestamp).toBe(1700000000); @@ -109,6 +113,7 @@ describe("PlatformBackend option-parity payloads (MEM-5893)", () => { const payload = spy.mock.calls[0][2].json; expect(payload).not.toHaveProperty("custom_instructions"); + expect(payload).not.toHaveProperty("agent_custom_instructions"); expect(payload).not.toHaveProperty("custom_categories"); expect(payload).not.toHaveProperty("structured_data_schema"); expect(payload).not.toHaveProperty("timestamp"); diff --git a/cli/python/AGENTS.md b/cli/python/AGENTS.md new file mode 100644 index 000000000..91d70e2d0 --- /dev/null +++ b/cli/python/AGENTS.md @@ -0,0 +1,47 @@ +# Python CLI (`cli/python/`) + +The `mem0-cli` package on PyPI. Typer-based, entry point `mem0`. + +## Commands + +```bash +pip install -e ".[dev]" # dev install: ruff + pytest +ruff check . # lint +ruff format . # format +pytest # test +hatch build # build +``` + +## Conventions + +> **Line length is 100 here, not 120.** The root Python SDK uses 120. Running the root +> `make format` over this directory reformats every file and fails CI. Use the local +> `ruff` invocations above. + +- **Python 3.10+.** Not 3.9, unlike the root SDK. +- **Ruff** with an extended rule set: `E`, `F`, `I`, `W`, `UP`, `B`, `SIM`, `RUF`. + Ignores `E501` (formatter handles it), `B008` (required by Typer's argument defaults), + and `SIM108`. +- **Ruff format:** double quotes, space indent, `docstring-code-format = true`. +- **isort** first-party is `mem0_cli` only. +- **pytest** for tests. +- Target version pinned to `py310`. + +## Layout + +``` +cli/python/ +├── src/mem0_cli/ package source (src layout) +└── tests/ +``` + +Entry point: `mem0 = "mem0_cli.app:main"`. + +## Dependencies + +Typer + Rich + httpx. `mem0ai` is **optional**, exposed through the `[oss]` extra for OSS mode. Do not promote it to a required dependency. + +## CI and release + +- CI: `cli-python-ci.yml`, ruff + pytest + `hatch build` on Python 3.10, 3.11, 3.12. +- Release: tag prefix `cli-v*` dispatches `cli-python-cd.yml`, publishing to PyPI over OIDC. diff --git a/cli/python/CLAUDE.md b/cli/python/CLAUDE.md new file mode 120000 index 000000000..47dc3e3d8 --- /dev/null +++ b/cli/python/CLAUDE.md @@ -0,0 +1 @@ +AGENTS.md \ No newline at end of file diff --git a/cli/python/src/mem0_cli/app.py b/cli/python/src/mem0_cli/app.py index d3258bf6a..7bfa76dd9 100644 --- a/cli/python/src/mem0_cli/app.py +++ b/cli/python/src/mem0_cli/app.py @@ -278,6 +278,11 @@ def add( custom_instructions: str | None = typer.Option( None, "--custom-instructions", help="Custom instructions for fact extraction." ), + agent_custom_instructions: str | None = typer.Option( + None, + "--agent-custom-instructions", + help="Extraction instructions for agent-scoped memories, overriding the project setting.", + ), custom_categories: str | None = typer.Option( None, "--custom-categories", @@ -327,6 +332,7 @@ def add( expires=expires, categories=categories, custom_instructions=custom_instructions, + agent_custom_instructions=agent_custom_instructions, custom_categories=custom_categories, structured_data_schema=structured_data_schema, timestamp=timestamp, @@ -1139,6 +1145,7 @@ def _build_help_json() -> dict: "--expires": "Expiration date (YYYY-MM-DD).", "--categories": "Not supported on add, use --custom-categories instead.", "--custom-instructions": "Custom instructions for fact extraction.", + "--agent-custom-instructions": "Extraction instructions for agent-scoped memories, overriding the project setting.", "--custom-categories": "Custom categories as a JSON array of {name: description} objects.", "--structured-data-schema": "Schema for structured data extraction, as JSON.", "--timestamp": "Unix timestamp for the memory.", diff --git a/cli/python/src/mem0_cli/backend/base.py b/cli/python/src/mem0_cli/backend/base.py index e5788b056..3db5cf238 100644 --- a/cli/python/src/mem0_cli/backend/base.py +++ b/cli/python/src/mem0_cli/backend/base.py @@ -26,6 +26,7 @@ class Backend(ABC): infer: bool = True, expires: str | None = None, custom_instructions: str | None = None, + agent_custom_instructions: str | None = None, custom_categories: list[dict] | None = None, structured_data_schema: dict | None = None, timestamp: int | None = None, diff --git a/cli/python/src/mem0_cli/backend/platform.py b/cli/python/src/mem0_cli/backend/platform.py index 083c804e6..1d153da9c 100644 --- a/cli/python/src/mem0_cli/backend/platform.py +++ b/cli/python/src/mem0_cli/backend/platform.py @@ -88,6 +88,7 @@ class PlatformBackend(Backend): infer: bool = True, expires: str | None = None, custom_instructions: str | None = None, + agent_custom_instructions: str | None = None, custom_categories: list[dict] | None = None, structured_data_schema: dict | None = None, timestamp: int | None = None, @@ -117,6 +118,8 @@ class PlatformBackend(Backend): payload["expiration_date"] = expires if custom_instructions: payload["custom_instructions"] = custom_instructions + if agent_custom_instructions: + payload["agent_custom_instructions"] = agent_custom_instructions if custom_categories: payload["custom_categories"] = custom_categories if structured_data_schema: diff --git a/cli/python/src/mem0_cli/commands/memory.py b/cli/python/src/mem0_cli/commands/memory.py index 315cde280..1771d8d20 100644 --- a/cli/python/src/mem0_cli/commands/memory.py +++ b/cli/python/src/mem0_cli/commands/memory.py @@ -77,6 +77,7 @@ def cmd_add( expires: str | None, categories: str | None, custom_instructions: str | None = None, + agent_custom_instructions: str | None = None, custom_categories: str | None = None, structured_data_schema: str | None = None, timestamp: int | None = None, @@ -166,6 +167,7 @@ def cmd_add( infer=not no_infer, expires=expires, custom_instructions=custom_instructions, + agent_custom_instructions=agent_custom_instructions, custom_categories=custom_cats, structured_data_schema=schema, timestamp=timestamp, diff --git a/cli/python/tests/test_option_parity.py b/cli/python/tests/test_option_parity.py index e7bc575f3..3c8821857 100644 --- a/cli/python/tests/test_option_parity.py +++ b/cli/python/tests/test_option_parity.py @@ -38,6 +38,7 @@ ADD_MAPPING: dict[str, list[str]] = { "metadata": ["metadata"], "expiration_date": ["expires"], "custom_instructions": ["custom_instructions"], + "agent_custom_instructions": ["agent_custom_instructions"], "custom_categories": ["custom_categories"], "infer": ["no_infer"], "immutable": ["immutable"], diff --git a/cli/python/tests/test_platform_backend_options.py b/cli/python/tests/test_platform_backend_options.py index 9559864d2..9d6c25fdf 100644 --- a/cli/python/tests/test_platform_backend_options.py +++ b/cli/python/tests/test_platform_backend_options.py @@ -22,12 +22,14 @@ class TestAddOptions: metadata={"source": "test"}, expires="2099-01-01", custom_instructions="Extract only preferences.", + agent_custom_instructions="Extract only tool outcomes.", custom_categories=[{"prefs": "user preferences"}], structured_data_schema={"type": "object"}, timestamp=1700000000, ) payload = mock_request.call_args.kwargs["json"] assert payload["custom_instructions"] == "Extract only preferences." + assert payload["agent_custom_instructions"] == "Extract only tool outcomes." assert payload["custom_categories"] == [{"prefs": "user preferences"}] assert payload["structured_data_schema"] == {"type": "object"} assert payload["timestamp"] == 1700000000 @@ -40,6 +42,7 @@ class TestAddOptions: backend.add(content="hello", user_id="alice") payload = mock_request.call_args.kwargs["json"] assert "custom_instructions" not in payload + assert "agent_custom_instructions" not in payload assert "custom_categories" not in payload assert "structured_data_schema" not in payload assert "timestamp" not in payload