@3ngram/cli is a thin command-line client over @3ngram/sdk and the REST /api/v1 surface. It is workspace-only before v1.0; the examples use the eventual 3ngram bin name, while local source runs can use node apps/cli/dist/index.js after building the package.
The CLI uses API keys, not MCP OAuth. Pass --api-key or set THREENGRAM_API_KEY; the key is sent as X-API-Key and is never printed in error output.
Configuration
Flags override environment variables. --base-url expects the REST origin without /api/v1.
| Name | Description |
|---|
--base-url <url> | REST origin, without /api/v1. Overrides THREENGRAM_BASE_URL. |
--api-key <key> | API key sent as X-API-Key. Overrides THREENGRAM_API_KEY. |
--json | Print the raw SDK response instead of human-readable output. |
THREENGRAM_BASE_URL | Default REST origin when —base-url is omitted. |
THREENGRAM_API_KEY | Default API key when —api-key is omitted. |
Commands
remember
Append a typed memory through the REST-backed SDK client.
Usage: 3ngram remember --type <t> --topic <t> --content <c> [--scope --project --tags]
SDK call: client.remember(input)
Options
| Option | Required | Repeatable | Description |
|---|
--type <memory-type> | yes | no | Memory type: decision, commitment, blocker, fact, preference, pattern, note, or event. |
--topic <topic> | yes | no | Short title for the memory. |
--content <text> | yes | no | Memory body to persist. |
--scope <scope> | no | no | Optional scope such as personal or work. |
--project <project> | no | no | Optional project key for project briefings. |
--tags <tag[,tag]> | no | yes | Repeatable; comma-separated values are flattened and trimmed. |
Examples
3ngram remember --type decision --topic "search backend" --content "Use Postgres full-text search for v1." --scope work --project 3ngram
3ngram remember --type note --topic "follow-up" --content "Check docs freshness gate." --tags docs,ci --json
{
"type": "object",
"properties": {
"memoryType": {
"type": "string",
"enum": [
"decision",
"commitment",
"blocker",
"fact",
"preference",
"pattern",
"note",
"event"
]
},
"topic": {
"type": "string",
"minLength": 1,
"maxLength": 256
},
"content": {
"type": "string",
"minLength": 1,
"maxLength": 2000
},
"scope": {
"default": "personal",
"type": "string",
"minLength": 1,
"maxLength": 64,
"pattern": "^[a-z0-9][a-z0-9-]*$"
},
"project": {
"type": "string",
"minLength": 1,
"maxLength": 256
},
"tags": {
"default": [],
"maxItems": 32,
"type": "array",
"items": {
"type": "string",
"minLength": 1,
"maxLength": 64
}
}
},
"required": [
"memoryType",
"topic",
"content"
],
"additionalProperties": false
}
Output schema
{
"type": "object",
"properties": {
"memory": {
"type": "object",
"properties": {
"id": {
"type": "string",
"format": "uuid",
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
},
"memoryType": {
"type": "string",
"enum": [
"decision",
"commitment",
"blocker",
"fact",
"preference",
"pattern",
"note",
"event"
]
},
"topic": {
"type": "string"
},
"scope": {
"type": "string",
"minLength": 1,
"maxLength": 64,
"pattern": "^[a-z0-9][a-z0-9-]*$"
},
"project": {
"anyOf": [
{
"type": "string",
"minLength": 1,
"maxLength": 256
},
{
"type": "null"
}
]
}
},
"required": [
"id",
"memoryType",
"topic",
"scope",
"project"
],
"additionalProperties": false
},
"embedded": {
"type": "string",
"enum": [
"pending",
"done",
"failed",
"off"
]
},
"commitmentId": {
"type": "string",
"format": "uuid",
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
}
},
"required": [
"memory",
"embedded"
],
"additionalProperties": false
}
search
Search memories with optional filters before result fusion.
Usage: 3ngram search <query> [--limit --type --scope --project --status]
SDK call: client.search(query, options)
Options
| Option | Required | Repeatable | Description |
|---|
<query> | yes | no | Positional search text. Quote multi-word queries. |
--query <query> | no | no | Alternative to the positional query; wins when both are present. |
--limit <n> | no | no | Maximum result count. |
--type <memory-type> | no | no | Maps to the SDK memoryType filter. |
--scope <scope> | no | no | Restrict search to one scope. |
--project <project> | no | no | Restrict search to one project. |
--status <active|archived> | no | no | Filter by supersession-aware memory status. |
Examples
3ngram search "oauth refresh token" --scope work --project 3ngram --limit 5
3ngram search --query "design decision" --type decision --json
{
"type": "object",
"properties": {
"query": {
"type": "string",
"minLength": 1
},
"limit": {
"default": 5,
"type": "integer",
"minimum": 1,
"maximum": 25
},
"memoryType": {
"type": "string",
"enum": [
"decision",
"commitment",
"blocker",
"fact",
"preference",
"pattern",
"note",
"event"
]
},
"scope": {
"type": "string",
"minLength": 1,
"maxLength": 64,
"pattern": "^[a-z0-9][a-z0-9-]*$"
},
"project": {
"type": "string",
"minLength": 1,
"maxLength": 256
},
"status": {
"type": "string",
"enum": [
"active",
"archived"
]
}
},
"required": [
"query"
],
"additionalProperties": false
}
Output schema
{
"type": "object",
"properties": {
"hits": {
"maxItems": 25,
"type": "array",
"items": {
"type": "object",
"properties": {
"id": {
"type": "string",
"format": "uuid",
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
},
"memoryType": {
"type": "string"
},
"topic": {
"type": "string"
},
"content": {
"type": "string",
"maxLength": 600
},
"contentLength": {
"type": "integer",
"minimum": 0,
"maximum": 9007199254740991
},
"truncated": {
"type": "boolean"
},
"score": {
"type": "number"
}
},
"required": [
"id",
"memoryType",
"topic",
"content",
"contentLength",
"truncated",
"score"
],
"additionalProperties": false
}
},
"count": {
"type": "integer",
"minimum": 0,
"maximum": 9007199254740991
}
},
"required": [
"hits",
"count"
],
"additionalProperties": false
}
facts
Read currently-valid facts, optionally with bi-temporal filters.
Usage: 3ngram facts [--subject --predicate --valid-at --as-known-at --limit]
SDK call: client.getFacts(filters)
Options
| Option | Required | Repeatable | Description |
|---|
--subject <subject> | no | no | Restrict facts to one subject. |
--predicate <predicate> | no | no | Restrict facts to one predicate. |
--valid-at <iso-date> | no | no | World-time coordinate for bi-temporal lookup. |
--as-known-at <iso-date> | no | no | Knowledge-time coordinate for bi-temporal lookup. |
--limit <n> | no | no | Maximum fact count. |
Examples
3ngram facts --subject 3ngram --predicate status
3ngram facts --subject "search backend" --valid-at 2026-06-01T00:00:00.000Z --json
{
"type": "object",
"properties": {
"subject": {
"type": "string",
"minLength": 1
},
"predicate": {
"type": "string",
"minLength": 1
},
"asOf": {
"type": "object",
"properties": {
"validAt": {
"type": "string",
"format": "date-time",
"pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$"
},
"asKnownAt": {
"type": "string",
"format": "date-time",
"pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$"
}
},
"additionalProperties": false
},
"limit": {
"default": 50,
"type": "integer",
"minimum": 1,
"maximum": 200
}
},
"additionalProperties": false
}
Output schema
{
"type": "object",
"properties": {
"facts": {
"type": "array",
"items": {
"type": "object",
"properties": {
"id": {
"type": "string",
"format": "uuid",
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
},
"subject": {
"type": "string"
},
"predicate": {
"type": "string"
},
"value": {
"type": "string"
},
"confidence": {
"anyOf": [
{
"type": "number"
},
{
"type": "null"
}
]
},
"validFrom": {
"type": "string",
"format": "date-time",
"pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$"
},
"validTo": {
"anyOf": [
{
"type": "string",
"format": "date-time",
"pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$"
},
{
"type": "null"
}
]
}
},
"required": [
"id",
"subject",
"predicate",
"value",
"confidence",
"validFrom",
"validTo"
],
"additionalProperties": false
}
},
"count": {
"type": "integer",
"minimum": 0,
"maximum": 9007199254740991
}
},
"required": [
"facts",
"count"
],
"additionalProperties": false
}
Exit behavior
0: command completed successfully.
1: no command was supplied, the server rejected the request, or the transport failed.
2: CLI usage or configuration error, including missing required flags.