Skip to content

Latest commit

 

History

History
693 lines (532 loc) · 21.1 KB

File metadata and controls

693 lines (532 loc) · 21.1 KB

CLI Reference

Complete reference for all vipune commands.

Global Flags

These flags apply to all commands:

Flag Short Description
--json Output as JSON (pretty-printed) instead of human-readable text
--project <id> -p Project identifier (auto-detected from git if omitted)
--db-path <path> Override database path

Commands

add

Store a memory.

vipune add <text> [--metadata <json>] [--force] [--memory-type <type>] [--status <status>] [--supersedes <id>]

Arguments:

  • text - Memory text content (required)

Flags:

  • -m, --metadata <json> - Optional JSON metadata (e.g., {"topic": "auth"})
  • --force - Bypass conflict detection and add regardless
  • --memory-type <type> - Memory type: fact (default), preference, procedure, guard, observation
  • --status <status> - Initial status: active (default) or candidate
  • --supersedes <id> - Atomically supersede an existing memory (mutually exclusive with --force)

Behavior:

  • Generates semantic embedding for the text
  • Checks for similar existing memories (similarity ≥ threshold)
  • If conflicts found: returns exit code 2, lists conflicting memories
  • If --force used: skips conflict check and adds memory
  • If --supersedes used: atomically adds new memory and marks target as superseded

Exit codes:

  • 0 - Successfully added
  • 1 - Error (invalid input, database error)
  • 2 - Conflicts detected (similar memories exist)
  • 3 - Content too long (exceeds embedding token limit)

Human output:

Added memory: 123e4567-e89b-12d3-a456-426614174000

Conflicts output:

Conflicts detected: 1 similar memory/memories found
Proposed: Authentication uses OAuth2
Use --force to add anyway
  123e4567-e89b-12d3-a456-426614174000 (similarity: 0.94)
    Auth system uses OAuth2 for login

JSON output (success):

{
  "status": "added",
  "id": "123e4567-e89b-12d3-a456-426614174000"
}

JSON output (conflicts):

{
  "status": "conflicts",
  "proposed": "Authentication uses OAuth2",
  "conflicts": [
    {
      "id": "123e4567-e89b-12d3-a456-426614174000",
      "content": "Auth system uses OAuth2 for login",
      "similarity": 0.94
    }
  ]
}

Supersede example:

# Replace an outdated memory atomically
vipune add "Alice now works at Google" --supersedes abc123-old-memory-id

This inserts the new memory and marks the old one as superseded in a single transaction.


search

Find memories by semantic similarity.

vipune search <query> [--limit <n>] [--recency <weight>] [--hybrid] [--memory-type <types>] [--status <statuses>] [--include-candidates]

Arguments:

  • query - Search query text (required)

Flags:

  • -l, --limit <n> - Maximum results to return (default: 5)
  • --recency <weight> - Recency bias for scoring, 0.0 to 1.0 (default: from config, typically 0.3)
  • --hybrid - Enables hybrid search combining semantic similarity with FTS5 full-text search using Reciprocal Rank Fusion (RRF)
  • --no-hybrid - Disables hybrid search even when enabled in config (useful to force pure semantic search)
  • --memory-type <types> - Filter by memory type (comma-separated, e.g., guard,procedure)
  • --status <statuses> - Filter by status (comma-separated, e.g., active,candidate)
  • --include-candidates - Shorthand to include both active and candidate memories
  • --no-touch - Do not update retrieval telemetry (retrieval_count, last_retrieved_at) for the returned memories (default: telemetry is updated)

Behavior:

  • Generates embedding for query
  • Unless --no-touch is set, increments retrieval_count and sets last_retrieved_at for every returned memory (see Retrieval telemetry)
  • Finds memories with highest cosine similarity
  • Combines semantic similarity with time decay for final score
  • Returns results sorted by final score (highest first)
  • All memories in current project scope
  • Default filtering: Only active memories are returned by default. Use --status or --include-candidates to include other statuses.

Recency scoring: The final score combines: (1 - recency_weight) * similarity + recency_weight * time_score

  • recency_weight = 0.0: Pure semantic similarity
  • recency_weight = 1.0: Pure recency (newest first)
  • recency_weight = 0.3: Default balance (70% semantic, 30% recency)

Exit codes:

  • 0 - Success (may return empty results if no matches)

Human output:

123e4567-e89b-12d3-a456-426614174000 [score: 0.95]
  Alice works at Microsoft as a senior engineer

234e5678-e89b-12d3-a456-426614174001 [score: 0.87]
  Bob is a software engineer at Google

JSON output:

