schift-cli 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. schift_cli/__init__.py +1 -0
  2. schift_cli/client.py +119 -0
  3. schift_cli/commands/__init__.py +0 -0
  4. schift_cli/commands/auth.py +68 -0
  5. schift_cli/commands/bench.py +65 -0
  6. schift_cli/commands/catalog.py +74 -0
  7. schift_cli/commands/db.py +96 -0
  8. schift_cli/commands/embed.py +104 -0
  9. schift_cli/commands/migrate.py +127 -0
  10. schift_cli/commands/query.py +66 -0
  11. schift_cli/commands/skill.py +110 -0
  12. schift_cli/commands/usage.py +50 -0
  13. schift_cli/config.py +58 -0
  14. schift_cli/data/schift-best-practices/AGENTS.md +77 -0
  15. schift_cli/data/schift-best-practices/CLAUDE.md +77 -0
  16. schift_cli/data/schift-best-practices/SKILL.md +89 -0
  17. schift_cli/data/schift-best-practices/references/bucket-organization.md +126 -0
  18. schift_cli/data/schift-best-practices/references/bucket-upload.md +116 -0
  19. schift_cli/data/schift-best-practices/references/chatbot-widget.md +238 -0
  20. schift_cli/data/schift-best-practices/references/cost-batching.md +179 -0
  21. schift_cli/data/schift-best-practices/references/cost-storage-tiers.md +183 -0
  22. schift_cli/data/schift-best-practices/references/deploy-cloudrun.md +140 -0
  23. schift_cli/data/schift-best-practices/references/embed-batch-processing.md +86 -0
  24. schift_cli/data/schift-best-practices/references/embed-error-handling.md +155 -0
  25. schift_cli/data/schift-best-practices/references/embed-multimodal.md +100 -0
  26. schift_cli/data/schift-best-practices/references/embed-task-types.md +135 -0
  27. schift_cli/data/schift-best-practices/references/rag-chunking.md +173 -0
  28. schift_cli/data/schift-best-practices/references/rag-workflow-builder.md +205 -0
  29. schift_cli/data/schift-best-practices/references/sdk-async-patterns.md +103 -0
  30. schift_cli/data/schift-best-practices/references/sdk-auth-patterns.md +76 -0
  31. schift_cli/data/schift-best-practices/references/search-collection-design.md +229 -0
  32. schift_cli/data/schift-best-practices/references/search-hybrid.md +163 -0
  33. schift_cli/data/schift-best-practices/references/search-similarity-tuning.md +134 -0
  34. schift_cli/display.py +85 -0
  35. schift_cli/main.py +39 -0
  36. schift_cli-0.1.0.dist-info/METADATA +12 -0
  37. schift_cli-0.1.0.dist-info/RECORD +40 -0
  38. schift_cli-0.1.0.dist-info/WHEEL +5 -0
  39. schift_cli-0.1.0.dist-info/entry_points.txt +2 -0
  40. schift_cli-0.1.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,66 @@
