diff --git a/.cursor-plugin/plugin.json b/.cursor-plugin/plugin.json index 27d3dbf..7d5a359 100644 --- a/.cursor-plugin/plugin.json +++ b/.cursor-plugin/plugin.json @@ -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" }, diff --git a/skills/pinecone-assistant/scripts/chat.py b/skills/pinecone-assistant/scripts/chat.py index 637f858..eacfb43 100755 --- a/skills/pinecone-assistant/scripts/chat.py +++ b/skills/pinecone-assistant/scripts/chat.py @@ -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", # ] @@ -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() @@ -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) @@ -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 [] diff --git a/skills/pinecone-assistant/scripts/context.py b/skills/pinecone-assistant/scripts/context.py index 438c13f..3443ac5 100755 --- a/skills/pinecone-assistant/scripts/context.py +++ b/skills/pinecone-assistant/scripts/context.py @@ -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", # ] @@ -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() @@ -52,7 +54,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) # Display query if not json: @@ -60,7 +61,9 @@ def main( # 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 [] @@ -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: diff --git a/skills/pinecone-assistant/scripts/create.py b/skills/pinecone-assistant/scripts/create.py index 724aa1c..876f6d1 100755 --- a/skills/pinecone-assistant/scripts/create.py +++ b/skills/pinecone-assistant/scripts/create.py @@ -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", # ] diff --git a/skills/pinecone-assistant/scripts/list.py b/skills/pinecone-assistant/scripts/list.py index c48c06b..8b545ee 100755 --- a/skills/pinecone-assistant/scripts/list.py +++ b/skills/pinecone-assistant/scripts/list.py @@ -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", # ] @@ -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 """ @@ -69,7 +70,6 @@ def main( for asst in assistants: asst_data = { "name": asst.name, - "region": getattr(asst, 'region', 'unknown'), "status": asst.status, "host": getattr(asst, 'host', ''), } @@ -77,8 +77,10 @@ def main( 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, @@ -108,7 +110,6 @@ 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) @@ -116,14 +117,15 @@ def main( 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 @@ -131,15 +133,17 @@ def main( 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() @@ -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 @@ -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 diff --git a/skills/pinecone-assistant/scripts/sync.py b/skills/pinecone-assistant/scripts/sync.py index 94c884f..ae9d29a 100644 --- a/skills/pinecone-assistant/scripts/sync.py +++ b/skills/pinecone-assistant/scripts/sync.py @@ -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", # ] @@ -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" diff --git a/skills/pinecone-assistant/scripts/upload.py b/skills/pinecone-assistant/scripts/upload.py index 57ec966..78e75c1 100755 --- a/skills/pinecone-assistant/scripts/upload.py +++ b/skills/pinecone-assistant/scripts/upload.py @@ -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", # ] @@ -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}") @@ -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, diff --git a/skills/pinecone-full-text-search/SKILL.md b/skills/pinecone-full-text-search/SKILL.md index d2ee3c5..63a7f79 100644 --- a/skills/pinecone-full-text-search/SKILL.md +++ b/skills/pinecone-full-text-search/SKILL.md @@ -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 . 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. diff --git a/skills/pinecone-full-text-search/scripts/ingest.py b/skills/pinecone-full-text-search/scripts/ingest.py index 30f609b..210c3a1 100755 --- a/skills/pinecone-full-text-search/scripts/ingest.py +++ b/skills/pinecone-full-text-search/scripts/ingest.py @@ -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. diff --git a/skills/pinecone-quickstart/scripts/quickstart_complete.py b/skills/pinecone-quickstart/scripts/quickstart_complete.py index 0404873..d19f4ae 100644 --- a/skills/pinecone-quickstart/scripts/quickstart_complete.py +++ b/skills/pinecone-quickstart/scripts/quickstart_complete.py @@ -1,7 +1,8 @@ #!/usr/bin/env python3 # /// script +# requires-python = ">=3.10" # dependencies = [ -# "pinecone>=8.0.0", +# "pinecone==9.1.0", # ] # /// @@ -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. @@ -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( @@ -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']}") diff --git a/skills/pinecone-quickstart/scripts/upsert.py b/skills/pinecone-quickstart/scripts/upsert.py index ac428ee..8dd59cc 100644 --- a/skills/pinecone-quickstart/scripts/upsert.py +++ b/skills/pinecone-quickstart/scripts/upsert.py @@ -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", # ] # /// @@ -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__":