{
  "results": [
    {
      "id": "123e4567-e89b-12d3-a456-426614174000",
      "content": "Alice works at Microsoft as a senior engineer",
      "similarity": 0.95,
      "created_at": "2024-01-15T10:30:00Z",
      "retrieval_count": 12,
      "last_retrieved_at": "2024-01-20T09:15:00Z",
      "memory_type": "fact",
      "status": "active"
    },
    {
      "id": "234e5678-e89b-12d3-a456-426614174001",
      "content": "Bob is a software engineer at Google",
      "similarity": 0.87,
      "created_at": "2024-01-16T14:20:00Z",
      "retrieval_count": 0,
      "last_retrieved_at": null,
      "memory_type": "fact",
      "status": "active"
    }
  ]
}

Recency example:

# Default recency balance (0.3)
vipune search "authentication"

# High recency bias (recent memories rank higher)
vipune search "authentication" --recency 0.7

# Pure semantic similarity (no time bias)
vipune search "authentication" --recency 0.0

get

Retrieve a memory by ID.

vipune get <id> [--no-touch]

Arguments:

  • id - Memory ID (required)

Flags:

  • --no-touch - Do not update retrieval telemetry (retrieval_count, last_retrieved_at) for the retrieved memory (default: telemetry is updated)

Exit codes:

  • 0 - Memory found
  • 1 - Memory not found or error

Human output:

ID: 123e4567-e89b-12d3-a456-426614174000
Content: Alice works at Microsoft as a senior engineer
Project: git@github.com:user/repo.git
Metadata: {"topic": "team"}
Created: 2024-01-15T10:30:00Z
Updated: 2024-01-15T10:30:00Z

JSON output:

{
  "id": "123e4567-e89b-12d3-a456-426614174000",
  "content": "Alice works at Microsoft as a senior engineer",
  "project_id": "git@github.com:user/repo.git",
  "metadata": "{\"topic\": \"team\"}",
  "created_at": "2024-01-15T10:30:00Z",
  "updated_at": "2024-01-15T10:30:00Z",
  "retrieval_count": 5,
  "last_retrieved_at": "2024-01-15T10:30:00Z",
  "memory_type": "fact",
  "status": "active"
}
  • retrieval_count / last_retrieved_at reflect retrieval telemetry (see --no-touch under search). Note that a get without --no-touch increments the counter before the JSON is emitted, so the value shown is the post-retrieval count.

list

List all memories in the current project.

vipune list [--limit <n>] [--memory-type <types>] [--status <statuses>] [--include-candidates]

Flags:

  • -l, --limit <n> - Maximum results to return (default: 10)
  • --memory-type <types> - Filter by memory type (comma-separated, e.g., guard,procedure)
  • --status <statuses> - Filter by status (comma-separated, e.g., active,candidate)
  • --include-candidates - Shorthand to include both active and candidate memories

Behavior:

  • Returns memories ordered by creation time (newest first)
  • Limited to current project scope
  • Default filtering: Only active memories are returned by default. Use --status or --include-candidates to include other statuses.
  • Listing does not update retrieval telemetry — retrieval_count and last_retrieved_at are touched only by search and get (and the equivalent MCP tools). The list command has no --no-touch flag because there is nothing to suppress.

Exit codes:

  • 0 - Success (may return empty list)

Human output:

123e4567-e89b-12d3-a456-426614174000: Alice works at Microsoft
234e5678-e89b-12d3-a456-426614174001: Bob is a software engineer at Google

JSON output:

{
  "memories": [
    {
      "id": "123e4567-e89b-12d3-a456-426614174000",
      "content": "Alice works at Microsoft",
      "created_at": "2024-01-15T10:30:00Z",
      "retrieval_count": 3,
      "last_retrieved_at": "2024-01-17T08:45:00Z",
      "memory_type": "fact",
      "status": "active"
    },
    {
      "id": "234e5678-e89b-12d3-a456-426614174001",
      "content": "Bob is a software engineer at Google",
      "created_at": "2024-01-16T14:20:00Z",
      "retrieval_count": 0,
      "last_retrieved_at": null,
      "memory_type": "preference",
      "status": "active"
    }
  ]
}

delete

Delete a memory by ID.

vipune delete <id>

Arguments:

  • id - Memory ID (required)

Exit codes:

  • 0 - Memory deleted
  • 1 - Memory not found or error

Human output:

Deleted memory: 123e4567-e89b-12d3-a456-426614174000

JSON output:

{
  "status": "deleted",
  "id": "123e4567-e89b-12d3-a456-426614174000"
}

update

Update a memory's content.

vipune update <id> [text] [--metadata <json>]

Arguments:

  • id - Memory ID (required)
  • text - New content (optional — if omitted, only metadata is updated)

Flags:

  • -m, --metadata <json> - Replace metadata (must be valid JSON)

Behavior:

  • If only text provided: generates new embedding, preserves metadata
  • If only --metadata provided: updates metadata without re-embedding
  • If both text and --metadata provided: updates both
  • Metadata must be valid JSON (validated on write)

