Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .cursor-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "pinecone",
"description": "Pinecone vector database integration for Cursor. Create and manage indexes, upsert data, and run semantic searches via the Pinecone MCP server. Build document Q&A assistants with citations, or get started fast with /pinecone-quickstart. Great for semantic search, RAG, and agentic AI apps.",
"version": "1.0.0",
"version": "1.0.1",
"author": {
"name": "Pinecone"
},
Expand Down
8 changes: 4 additions & 4 deletions skills/pinecone-assistant/scripts/chat.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
#!/usr/bin/env python3
# /// script
# requires-python = ">=3.10"
# dependencies = [
# "pinecone>=8.0.0",
# "pinecone==9.1.0",
# "typer>=0.15.0",
# "rich>=13.0.0",
# ]
Expand All @@ -25,7 +26,7 @@
from rich.panel import Panel
from rich.table import Table
from pinecone import Pinecone
from pinecone_plugins.assistant.models.chat import Message
from pinecone.models.assistant import Message

app = typer.Typer()
console = Console()
Expand All @@ -48,7 +49,6 @@ def main(
try:
# Initialize Pinecone client
pc = Pinecone(api_key=api_key,source_tag="cursor_plugin:assistant")
asst = pc.assistant.Assistant(assistant_name=assistant)

# Create message
user_msg = Message(role="user", content=message)
Expand All @@ -58,7 +58,7 @@ def main(

# Get response
with console.status("[bold blue]Thinking...[/bold blue]"):
response = asst.chat(messages=[user_msg], stream=False)
response = pc.assistants.chat(assistant_name=assistant, messages=[user_msg], stream=False)

answer_content = response.message.content
citations = response.citations if hasattr(response, 'citations') else []
Expand Down
14 changes: 10 additions & 4 deletions skills/pinecone-assistant/scripts/context.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
#!/usr/bin/env python3
# /// script
# requires-python = ">=3.10"
# dependencies = [
# "pinecone>=8.0.0",
# "pinecone==9.1.0",
# "typer>=0.15.0",
# "rich>=13.0.0",
# ]
Expand All @@ -27,6 +28,7 @@
from rich.table import Table
from rich.text import Text
from pinecone import Pinecone
from pinecone.models.assistant import TextSnippet

app = typer.Typer()
console = Console()
Expand All @@ -52,15 +54,16 @@ def main(
try:
# Initialize Pinecone client
pc = Pinecone(api_key=api_key, source_tag="cursor_plugin:assistant")
asst = pc.assistant.Assistant(assistant_name=assistant)

# Display query
if not json:
console.print(Panel(f"[bold cyan]Query:[/bold cyan] {query}", border_style="cyan"))

# Retrieve context
with console.status("[bold blue]Searching knowledge base...[/bold blue]", spinner="dots"):
response = asst.context(query=query, top_k=top_k, snippet_size=snippet_size)
response = pc.assistants.context(
assistant_name=assistant, query=query, top_k=top_k, snippet_size=snippet_size
)

# Get snippets from response
snippets = response.snippets if hasattr(response, 'snippets') else []
Expand All @@ -83,7 +86,10 @@ def main(
"pages": pages,
"content": getattr(snippet, 'content', ''),
"score": getattr(snippet, 'score', 0.0),
"type": getattr(snippet, 'type', 'text'),
# SDK 9 encodes the snippet kind as a msgspec tag, not an
# instance attribute, so getattr(snippet, 'type') silently
# returned the default for every snippet.
"type": "text" if isinstance(snippet, TextSnippet) else "multimodal",
})
print(json_module.dumps({"snippets": results, "count": len(results)}, indent=2))
else:
Expand Down
3 changes: 2 additions & 1 deletion skills/pinecone-assistant/scripts/create.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
#!/usr/bin/env python3
# /// script
# requires-python = ">=3.10"
# dependencies = [
# "pinecone>=8.0.0",
# "pinecone==9.1.0",
# "typer>=0.15.0",
# "rich>=13.0.0",
# ]
Expand Down
49 changes: 30 additions & 19 deletions skills/pinecone-assistant/scripts/list.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
#!/usr/bin/env python3
# /// script
# requires-python = ">=3.10"
# dependencies = [
# "pinecone>=8.0.0",
# "pinecone==9.1.0",
# "typer>=0.15.0",
# "rich>=13.0.0",
# ]
Expand All @@ -16,7 +17,7 @@
PINECONE_API_KEY: Required Pinecone API key

Output:
Formatted table or JSON list of assistants with name, region, status, and host
Formatted table or JSON list of assistants with name, status, and host
Optionally include files for each assistant with --files flag
"""

Expand Down Expand Up @@ -69,16 +70,17 @@ def main(
for asst in assistants:
asst_data = {
"name": asst.name,
"region": getattr(asst, 'region', 'unknown'),
"status": asst.status,
"host": getattr(asst, 'host', ''),
}

if files:
# Get files for this assistant
try:
assistant_instance = pc.assistant.Assistant(assistant_name=asst.name)
file_list = assistant_instance.list_files()
# Models from list_assistants() carry a client back-reference,
# so list_files() works on them directly and returns a list.
# pc.assistants.list_files() returns a Paginator with no __len__.
file_list = asst.list_files()
asst_data["files"] = [
{
"name": f.name,
Expand Down Expand Up @@ -108,38 +110,40 @@ def main(
# Assistants table
table = Table(show_header=True, header_style="bold cyan")
table.add_column("Name", style="green", width=30)
table.add_column("Region", style="blue", width=10)
table.add_column("Status", style="yellow", width=15)
if files:
table.add_column("Files", style="magenta", width=10)
table.add_column("Host", style="dim", width=40 if files else 50)

for asst in assistants:
name = asst.name
region = getattr(asst, 'region', 'unknown')
status = asst.status
host = getattr(asst, 'host', '')

# Color code status
if status == 'ready':
# Color code status. The API returns capitalized values
# ("Ready", "Initializing"), so compare case-insensitively.
status_key = (status or '').lower()
if status_key == 'ready':
status_display = f"[green]{status}[/green]"
elif status == 'indexing':
elif status_key in ('indexing', 'initializing'):
status_display = f"[yellow]{status}[/yellow]"
else:
status_display = status

if files:
# Get file count for this assistant
try:
assistant_instance = pc.assistant.Assistant(assistant_name=asst.name)
file_list = assistant_instance.list_files()
# Models from list_assistants() carry a client back-reference,
# so list_files() works on them directly and returns a list.
# pc.assistants.list_files() returns a Paginator with no __len__.
file_list = asst.list_files()
file_count = str(len(file_list))
except Exception:
file_count = "?"

table.add_row(name, region, status_display, file_count, host)
table.add_row(name, status_display, file_count, host)
else:
table.add_row(name, region, status_display, host)
table.add_row(name, status_display, host)

console.print(table)
console.print()
Expand All @@ -149,8 +153,10 @@ def main(
console.print("[bold]File Details:[/bold]\n")
for asst in assistants:
try:
assistant_instance = pc.assistant.Assistant(assistant_name=asst.name)
file_list = assistant_instance.list_files()
# Models from list_assistants() carry a client back-reference,
# so list_files() works on them directly and returns a list.
# pc.assistants.list_files() returns a Paginator with no __len__.
file_list = asst.list_files()

if file_list:
# Create a table for this assistant's files
Expand All @@ -165,11 +171,16 @@ def main(
file_id = file_obj.id
file_status = file_obj.status

# Color code file status
if file_status == 'available':
# Color code file status. The API returns
# capitalized values ("Available", "Processing",
# "ProcessingFailed"), so normalize before comparing.
fs_key = (file_status or '').lower()
if fs_key == 'available':
file_status_display = f"[green]{file_status}[/green]"
elif file_status == 'processing':
elif fs_key == 'processing':
file_status_display = f"[yellow]{file_status}[/yellow]"
elif 'failed' in fs_key:
file_status_display = f"[red]{file_status}[/red]"
else:
file_status_display = file_status

Expand Down
9 changes: 7 additions & 2 deletions skills/pinecone-assistant/scripts/sync.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
#!/usr/bin/env python3
# /// script
# requires-python = ">=3.10"
# dependencies = [
# "pinecone>=8.0.0",
# "pinecone==9.1.0",
# "typer>=0.15.0",
# "rich>=13.0.0",
# ]
Expand Down Expand Up @@ -119,7 +120,11 @@ def main(
try:
# Initialize Pinecone client
pc = Pinecone(api_key=api_key, source_tag="cursor_plugin:assistant")
asst = pc.assistant.Assistant(assistant_name=assistant)
# describe() returns a model carrying a client back-reference, so the
# asst.list_files()/upload_file()/delete_file() calls below keep working.
# Its list_files() also materializes, unlike pc.assistants.list_files(),
# which returns a Paginator with no __len__.
asst = pc.assistants.describe(name=assistant)

console.print(Panel(
f"[bold cyan]Assistant:[/bold cyan] {assistant}\n"
Expand Down
7 changes: 4 additions & 3 deletions skills/pinecone-assistant/scripts/upload.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
#!/usr/bin/env python3
# /// script
# requires-python = ">=3.10"
# dependencies = [
# "pinecone>=8.0.0",
# "pinecone==9.1.0",
# "typer>=0.15.0",
# "rich>=13.0.0",
# ]
Expand Down Expand Up @@ -127,7 +128,6 @@ def main(
try:
# Initialize Pinecone client
pc = Pinecone(api_key=api_key, source_tag="cursor_plugin:assistant")
asst = pc.assistant.Assistant(assistant_name=assistant)

# Find files to upload
console.print(f"\n[bold]Scanning for documentation files in:[/bold] {source}")
Expand Down Expand Up @@ -173,7 +173,8 @@ def main(
}

# Upload file
asst.upload_file(
pc.assistants.upload_file(
assistant_name=assistant,
file_path=str(file_path),
metadata=metadata,
timeout=None,
Expand Down
2 changes: 1 addition & 1 deletion skills/pinecone-full-text-search/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ description: Create, ingest into, and query a Pinecone full-text-search (FTS) in

# pinecone-full-text-search

> **Requires `pinecone` Python SDK ≥ 9.0** (`pip install pinecone>=9.0`). The FTS document-schema API lives under `pinecone.preview` and is incomplete or absent in earlier SDK builds. The packaged helper scripts pin `pinecone==9.0.0` via PEP 723 inline metadata; if you're writing your own code against this skill, pin v9 explicitly. The wire API version is `2026-01.alpha`.
> **Requires `pinecone` Python SDK ≥ 9.0** (`pip install pinecone>=9.0`). The FTS document-schema API lives under `pinecone.preview` and is incomplete or absent in earlier SDK builds. The packaged helper scripts pin `pinecone==9.1.0` via PEP 723 inline metadata; if you're writing your own code against this skill, pin v9 explicitly. The wire API version is `2026-01.alpha`.

> **Authoritative reference (last resort).** If you hit a question this skill and its `references/*.md` files don't answer, the official Pinecone FTS docs are at <https://docs.pinecone.io/guides/search/full-text-search>. Prefer this skill's content for anything covered here — the docs may describe surfaces (e.g. classic vector API) that don't apply to the document-schema FTS path. Consult the link only when you're genuinely stuck.

Expand Down
2 changes: 1 addition & 1 deletion skills/pinecone-full-text-search/scripts/ingest.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
# requires-python = ">=3.10"
# dependencies = [
# "typer>=0.12",
# "pinecone==9.0.0",
# "pinecone==9.1.0",
# ]
# ///
"""Ingest a JSONL file into a Pinecone FTS index — safely.
Expand Down
9 changes: 5 additions & 4 deletions skills/pinecone-quickstart/scripts/quickstart_complete.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
#!/usr/bin/env python3
# /// script
# requires-python = ">=3.10"
# dependencies = [
# "pinecone>=8.0.0",
# "pinecone==9.1.0",
# ]
# ///

Expand Down Expand Up @@ -47,7 +48,7 @@
]

dense_index = pc.Index(index_name)
dense_index.upsert_records("example-namespace", records)
dense_index.upsert_records(namespace="example-namespace", records=records)

# 3. Search records
# The query uses different words than the records — semantic search finds meaning, not keywords.
Expand All @@ -60,7 +61,7 @@

print("Search results:")
for hit in results["result"]["hits"]:
print(f" id: {hit['_id']} | score: {round(hit['_score'], 2)} | text: {hit['fields']['chunk_text']}")
print(f" id: {hit['id']} | score: {round(hit['score'], 2)} | text: {hit['fields']['chunk_text']}")

# 4. Search with reranking
reranked_results = dense_index.search(
Expand All @@ -71,4 +72,4 @@

print("\nReranked results:")
for hit in reranked_results["result"]["hits"]:
print(f" id: {hit['_id']} | score: {round(hit['_score'], 2)} | text: {hit['fields']['chunk_text']}")
print(f" id: {hit['id']} | score: {round(hit['score'], 2)} | text: {hit['fields']['chunk_text']}")
5 changes: 3 additions & 2 deletions skills/pinecone-quickstart/scripts/upsert.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
#!/usr/bin/env python3
# /// script
# requires-python = ">=3.10"
# dependencies = [
# "pinecone>=8.0.0",
# "pinecone==9.1.0",
# "typer>=0.15.0",
# ]
# ///
Expand Down Expand Up @@ -40,7 +41,7 @@ def main(
]

idx = pc.Index(index)
idx.upsert_records(namespace, records)
idx.upsert_records(namespace=namespace, records=records)
typer.echo(f"Upserted {len(records)} records into '{index}' (namespace: '{namespace}')")

if __name__ == "__main__":
Expand Down
Loading