1
+ from __future__ import annotations
2
+
3
+ import click
4
+
5
+ from schift_cli.client import get_client, SchiftAPIError
6
+ from schift_cli.display import console, error, print_table
7
+
8
+
9
+ @click.command("query")
10
+ @click.argument("text")
11
+ @click.option("--collection", "-c", required=True, help="Collection name to search")
12
+ @click.option("--top-k", "-k", type=int, default=10, show_default=True,
13
+ help="Number of results to return")
14
+ @click.option("--model", "-m", default=None, help="Embedding model to use for the query (uses collection default if omitted)")
15
+ @click.option("--threshold", type=float, default=None, help="Minimum similarity score filter")
16
+ def query(text: str, collection: str, top_k: int, model: str | None, threshold: float | None) -> None:
17
+ """Search a vector collection with natural language.
18
+
19
+ TEXT is the search query string.
20
+ """
21
+ payload: dict = {
22
+ "text": text,
23
+ "collection": collection,
24
+ "top_k": top_k,
25
+ }
26
+ if model:
27
+ payload["model"] = model
28
+ if threshold is not None:
29
+ payload["threshold"] = threshold
30
+
31
+ try:
32
+ with get_client() as client:
33
+ data = client.post("/query", json=payload)
34
+ except SchiftAPIError as e:
35
+ error(f"Query failed: {e.detail}")
36
+ raise SystemExit(1)
37
+ except click.ClickException:
38
+ raise
39
+
40
+ results = data.get("results", [])
41
+ if not results:
42
+ console.print("No results found.")
43
+ return
44
+
45
+ rows = [
46
+ (
47
+ str(i + 1),
48
+ r.get("id", "-"),
49
+ f"{r.get('score', 0):.4f}",
50
+ _truncate(r.get("text", r.get("metadata", {}).get("text", "-")), 80),
51
+ )
52
+ for i, r in enumerate(results)
53
+ ]
54
+
55
+ print_table(
56
+ f"Search Results ({collection})",
57
+ ["#", "ID", "Score", "Text"],
58
+ rows,
59
+ caption=f"Query: \"{text}\" | top-k: {top_k}",
60
+ )
61
+
62
+
63
+ def _truncate(s: str, max_len: int) -> str:
64
+ if len(s) <= max_len:
65
+ return s
66
+ return s[: max_len - 3] + "..."
@@ -0,0 +1,110 @@
1
+ """schift skill install — install Claude Code skills for Schift best practices."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import shutil
6
+ from importlib import resources
7
+ from pathlib import Path
8
+
9
+ import click
10
+
11
+
12
+ SKILL_NAME = "schift-best-practices"
13
+ CLAUDE_SKILLS_DIR = Path.home() / ".claude" / "skills"
14
+
15
+
16
+ @click.group()
17
+ def skill() -> None:
18
+ """Manage Claude Code skills for Schift."""
19
+
20
+
21
+ @skill.command()
22
+ @click.option(
23
+ "--dest",
24
+ type=click.Path(),
25
+ default=None,
26
+ help="Override destination directory (default: ~/.claude/skills/)",
27
+ )
28
+ def install(dest: str | None) -> None:
29
+ """Install schift-best-practices skill for Claude Code."""
30
+
31
+ target = Path(dest) if dest else CLAUDE_SKILLS_DIR / SKILL_NAME
32
+
33
+ # Locate bundled skill files
34
+ source = _find_skill_source()
35
+ if source is None:
36
+ click.secho(
37
+ "Error: bundled skill files not found. "
38
+ "Try reinstalling schift-cli.",
39
+ fg="red",
40
+ )
41
+ raise SystemExit(1)
42
+
43
+ if target.exists():
44
+ click.confirm(
45
+ f"{target} already exists. Overwrite?",
46
+ abort=True,
47
+ )
48
+ shutil.rmtree(target)
49
+
50
+ shutil.copytree(source, target)
51
+
52
+ # Create CLAUDE.md symlink if missing
53
+ claude_md = target / "CLAUDE.md"
54
+ agents_md = target / "AGENTS.md"
55
+ if agents_md.exists() and not claude_md.exists():
56
+ claude_md.symlink_to("AGENTS.md")
57
+
58
+ click.secho(f"Installed {SKILL_NAME} to {target}", fg="green")
59
+ click.echo(
60
+ "Claude Code will now reference Schift best practices "
61
+ "when you work on embedding, search, or RAG code."
62
+ )
63
+
64
+
65
+ @skill.command()
66
+ def uninstall() -> None:
67
+ """Remove schift-best-practices skill from Claude Code."""
68
+
69
+ target = CLAUDE_SKILLS_DIR / SKILL_NAME
70
+ if not target.exists():
71
+ click.echo(f"{SKILL_NAME} is not installed.")
72
+ return
73
+
74
+ click.confirm(f"Remove {target}?", abort=True)
75
+ shutil.rmtree(target)
76
+ click.secho(f"Removed {SKILL_NAME}", fg="yellow")
77
+
78
+
79
+ @skill.command(name="list")
80
+ def list_skills() -> None:
81
+ """List installed Schift skills."""
82
+
83
+ target = CLAUDE_SKILLS_DIR / SKILL_NAME
84
+ if target.exists():
85
+ refs = list((target / "references").glob("*.md")) if (target / "references").exists() else []
86
+ click.echo(f"{SKILL_NAME} ({len(refs)} references) {target}")
87
+ else:
88
+ click.echo("No Schift skills installed. Run: schift skill install")
89
+
90
+
91
+ def _find_skill_source() -> Path | None:
92
+ """Locate the bundled skill directory shipped with the package."""
93
+
94
+ # 1. Check package data (installed via pip)
95
+ try:
96
+ pkg = resources.files("schift_cli") / "data" / SKILL_NAME
97
+ # resources.files returns a Traversable; resolve to Path
98
+ pkg_path = Path(str(pkg))
99
+ if pkg_path.is_dir() and (pkg_path / "SKILL.md").exists():
100
+ return pkg_path
101
+ except (TypeError, FileNotFoundError):
102
+ pass
103
+
104
+ # 2. Fallback: check relative to repo root (dev mode / editable install)
105
+ repo_root = Path(__file__).resolve().parents[4] # sdk/cli/schift_cli/commands → repo root
106
+ dev_path = repo_root / "skills" / SKILL_NAME
107
+ if dev_path.is_dir() and (dev_path / "SKILL.md").exists():
108
+ return dev_path
109
+
110
+ return None
@@ -0,0 +1,50 @@
1
+ from __future__ import annotations
2
+
3
+ import click
4
+
5
+ from schift_cli.client import get_client, SchiftAPIError
6
+ from schift_cli.display import error, print_kv, print_table
7
+
8
+
9
+ @click.command("usage")
10
+ @click.option("--period", "-p", default="30d", show_default=True,
11
+ help="Time period (e.g. 7d, 30d, 90d)")
12
+ def usage(period: str) -> None:
13
+ """Show API usage and billing summary."""
14
+ try:
15
+ with get_client() as client:
16
+ data = client.get("/usage", params={"period": period})
17
+ except SchiftAPIError as e:
18
+ error(f"Failed to fetch usage: {e.detail}")
19
+ raise SystemExit(1)
20
+ except click.ClickException:
21
+ raise
22
+
23
+ summary = data.get("summary", data)
24
+
25
+ print_kv(f"Usage Summary (last {period})", {
26
+ "Total Requests": summary.get("total_requests", "-"),
27
+ "Embeddings Generated": summary.get("embeddings_generated", "-"),
28
+ "Projections Computed": summary.get("projections_computed", "-"),
29
+ "Queries Executed": summary.get("queries_executed", "-"),
30
+ "Storage Used": summary.get("storage_used", "-"),
31
+ "Cost": summary.get("cost", "-"),
32
+ })
33
+
34
+ # Show per-model breakdown if available
35
+ breakdown = data.get("by_model", [])
36
+ if breakdown:
37
+ rows = [
38
+ (
39
+ b.get("model", ""),
40
+ str(b.get("requests", "")),
41
+ str(b.get("tokens", "")),
42
+ b.get("cost", ""),
43
+ )
44
+ for b in breakdown
45
+ ]
46
+ print_table(
47
+ "Usage by Model",
48
+ ["Model", "Requests", "Tokens", "Cost"],
49
+ rows,
50
+ )
schift_cli/config.py ADDED
@@ -0,0 +1,58 @@
1
+ from __future__ import annotations
2
+
3
+ import json
4
+ import os
5
+ from pathlib import Path
6
+ from typing import Any
7
+
8
+ CONFIG_DIR = Path.home() / ".schift"
9
+ CONFIG_FILE = CONFIG_DIR / "config.json"
10
+
11
+ ENV_API_KEY = "SCHIFT_API_KEY"
12
+ ENV_API_URL = "SCHIFT_API_URL"
13
+
14
+ DEFAULT_API_URL = "https://api.schift.io/v1"
15
+
16
+
17
+ def _ensure_config_dir() -> None:
18
+ CONFIG_DIR.mkdir(parents=True, exist_ok=True)
19
+
20
+
21
+ def load_config() -> dict[str, Any]:
22
+ if not CONFIG_FILE.exists():
23
+ return {}
24
+ try:
25
+ return json.loads(CONFIG_FILE.read_text())
26
+ except (json.JSONDecodeError, OSError):
27
+ return {}
28
+
29
+
30
+ def save_config(config: dict[str, Any]) -> None:
31
+ _ensure_config_dir()
32
+ CONFIG_FILE.write_text(json.dumps(config, indent=2) + "\n")
33
+ # Restrict permissions — API key lives here
34
+ CONFIG_FILE.chmod(0o600)
35
+
36
+
37
+ def get_api_key() -> str | None:
38
+ """Return API key from env var (highest priority) or config file."""
39
+ env_key = os.environ.get(ENV_API_KEY)
40
+ if env_key:
41
+ return env_key
42
+ return load_config().get("api_key")
43
+
44
+
45
+ def set_api_key(api_key: str) -> None:
46
+ config = load_config()
47
+ config["api_key"] = api_key
48
+ save_config(config)
49
+
50
+
51
+ def clear_api_key() -> None:
52
+ config = load_config()
53
+ config.pop("api_key", None)
54
+ save_config(config)
55
+
56
+
57
+ def get_api_url() -> str:
58
+ return os.environ.get(ENV_API_URL, DEFAULT_API_URL)
@@ -0,0 +1,77 @@
1
+ # schift-best-practices
2
+
3
+ > **Note:** `CLAUDE.md` is a symlink to this file.
4
+
5
+ ## Overview
6
+
7
+ Embedding API, vector search, and RAG pipeline best practices from Schift. Use this skill when building embedding-powered applications, integrating Schift SDK, or optimizing vector search and retrieval.
8
+
9
+ ## Structure
10
+
11
+ ```
12
+ schift-best-practices/
13
+ SKILL.md # Main skill file - read this first
14
+ AGENTS.md # This navigation guide
15
+ CLAUDE.md # Symlink to AGENTS.md
16
+ references/ # Detailed reference files
17
+ ```
18
+
19
+ ## Usage
20
+
21
+ 1. Read `SKILL.md` for the main skill instructions
22
+ 2. Browse `references/` for detailed documentation on specific topics
23
+ 3. Reference files are loaded on-demand - read only what you need
24
+
25
+ ## Reference Categories
26
+
27
+ | Priority | Category | Impact | Prefix |
28
+ |----------|----------|--------|--------|
29
+ | 1 | Embedding API | CRITICAL | `embed-` |
30
+ | 2 | Search & Retrieval | CRITICAL | `search-` |
31
+ | 3 | Authentication & SDK | CRITICAL | `sdk-` |
32
+ | 4 | Bucket Management | HIGH | `bucket-` |
33
+ | 5 | RAG Pipelines | HIGH | `rag-` |
34
+ | 6 | Cost Optimization | MEDIUM | `cost-` |
35
+ | 7 | Deployment | LOW-MEDIUM | `deploy-` |
36
+ | 8 | Chatbot Patterns | LOW | `chatbot-` |
37
+
38
+ Reference files are named `{prefix}-{topic}.md` (e.g., `embed-batch-processing.md`).
39
+
40
+ ## Available References
41
+
42
+ **Embedding API** (`embed-`):
43
+ - `references/embed-batch-processing.md`
44
+ - `references/embed-multimodal.md`
45
+ - `references/embed-task-types.md`
46
+ - `references/embed-error-handling.md`
47
+
48
+ **Search & Retrieval** (`search-`):
49
+ - `references/search-similarity-tuning.md`
50
+ - `references/search-hybrid.md`
51
+ - `references/search-collection-design.md`
52
+
53
+ **Authentication & SDK** (`sdk-`):
54
+ - `references/sdk-auth-patterns.md`
55
+ - `references/sdk-async-patterns.md`
56
+
57
+ **Bucket Management** (`bucket-`):
58
+ - `references/bucket-upload.md`
59
+ - `references/bucket-organization.md`
60
+
61
+ **RAG Pipelines** (`rag-`):
62
+ - `references/rag-chunking.md`
63
+ - `references/rag-workflow-builder.md`
64
+
65
+ **Cost Optimization** (`cost-`):
66
+ - `references/cost-batching.md`
67
+ - `references/cost-storage-tiers.md`
68
+
69
+ **Deployment** (`deploy-`):
70
+ - `references/deploy-cloudrun.md`
71
+
72
+ **Chatbot Patterns** (`chatbot-`):
73
+ - `references/chatbot-widget.md`
74
+
75
+ ---
76
+
77
+ *18 reference files across 8 categories*
@@ -0,0 +1,77 @@
1
+ # schift-best-practices
2
+
3
+ > **Note:** `CLAUDE.md` is a symlink to this file.
4
+
5
+ ## Overview
6
+
7
+ Embedding API, vector search, and RAG pipeline best practices from Schift. Use this skill when building embedding-powered applications, integrating Schift SDK, or optimizing vector search and retrieval.
8
+
9
+ ## Structure
10
+
11
+ ```
12
+ schift-best-practices/
13
+ SKILL.md # Main skill file - read this first
14
+ AGENTS.md # This navigation guide
15
+ CLAUDE.md # Symlink to AGENTS.md
16
+ references/ # Detailed reference files
17
+ ```
18
+
19
+ ## Usage
20
+
21
+ 1. Read `SKILL.md` for the main skill instructions
22
+ 2. Browse `references/` for detailed documentation on specific topics
23
+ 3. Reference files are loaded on-demand - read only what you need
24
+
25
+ ## Reference Categories
26
+
27
+ | Priority | Category | Impact | Prefix |
28
+ |----------|----------|--------|--------|
29
+ | 1 | Embedding API | CRITICAL | `embed-` |
30
+ | 2 | Search & Retrieval | CRITICAL | `search-` |
31
+ | 3 | Authentication & SDK | CRITICAL | `sdk-` |
32
+ | 4 | Bucket Management | HIGH | `bucket-` |
33
+ | 5 | RAG Pipelines | HIGH | `rag-` |
34
+ | 6 | Cost Optimization | MEDIUM | `cost-` |
35
+ | 7 | Deployment | LOW-MEDIUM | `deploy-` |
36
+ | 8 | Chatbot Patterns | LOW | `chatbot-` |
37
+
38
+ Reference files are named `{prefix}-{topic}.md` (e.g., `embed-batch-processing.md`).
39
+
40
+ ## Available References
41
+
42
+ **Embedding API** (`embed-`):
43
+ - `references/embed-batch-processing.md`
44
+ - `references/embed-multimodal.md`
45
+ - `references/embed-task-types.md`
46
+ - `references/embed-error-handling.md`
47
+
48
+ **Search & Retrieval** (`search-`):
49
+ - `references/search-similarity-tuning.md`
50
+ - `references/search-hybrid.md`
51
+ - `references/search-collection-design.md`
52
+
53
+ **Authentication & SDK** (`sdk-`):
54
+ - `references/sdk-auth-patterns.md`
55
+ - `references/sdk-async-patterns.md`
56
+
57
+ **Bucket Management** (`bucket-`):
58
+ - `references/bucket-upload.md`
59
+ - `references/bucket-organization.md`
60
+
61
+ **RAG Pipelines** (`rag-`):
62
+ - `references/rag-chunking.md`
63
+ - `references/rag-workflow-builder.md`
64
+
65
+ **Cost Optimization** (`cost-`):
66
+ - `references/cost-batching.md`
67
+ - `references/cost-storage-tiers.md`
68
+
69
+ **Deployment** (`deploy-`):
70
+ - `references/deploy-cloudrun.md`
71
+
72
+ **Chatbot Patterns** (`chatbot-`):
73
+ - `references/chatbot-widget.md`
74
+
75
+ ---
76
+
77
+ *18 reference files across 8 categories*
@@ -0,0 +1,89 @@
1
+ ---
2
+ name: schift-best-practices
3
+ description: Embedding API, vector search, and RAG pipeline best practices from Schift. Use this skill when building embedding-powered applications, integrating Schift SDK, or optimizing vector search and retrieval.
4
+ license: MIT
5
+ metadata:
6
+ author: schift
7
+ version: "0.1.0"
8
+ organization: Schift
9
+ date: March 2026
10
+ abstract: Comprehensive embedding and vector search best practices for developers using Schift's managed embedding infrastructure. Contains rules across 8 categories covering embedding API usage, search optimization, bucket management, RAG pipelines, SDK patterns, cost optimization, deployment, and chatbot integration. Each rule includes incorrect vs. correct code examples in Python and TypeScript.
11
+ ---
12
+
13
+ # Schift Best Practices
14
+
15
+ Best practices for building embedding-powered applications with Schift's managed embedding infrastructure. Covers embedding API, vector search, RAG pipelines, and deployment patterns.
16
+
17
+ ## When to Apply
18
+
19
+ Reference these guidelines when:
20
+ - Calling Schift's embedding or search API
21
+ - Designing vector collections or bucket structures
22
+ - Building RAG pipelines with the workflow engine
23
+ - Integrating the Python or TypeScript SDK
24
+ - Deploying Schift-powered applications
25
+ - Optimizing cost and performance for embedding workloads
26
+ - Building chatbot or Q&A interfaces backed by Schift
27
+
28
+ ## Rule Categories by Priority
29
+
30
+ | Priority | Category | Impact | Prefix |
31
+ |----------|----------|--------|--------|
32
+ | 1 | Embedding API | CRITICAL | `embed-` |
33
+ | 2 | Search & Retrieval | CRITICAL | `search-` |
34
+ | 3 | Authentication & SDK | CRITICAL | `sdk-` |
35
+ | 4 | Bucket Management | HIGH | `bucket-` |
36
+ | 5 | RAG Pipelines | HIGH | `rag-` |
37
+ | 6 | Cost Optimization | MEDIUM | `cost-` |
38
+ | 7 | Deployment | LOW-MEDIUM | `deploy-` |
39
+ | 8 | Chatbot Patterns | LOW | `chatbot-` |
40
+
41
+ ## How to Use
42
+
43
+ Read individual rule files for detailed explanations and code examples:
44
+
45
+ ```
46
+ references/embed-batch-processing.md
47
+ references/search-similarity-tuning.md
48
+ references/sdk-auth-patterns.md
49
+ ```
50
+
51
+ Each rule file contains:
52
+ - Brief explanation of why it matters
53
+ - Incorrect code example with explanation
54
+ - Correct code example with explanation
55
+ - Performance impact or cost implications
56
+ - Schift-specific notes
57
+
58
+ ## Key Concepts
59
+
60
+ - **Canonical Space**: All embeddings are projected into Schift's 1024-dimensional canonical vector space, enabling cross-model compatibility
61
+ - **Bucket**: File upload → automatic OCR + chunking + embedding → searchable index
62
+ - **Workflow**: DAG-based RAG pipeline builder with block nodes
63
+ - **Task Types**: retrieval, similarity, classification, clustering, QA, fact_verification, code_retrieval
64
+ - **Modalities**: text, image, audio, video, document
65
+
66
+ ## SDK Quick Start
67
+
68
+ ```python
69
+ # Python
70
+ pip install schift
71
+ from schift import Schift
72
+ client = Schift(api_key="sch_...")
73
+ result = client.embed("Hello world")
74
+ ```
75
+
76
+ ```typescript
77
+ // TypeScript
78
+ npm install @schift-io/sdk
79
+ import { Schift } from '@schift-io/sdk';
80
+ const client = new Schift({ apiKey: 'sch_...' });
81
+ const result = await client.embed('Hello world');
82
+ ```
83
+
84
+ ## References
85
+
86
+ - https://docs.schift.io
87
+ - https://github.com/schift-io/schift
88
+ - https://pypi.org/project/schift/
89
+ - https://www.npmjs.com/package/@schift-io/sdk
@@ -0,0 +1,126 @@
1
+ ---
2
+ title: Bucket Organization by Data Domain
3
+ impact: MEDIUM
4
+ impactDescription: One bucket per upload session inflates bucket count, fragments the FAISS index, and makes cross-document search impossible without aggregating results manually. Long-lived domain buckets give better search quality and simpler code.
5
+ tags: [bucket, organization, architecture, faiss, search]
6
+ ---
7
+
8
+ ## Organize Buckets by Data Domain, Not by Upload Session
9
+
10
+ A Schift bucket maintains a single FAISS index over all documents ever uploaded to it. Search over a bucket queries that entire index in one call. When you create a new bucket for every upload session, each bucket becomes a silo — you lose the ability to search across related documents without fanning out to every bucket and merging results yourself.
11
+
12
+ Design buckets around stable data domains: what set of documents belongs together conceptually? That set is a bucket.
13
+
14
+ ### Incorrect
15
+
16
+ ```python
17
+ # Python - new bucket per upload session
18
+ # After 10 uploads you have 10 buckets, none searchable together
19
+ from schift import Schift
20
+ from datetime import datetime
21
+
22
+ client = Schift()
23
+
24
+ def upload_weekly_reports(files: list[str]):
25
+ # Creates a new bucket every time this function runs
26
+ bucket = client.bucket.create(f"reports-{datetime.now().isoformat()}")
27
+ job = client.bucket.upload(bucket.id, files=files)
28
+ job.wait()
29
+ return bucket.id
30
+
31
+ # Searching now requires querying every bucket and merging manually
32
+ def search_all_reports(query: str) -> list:
33
+ buckets = client.bucket.list() # grows every week
34
+ all_hits = []
35
+ for b in buckets:
36
+ results = client.bucket.search(b.id, query)
37
+ all_hits.extend(results.hits)
38
+ all_hits.sort(key=lambda h: h.score, reverse=True)
39
+ return all_hits
40
+ ```
41
+
42
+ ```typescript
43
+ // TypeScript - same anti-pattern
44
+ import { Schift } from '@schift-io/sdk';
45
+
46
+ const client = new Schift();
47
+
48
+ async function uploadWeeklyReports(files: string[]) {
49
+ const bucket = await client.bucket.create(`reports-${Date.now()}`);
50
+ const job = await client.bucket.upload(bucket.id, { files });
51
+ await job.wait();
52
+ return bucket.id;
53
+ }
54
+ ```
55
+
56
+ ### Correct
57
+
58
+ ```python
59
+ # Python - one bucket per data domain, append files over time
60
+ from schift import Schift
61
+
62
+ client = Schift()
63
+
64
+ # Provision once (e.g., at app startup or in a migration script)
65
+ def get_or_create_bucket(name: str) -> str:
66
+ existing = [b for b in client.bucket.list() if b.name == name]
67
+ if existing:
68
+ return existing[0].id
69
+ return client.bucket.create(name).id
70
+
71
+ # Reuse the same bucket across all weekly uploads
72
+ REPORTS_BUCKET = get_or_create_bucket("weekly-reports")
73
+
74
+ def upload_weekly_reports(files: list[str]):
75
+ job = client.bucket.upload(REPORTS_BUCKET, files=files)
76
+ job.wait()
77
+
78
+ # Single search call covers all documents ever uploaded to this bucket
79
+ def search_reports(query: str):
80
+ return client.bucket.search(REPORTS_BUCKET, query).hits
81
+ ```
82
+
83
+ ```typescript
84
+ // TypeScript - same domain-first approach
85
+ import { Schift } from '@schift-io/sdk';
86
+
87
+ const client = new Schift();
88
+
89
+ async function getOrCreateBucket(name: string): Promise<string> {
90
+ const buckets = await client.bucket.list();
91
+ const existing = buckets.find(b => b.name === name);
92
+ if (existing) return existing.id;
93
+ const bucket = await client.bucket.create(name);
94
+ return bucket.id;
95
+ }
96
+
97
+ const REPORTS_BUCKET = await getOrCreateBucket('weekly-reports');
98
+
99
+ async function uploadWeeklyReports(files: string[]) {
100
+ const job = await client.bucket.upload(REPORTS_BUCKET, { files });
101
+ await job.wait();
102
+ }
103
+
104
+ async function searchReports(query: string) {
105
+ return (await client.bucket.search(REPORTS_BUCKET, query)).hits;
106
+ }
107
+ ```
108
+
109
+ **Recommended domain examples:**
110
+
111
+ | Domain | Bucket name |
112
+ |--------|-------------|
113
+ | Internal knowledge base | `company-docs` |
114
+ | Product documentation | `product-manuals` |
115
+ | Legal contracts | `legal-contracts` |
116
+ | Support ticket history | `support-history` |
117
+ | Weekly reports | `weekly-reports` |
118
+
119
+ If two domains are always searched together, merge them into one bucket. If they are never searched together and have different access control requirements, keep them separate.
120
+
121
+ Each bucket has an independent FAISS index. Uploads append to the index; the index is never rebuilt from scratch unless explicitly requested.
122
+
123
+ ## Reference
124
+
125
+ - https://docs.schift.io/buckets/organization
126
+ - https://docs.schift.io/buckets/faiss-index