Exit codes:

  • 0 - Memory updated
  • 1 - Memory not found or error

Human output:

Updated memory: 123e4567-e89b-12d3-a456-426614174000

JSON output:

{
  "status": "updated",
  "id": "123e4567-e89b-12d3-a456-426614174000"
}

validate

Check if text content is within the embedding model's token limit.

vipune validate <text>

Arguments:

  • text - Text to validate (required)

Exit codes:

  • 0 - Within token limit
  • 3 - Content exceeds token limit

Human output:

Token count: 42/512 — within limit

JSON output:

{
  "token_count": 42,
  "max_tokens": 512,
  "within_limit": true
}

version

Display version information.

vipune version

Exit codes:

  • 0 - Success

Human output:

vipune 0.1.1

JSON output:

{
  "version": "0.1.1",
  "name": "vipune"
}

Note: Output version matches your installed version. Update this example when upgrading vipune.


mcp

Start vipune as an MCP (Model Context Protocol) server over stdio. This enables AI agents like Claude Code and Cursor to use vipune as a native memory provider.

vipune mcp

Behavior:

  • Starts MCP server on stdin/stdout
  • Exposes three tools: store_memory, search_memories, list_memories
  • Automatically detects project from git repository
  • Uses default database path (~/.vipune/memories.db)

Available Tools:

Tool Description
store_memory Store information for later recall
search_memories Find memories by meaning, with type/status filters
list_memories List recent memories, with type/status filters
supersede_memory Replace an existing memory with new content
get_memory Retrieve a specific memory by ID
delete_memory Delete a memory permanently by ID
update_memory Update an existing memory's content, metadata, type, or status

Exit codes:

  • 0 - Success
  • 1 - Error (initialization failed)

New v0.3 tool parameters:

store_memory accepts:

  • text — the information to remember (required)
  • metadata — optional structured labels as JSON (e.g., {"topic": "auth"})
  • memory_type — type of memory: fact (default), preference, procedure, guard, observation
  • status — initial status: active (default) or candidate
  • supersedes — ID of memory to supersede (atomically replaces old memory)
  • force — force store even if conflicts detected (default: false)

supersede_memory accepts:

  • new_text — the new content that replaces the old memory (required)
  • old_memory_id — ID of the memory to supersede (required)
  • memory_type — optional type for the new memory (default: fact)
  • metadata — optional structured labels as JSON

get_memory accepts:

  • id — ID of the memory to retrieve (required)
  • no_touch — skip updating retrieval telemetry (default: false)

delete_memory accepts:

  • id — ID of the memory to delete (required)

update_memory accepts:

  • id — ID of the memory to update (required)
  • text — optional new content for the memory
  • metadata — optional new metadata as JSON object
  • memory_type — optional new memory type
  • status — optional new status: active or candidate (At least one optional field must be provided)

search_memories and list_memories accept optional:

  • memory_types — array of types to filter by (e.g., ["guard", "procedure"])
  • statuses — array of statuses to filter by (e.g., ["active", "candidate"])
  • no_touch — skip updating retrieval telemetry (default: false)

search_memories also accepts:

  • recency_weight — recency bias for scoring, 0.0 to 1.0 (default: config value)
  • hybrid — use hybrid search (semantic + BM25), true/false (default: config value)

Example MCP configuration (Claude Code):

{
  "mcpServers": {
    "vipune": {
      "command": "vipune",
      "args": ["mcp"]
    }
  }
}

Upgrading from v0.2

v0.3 includes automatic schema migrations — no manual steps required. On first run after upgrading, vipune will add the new type, status, and superseded_by columns to your existing database. All existing memories default to type fact and status active.

Breaking change: Content that previously was silently truncated when exceeding the embedding token limit now fails with exit code 3. Use vipune validate <text> to check content length before adding.

Retrieval telemetry

search and get maintain two telemetry columns per memory: retrieval_count (how many times the memory has been returned by search or get) and last_retrieved_at (timestamp of the most recent retrieval). Both commands accept --no-touch to skip updating them — useful for reads that should not influence ranking or promotion signals.

search and get include retrieval_count and last_retrieved_at in their JSON output (--json). Retrieval via list does not count as a retrieval and never touches telemetry.


Exit Codes Summary

Code Meaning
0 Success
1 Error (invalid input, database error, not found)
2 Conflicts detected (similar memories exist)
3 Content too long (exceeds embedding token limit)
64 Usage error (invalid flag or argument — clap argument-parsing failure)

Note: Argument-parsing errors exit 64 (EX_USAGE from the sysexits convention), so exit code 2 unambiguously means "conflicts detected". Previously, usage errors also exited 2, which collided with the conflict code — integrations that branched on exit code alone could not distinguish a semantic conflict from a typo'd flag.


Project Detection

vipune automatically detects projects from git repositories. When inside a git repo, the project ID is inferred from the remote origin URL.

To override project scope:

vipune add "Memory for specific project" --project "my-custom-project"

Project ID examples:

  • Inside ~/projects/myapp/.git: git@github.com:user/myapp.git
  • No git repository: default (all memories share default scope)

Migrating nested-namespace projects

Repositories with a nested namespace in their remote URL can resolve to different project_ids depending on the URL form, silently fragmenting recall. This affects GitLab subgroups, Gitea organisations, self-hosted Forgejo, and Azure DevOps — any host where the path has more than two segments. Plain github.com/owner/repo (two segments) is unaffected because both URL forms agree.

How the split happens: vipune derives the project_id from the origin remote. Before the canonicalisation fix (see #164), the two URL forms used different truncation rules for nested paths:

Remote URL Old project_id
https://gitlab.example.com/group/subgroup/project.git subgroup/project
git@gitlab.example.com:group/subgroup/project.git group/subgroup/project

If you switched the remote between HTTPS and SSH — or cloned one way on one machine and the other way elsewhere — the same repository's memories got filed under two different ids. A search scoped to one cannot see the other, and nothing signals it.

Canonical rule going forward: for both URL forms, vipune takes the last two path segments (subgroup/project). The SSH-shortcut and :// forms now agree, so switching remotes no longer forks your memories. Note this is a breaking change to existing ids for affected users — the id for a repo that previously resolved via its remote will change (see the project CHANGELOG).

Migration: repair existing splits. The detect-and-repair tooling from #158 Phase 2 makes this safe rather than a silent re-fork.

  1. Detect suspected splits. doctor --projects scans all project ids in the database (it ignores -p/--project, because a split spans two ids by definition) and reports pairs where one bare id equals a segment of another, with row counts for each side:

    vipune doctor --projects
    Suspected project splits:
    
      'pi-an' (8 rows)  +  'randomm/pi-an' (2 rows)
    
    These are suspected pairs — confirm they represent the same repository before merging.
    

    Pairs are suspected only — they require human confirmation before merging. Known false positives include a genuinely separate project whose directory name matches another project's repo name (ci-runner vs team/ci-runner).

  2. Merge confirmed pairs. Move all rows from one id to the other, in a single transaction:

    vipune project merge <from> <to>
    vipune project merge group/subgroup/project subgroup/project
    
    Merged 12 row(s) from 'group/subgroup/project' to 'subgroup/project'
    
    Note: If a vipune MCP server is running, it holds its project_id from startup. Restart the MCP server to see rows under the new project id.
    

    The merge is user-invoked, never automatic — it moves rows from from into to (merging into a target that already holds rows is the normal case), preserves content, timestamps, counters, and embeddings byte-identically, and is idempotent (a second run moves zero rows).

  3. Restart your MCP server if one is running. It resolves project_id once at startup and stays scoped to the old id until restarted, so it will not see the merged rows otherwise.


Error Handling

All commands return exit code 1 on error, with error message to stderr or JSON error response.

JSON error format:

{
  "error": "Memory not found"
}

Common errors:

  • Memory not found (get, update, delete)
  • Invalid metadata (not valid JSON)
  • Database errors (permissions, disk full)
  • Missing or invalid configuration

Examples

Semantic search:

vipune search "how do we handle authentication"

Hybrid search (keywords):

vipune search "JWT tokens" --hybrid

See Search Guide for when to use hybrid vs semantic search and how recency weighting works.

Find by metadata (via search):

# High recency bias for time-sensitive queries
vipune search "recent changes" --recency 0.8

# Pure semantic search for knowledge retrieval
vipune search "authentication architecture" --recency 0.0

Force add despite conflicts:

vipune add "Duplicate memory" --force

Batch import (loop in shell):

for fact in facts.txt; do
  vipune add "$fact" || break
done

Export all memories:

vipune list --limit 9999 --json > memories.json

Find and update:

# Search for memory
vipune search "auth implementation"
# Get output ID, then update
vipune update 123e4567-e89b-12d3-a456-426614174000 "Auth uses JWT with refresh tokens"

JSON processing with jq:

# Add and extract ID
ID=$(vipune add --json "Important fact" | jq -r '.id')
echo "Added: $ID"

# Search and get highest similarity
vipune search --json "test" | jq '.results[0].similarity'

# Check for conflicts in script
if vipune add --json "New fact" | jq -e '.conflicts' > /dev/null; then
  echo "Conflict detected!"
fi

Project-specific operations:

# Add memory to specific project
vipune --project "my-company/project" add "Company-specific knowledge"

# Search within project
vipune --project "my-company/project" search "API keys"

See also:

  • Search Guide — When to use hybrid vs semantic search, recency weighting
  • Query Guide — Writing effective semantic queries