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,229 @@
1
+ ---
2
+ title: Design Collections by Domain/Use-Case, Not by Data Source
3
+ impact: HIGH
4
+ impactDescription: A single monolithic collection forces every query to search irrelevant data, bloating latency and degrading result quality. Per-file collections create management overhead and prevent cross-document ranking within a domain. Domain-aligned collections with consistent metadata schemas give you both search quality and operational simplicity.
5
+ tags:
6
+ - collection
7
+ - schema
8
+ - design
9
+ - organization
10
+ - metadata
11
+ ---
12
+
13
+ ## Design Collections by Domain/Use-Case, Not by Data Source
14
+
15
+ Collection design is a structural decision that affects search quality, filter performance, and long-term maintainability. The two common anti-patterns are opposites of each other: one giant collection that holds everything (poor signal-to-noise ratio per query), and one collection per uploaded file (combinatorial overhead, no cross-document ranking).
16
+
17
+ The correct approach is to group documents by the queries they serve. A user asking "how do I reset my API key?" should search `support-docs` — not wade through API reference and release notes in the same index.
18
+
19
+ ### Incorrect
20
+
21
+ **Anti-pattern A: One monolithic collection**
22
+
23
+ ```python
24
+ # Python — everything in one collection
25
+ from schift import Schift
26
+
27
+ client = Schift(api_key="sch_...")
28
+
29
+ # Dumping all company knowledge into a single collection
30
+ client.collection.add("everything", documents=[
31
+ {"id": "pd-001", "text": "Product feature: drag-and-drop upload..."},
32
+ {"id": "st-001", "text": "Support ticket: user cannot login..."},
33
+ {"id": "api-001", "text": "POST /v1/embed — Creates embeddings..."},
34
+ {"id": "rn-001", "text": "Release notes v2.3: added batch support..."},
35
+ ])
36
+
37
+ # Query now competes against all domains — noisy results
38
+ results = client.search("everything", "how to embed documents")
39
+ # Returns: API reference, support tickets, release notes, all mixed together
40
+ ```
41
+
42
+ **Anti-pattern B: One collection per file**
43
+
44
+ ```python
45
+ # Python — a new collection for every uploaded file
46
+ for filename, content in uploaded_files:
47
+ collection_id = filename.replace(".pdf", "").replace(" ", "-")
48
+ client.collection.create(collection_id, schema={}) # no schema
49
+ client.collection.add(collection_id, documents=[{"id": "doc", "text": content}])
50
+
51
+ # Now you must search 200 collections separately — impossible to rank across files
52
+ ```
53
+
54
+ ```typescript
55
+ // TypeScript — same anti-patterns
56
+ import { Schift } from '@schift-io/sdk';
57
+
58
+ const client = new Schift({ apiKey: 'sch_...' });
59
+
60
+ // Anti-pattern A: monolithic
61
+ await client.collection.add('everything', { documents: allMyDocuments });
62
+
63
+ // Anti-pattern B: per-file
64
+ for (const file of uploadedFiles) {
65
+ const id = file.name.replace('.pdf', '');
66
+ await client.collection.create(id, { schema: {} }); // no schema
67
+ await client.collection.add(id, { documents: [{ id: 'doc', text: file.content }] });
68
+ }
69
+ ```
70
+
71
+ ### Correct
72
+
73
+ Create collections per domain/use-case with a defined metadata schema:
74
+
75
+ ```python
76
+ # Python — domain-aligned collections with schemas
77
+ from schift import Schift
78
+
79
+ client = Schift(api_key="sch_...")
80
+
81
+ # Define schema for each domain at creation — only these fields are filter-indexed
82
+ client.collection.create("product-docs", schema={
83
+ "fields": {
84
+ "version": {"type": "string", "filterable": True},
85
+ "category": {"type": "string", "filterable": True}, # "feature", "guide", "faq"
86
+ "updated_at": {"type": "date", "filterable": True},
87
+ "language": {"type": "string", "filterable": True},
88
+ }
89
+ })
90
+
91
+ client.collection.create("support-tickets", schema={
92
+ "fields": {
93
+ "status": {"type": "string", "filterable": True}, # "open", "closed"
94
+ "priority": {"type": "string", "filterable": True},
95
+ "created_at": {"type": "date", "filterable": True},
96
+ "product_area": {"type": "string", "filterable": True},
97
+ }
98
+ })
99
+
100
+ client.collection.create("api-reference", schema={
101
+ "fields": {
102
+ "endpoint": {"type": "string", "filterable": True},
103
+ "method": {"type": "string", "filterable": True}, # "GET", "POST", etc.
104
+ "version": {"type": "string", "filterable": True},
105
+ "deprecated": {"type": "boolean", "filterable": True},
106
+ }
107
+ })
108
+
109
+ # Ingest respects the schema — metadata is validated at write time
110
+ client.collection.add("product-docs", documents=[
111
+ {
112
+ "id": "pd-drag-drop-001",
113
+ "text": "Drag-and-drop upload lets you add files directly to a bucket...",
114
+ "metadata": {
115
+ "version": "2.3",
116
+ "category": "feature",
117
+ "updated_at": "2026-02-10",
118
+ "language": "en",
119
+ }
120
+ },
121
+ ])
122
+
123
+ # Queries are scoped to the right domain — clean, relevant results
124
+ product_results = client.search(
125
+ "product-docs",
126
+ "how to upload files",
127
+ top_k=5,
128
+ filters={"category": "guide", "language": "en"}
129
+ )
130
+
131
+ support_results = client.search(
132
+ "support-tickets",
133
+ "upload fails with 413 error",
134
+ top_k=8,
135
+ filters={"status": "open", "product_area": "ingestion"}
136
+ )
137
+
138
+ api_results = client.search(
139
+ "api-reference",
140
+ "create embedding endpoint",
141
+ top_k=3,
142
+ filters={"deprecated": False, "version": "v1"}
143
+ )
144
+ ```
145
+
146
+ ```typescript
147
+ // TypeScript — domain-aligned collections with schemas
148
+ import { Schift } from '@schift-io/sdk';
149
+
150
+ const client = new Schift({ apiKey: 'sch_...' });
151
+
152
+ // Create collections with explicit schemas
153
+ await client.collection.create('product-docs', {
154
+ schema: {
155
+ fields: {
156
+ version: { type: 'string', filterable: true },
157
+ category: { type: 'string', filterable: true },
158
+ updated_at: { type: 'date', filterable: true },
159
+ language: { type: 'string', filterable: true },
160
+ },
161
+ },
162
+ });
163
+
164
+ await client.collection.create('support-tickets', {
165
+ schema: {
166
+ fields: {
167
+ status: { type: 'string', filterable: true },
168
+ priority: { type: 'string', filterable: true },
169
+ created_at: { type: 'date', filterable: true },
170
+ product_area: { type: 'string', filterable: true },
171
+ },
172
+ },
173
+ });
174
+
175
+ await client.collection.create('api-reference', {
176
+ schema: {
177
+ fields: {
178
+ endpoint: { type: 'string', filterable: true },
179
+ method: { type: 'string', filterable: true },
180
+ version: { type: 'string', filterable: true },
181
+ deprecated: { type: 'boolean', filterable: true },
182
+ },
183
+ },
184
+ });
185
+
186
+ // Ingest with required metadata fields
187
+ await client.collection.add('product-docs', {
188
+ documents: [
189
+ {
190
+ id: 'pd-drag-drop-001',
191
+ text: 'Drag-and-drop upload lets you add files directly to a bucket...',
192
+ metadata: {
193
+ version: '2.3',
194
+ category: 'feature',
195
+ updated_at: '2026-02-10',
196
+ language: 'en',
197
+ },
198
+ },
199
+ ],
200
+ });
201
+
202
+ // Query scoped to the right domain
203
+ const productResults = await client.search('product-docs', 'how to upload files', {
204
+ topK: 5,
205
+ filters: { category: 'guide', language: 'en' },
206
+ });
207
+
208
+ const supportResults = await client.search('support-tickets', 'upload fails with 413 error', {
209
+ topK: 8,
210
+ filters: { status: 'open', product_area: 'ingestion' },
211
+ });
212
+ ```
213
+
214
+ **Collection design guidelines:**
215
+
216
+ | Principle | Rationale |
217
+ |-----------|-----------|
218
+ | One collection per domain/product area | Keeps semantic similarity scores meaningful within a shared context |
219
+ | Define schema before ingesting | Filterable fields must be declared at creation; adding them later requires re-indexing |
220
+ | Use consistent metadata keys | Inconsistent keys (`doc_type` vs `docType`) break filter queries silently |
221
+ | Aim for 10k–1M documents per collection | Smaller: consider merging. Larger: consider sharding by time or sub-domain |
222
+ | Name collections for query intent | `product-docs` not `confluence-export-2025` |
223
+
224
+ If a single query would logically need to search across multiple domains (e.g., a universal search bar), run parallel searches against each relevant collection and merge results by score on the client side.
225
+
226
+ ## Reference
227
+
228
+ - https://docs.schift.io/collections/schema
229
+ - https://docs.schift.io/collections/design
@@ -0,0 +1,163 @@
1
+ ---
2
+ title: Combine Semantic Search with Metadata Filters for Precise Retrieval
3
+ impact: HIGH
4
+ impactDescription: Pure semantic search returns results that are thematically similar but contextually wrong — e.g., querying "quarterly revenue" returns documents from the wrong year or department. Adding metadata filters eliminates these false positives without sacrificing semantic ranking quality.
5
+ tags:
6
+ - search
7
+ - filters
8
+ - metadata
9
+ - hybrid-search
10
+ - retrieval
11
+ ---
12
+
13
+ ## Combine Semantic Search with Metadata Filters for Precise Retrieval
14
+
15
+ Semantic search ranks results by vector similarity, which captures meaning well but is blind to structured attributes like dates, departments, document types, or user ownership. Without filters, a query for "quarterly revenue" might surface a 2019 report with slightly higher similarity than the 2025 report you actually need.
16
+
17
+ The `filters` parameter in `client.search()` applies exact-match or range constraints on document metadata before semantic ranking. The semantic model then ranks only the pre-filtered candidates, giving you both precision and relevance.
18
+
19
+ ### Incorrect
20
+
21
+ Pure semantic search without filters returns semantically similar but contextually irrelevant results:
22
+
23
+ ```python
24
+ # Python — semantic-only search, no filters
25
+ from schift import Schift
26
+
27
+ client = Schift(api_key="sch_...")
28
+
29
+ # Searching for 2025 finance data — but will return results from any year/department
30
+ results = client.search(
31
+ "finance-reports",
32
+ "quarterly revenue growth",
33
+ top_k=5
34
+ )
35
+
36
+ # Results may include 2019 marketing reports that mention "revenue" prominently
37
+ for r in results:
38
+ print(r.text, r.score)
39
+ ```
40
+
41
+ ```typescript
42
+ // TypeScript — semantic-only search, no filters
43
+ import { Schift } from '@schift-io/sdk';
44
+
45
+ const client = new Schift({ apiKey: 'sch_...' });
46
+
47
+ // No filters — returns semantically similar but potentially wrong-year docs
48
+ const results = await client.search('finance-reports', 'quarterly revenue growth', {
49
+ topK: 5,
50
+ });
51
+ ```
52
+
53
+ This wastes top_k slots on irrelevant documents and requires the LLM to reason about document provenance instead of just answering.
54
+
55
+ ### Correct
56
+
57
+ Use the `filters` parameter to narrow candidates by metadata before semantic ranking:
58
+
59
+ ```python
60
+ # Python — hybrid search with metadata filters
61
+ from schift import Schift
62
+
63
+ client = Schift(api_key="sch_...")
64
+
65
+ # Narrow to 2025 finance department docs, then rank by semantic similarity
66
+ results = client.search(
67
+ "finance-reports",
68
+ "quarterly revenue growth",
69
+ top_k=5,
70
+ filters={
71
+ "year": 2025,
72
+ "department": "finance",
73
+ }
74
+ )
75
+
76
+ # All results are from the right context; semantic score reflects true relevance
77
+ for r in results:
78
+ print(r.metadata["year"], r.metadata["department"], r.score, r.text[:100])
79
+
80
+
81
+ # Multi-value filter: search across multiple years
82
+ results = client.search(
83
+ "finance-reports",
84
+ "revenue trend analysis",
85
+ top_k=10,
86
+ filters={
87
+ "year": {"$in": [2023, 2024, 2025]},
88
+ "department": "finance",
89
+ "doc_type": "quarterly-report",
90
+ }
91
+ )
92
+
93
+ # Filter by recency for support tickets
94
+ recent_tickets = client.search(
95
+ "support-tickets",
96
+ "payment gateway timeout error",
97
+ top_k=8,
98
+ filters={
99
+ "status": "open",
100
+ "created_after": "2026-01-01",
101
+ "priority": {"$in": ["high", "critical"]},
102
+ }
103
+ )
104
+ ```
105
+
106
+ ```typescript
107
+ // TypeScript — hybrid search with metadata filters
108
+ import { Schift } from '@schift-io/sdk';
109
+
110
+ const client = new Schift({ apiKey: 'sch_...' });
111
+
112
+ // Narrow to 2025 finance docs, then semantic ranking
113
+ const results = await client.search('finance-reports', 'quarterly revenue growth', {
114
+ topK: 5,
115
+ filters: {
116
+ year: 2025,
117
+ department: 'finance',
118
+ },
119
+ });
120
+
121
+ results.forEach(r => {
122
+ console.log(r.metadata.year, r.metadata.department, r.score, r.text.slice(0, 100));
123
+ });
124
+
125
+ // Multi-value filter across years
126
+ const trendResults = await client.search('finance-reports', 'revenue trend analysis', {
127
+ topK: 10,
128
+ filters: {
129
+ year: { $in: [2023, 2024, 2025] },
130
+ department: 'finance',
131
+ doc_type: 'quarterly-report',
132
+ },
133
+ });
134
+
135
+ // Filter for recent high-priority support tickets
136
+ const recentTickets = await client.search(
137
+ 'support-tickets',
138
+ 'payment gateway timeout error',
139
+ {
140
+ topK: 8,
141
+ filters: {
142
+ status: 'open',
143
+ created_after: '2026-01-01',
144
+ priority: { $in: ['high', 'critical'] },
145
+ },
146
+ }
147
+ );
148
+ ```
149
+
150
+ **Supported filter operators:**
151
+
152
+ | Operator | Meaning | Example |
153
+ |----------|---------|---------|
154
+ | exact match | `field: value` | `"year": 2025` |
155
+ | `$in` | any of these values | `"status": { "$in": ["open", "pending"] }` |
156
+ | `$gt` / `$gte` | greater than | `"score": { "$gte": 90 }` |
157
+ | `$lt` / `$lte` | less than | `"created_at": { "$lt": "2025-06-01" }` |
158
+
159
+ Define filterable fields in your collection schema at creation time — only schema-declared fields are indexed for filtering.
160
+
161
+ ## Reference
162
+
163
+ - https://docs.schift.io/search/filters
@@ -0,0 +1,134 @@
1
+ ---
2
+ title: Tune top_k and Similarity Threshold for Your Use Case
3
+ impact: CRITICAL
4
+ impactDescription: Using wrong top_k or no score threshold causes either noisy results (too many irrelevant chunks passed to LLM) or missed relevant results, directly degrading answer quality and wasting tokens.
5
+ tags:
6
+ - search
7
+ - retrieval
8
+ - top_k
9
+ - score_threshold
10
+ - precision
11
+ - recall
12
+ ---
13
+
14
+ ## Tune top_k and Similarity Threshold for Your Use Case
15
+
16
+ The default `top_k=10` is a reasonable starting point but rarely optimal. Every use case has a different precision/recall trade-off: a chatbot answering factual questions needs tight, high-confidence results, while a research tool benefits from broader recall. Passing too many low-quality chunks to an LLM inflates costs and degrades answer quality; returning too few misses the relevant context entirely.
17
+
18
+ Use `score_threshold` to discard results below a minimum similarity score. Schift returns cosine similarity scores in `[0, 1]` — results below ~0.65 are typically noise.
19
+
20
+ ### Incorrect
21
+
22
+ Using default `top_k=10` everywhere with no threshold filter:
23
+
24
+ ```python
25
+ # Python — no threshold, fixed top_k for all use cases
26
+ from schift import Schift
27
+
28
+ client = Schift(api_key="sch_...")
29
+
30
+ # Same settings for everything: chatbot QA, exploratory search, code lookup
31
+ results = client.search(collection_id, query) # top_k defaults to 10, no threshold
32
+
33
+ for r in results:
34
+ context += r.text # may include chunks with score=0.42 (largely irrelevant)
35
+ ```
36
+
37
+ ```typescript
38
+ // TypeScript — no threshold, fixed top_k for all use cases
39
+ import { Schift } from '@schift-io/sdk';
40
+
41
+ const client = new Schift({ apiKey: 'sch_...' });
42
+
43
+ // Same settings for everything
44
+ const results = await client.search(collectionId, query); // top_k=10, no threshold
45
+
46
+ const context = results.map(r => r.text).join('\n'); // includes noise
47
+ ```
48
+
49
+ This passes irrelevant chunks to the LLM, increases prompt token count, and produces hallucinated or unfocused answers.
50
+
51
+ ### Correct
52
+
53
+ Set `top_k` and `score_threshold` based on the retrieval goal:
54
+
55
+ ```python
56
+ # Python — tuned per use case
57
+ from schift import Schift
58
+
59
+ client = Schift(api_key="sch_...")
60
+
61
+ # Chatbot Q&A: tight precision, high confidence
62
+ qa_results = client.search(
63
+ collection_id,
64
+ query,
65
+ top_k=3, # few but precise chunks
66
+ score_threshold=0.78 # discard anything below 78% similarity
67
+ )
68
+
69
+ # Research / exploratory: wider recall, lower threshold
70
+ explore_results = client.search(
71
+ collection_id,
72
+ query,
73
+ top_k=30, # broad recall for exploration
74
+ score_threshold=0.55 # accept weaker matches
75
+ )
76
+
77
+ # Code retrieval: moderate, function-level precision
78
+ code_results = client.search(
79
+ collection_id,
80
+ query,
81
+ top_k=5,
82
+ score_threshold=0.70
83
+ )
84
+
85
+ # Always check: if nothing passes threshold, surface a "no results" message
86
+ if not qa_results:
87
+ return "I couldn't find relevant information for your question."
88
+ ```
89
+
90
+ ```typescript
91
+ // TypeScript — tuned per use case
92
+ import { Schift } from '@schift-io/sdk';
93
+
94
+ const client = new Schift({ apiKey: 'sch_...' });
95
+
96
+ // Chatbot Q&A: tight precision, high confidence
97
+ const qaResults = await client.search(collectionId, query, {
98
+ topK: 3,
99
+ scoreThreshold: 0.78,
100
+ });
101
+
102
+ // Research / exploratory: wider recall
103
+ const exploreResults = await client.search(collectionId, query, {
104
+ topK: 30,
105
+ scoreThreshold: 0.55,
106
+ });
107
+
108
+ // Code retrieval
109
+ const codeResults = await client.search(collectionId, query, {
110
+ topK: 5,
111
+ scoreThreshold: 0.70,
112
+ });
113
+
114
+ // Handle empty results gracefully
115
+ if (qaResults.length === 0) {
116
+ return "I couldn't find relevant information for your question.";
117
+ }
118
+ ```
119
+
120
+ **Rule of thumb by use case:**
121
+
122
+ | Use Case | top_k | score_threshold |
123
+ |----------|-------|-----------------|
124
+ | Chatbot Q&A | 3–5 | 0.75–0.82 |
125
+ | Document Q&A (long docs) | 5–8 | 0.70–0.78 |
126
+ | Code retrieval | 3–6 | 0.68–0.75 |
127
+ | Research / exploration | 20–50 | 0.50–0.62 |
128
+ | Fact verification | 1–3 | 0.80–0.88 |
129
+
130
+ Start with these ranges and adjust based on observed result quality. Log score distributions during development to calibrate thresholds for your data.
131
+
132
+ ## Reference
133
+
134
+ - https://docs.schift.io/search/parameters
schift_cli/display.py ADDED
@@ -0,0 +1,85 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Any, Sequence
4
+
5
+ from rich.console import Console
6
+ from rich.panel import Panel
7
+ from rich.progress import (
8
+ BarColumn,
9
+ MofNCompleteColumn,
10
+ Progress,
11
+ SpinnerColumn,
12
+ TextColumn,
13
+ TimeRemainingColumn,
14
+ )
15
+ from rich.table import Table
16
+
17
+ console = Console()
18
+ error_console = Console(stderr=True)
19
+
20
+
21
+ # -- Tables -------------------------------------------------------------------
22
+
23
+ def print_table(
24
+ title: str,
25
+ columns: Sequence[str],
26
+ rows: Sequence[Sequence[Any]],
27
+ *,
28
+ caption: str | None = None,
29
+ ) -> None:
30
+ table = Table(title=title, caption=caption, show_lines=False)
31
+ for col in columns:
32
+ table.add_column(col, overflow="fold")
33
+ for row in rows:
34
+ table.add_row(*(str(v) for v in row))
35
+ console.print(table)
36
+
37
+
38
+ def print_kv(title: str, data: dict[str, Any]) -> None:
39
+ """Print a key-value panel (e.g. model details, db stats)."""
40
+ table = Table(show_header=False, box=None, padding=(0, 2))
41
+ table.add_column("Key", style="bold cyan", no_wrap=True)
42
+ table.add_column("Value")
43
+ for k, v in data.items():
44
+ table.add_row(k, str(v))
45
+ console.print(Panel(table, title=title, border_style="blue"))
46
+
47
+
48
+ # -- Status messages ----------------------------------------------------------
49
+
50
+ def success(msg: str) -> None:
51
+ console.print(f"[bold green]OK[/] {msg}")
52
+
53
+
54
+ def info(msg: str) -> None:
55
+ console.print(f"[bold blue]--[/] {msg}")
56
+
57
+
58
+ def warn(msg: str) -> None:
59
+ error_console.print(f"[bold yellow]WARN[/] {msg}")
60
+
61
+
62
+ def error(msg: str) -> None:
63
+ error_console.print(f"[bold red]ERROR[/] {msg}")
64
+
65
+
66
+ # -- Progress -----------------------------------------------------------------
67
+
68
+ def make_progress() -> Progress:
69
+ return Progress(
70
+ SpinnerColumn(),
71
+ TextColumn("[progress.description]{task.description}"),
72
+ BarColumn(),
73
+ MofNCompleteColumn(),
74
+ TimeRemainingColumn(),
75
+ console=console,
76
+ )
77
+
78
+
79
+ def spinner(description: str = "Working...") -> Progress:
80
+ """A simple indeterminate spinner for operations without a known total."""
81
+ return Progress(
82
+ SpinnerColumn(),
83
+ TextColumn("[progress.description]{task.description}"),
84
+ console=console,
85
+ )
schift_cli/main.py ADDED
@@ -0,0 +1,39 @@
1
+ from __future__ import annotations
2
+
3
+ import click
4
+
5
+ from schift_cli import __version__
6
+ from schift_cli.commands.auth import auth
7
+ from schift_cli.commands.bench import bench
8
+ from schift_cli.commands.catalog import catalog
9
+ from schift_cli.commands.db import db
10
+ from schift_cli.commands.embed import embed
11
+ from schift_cli.commands.migrate import migrate
12
+ from schift_cli.commands.query import query
13
+ from schift_cli.commands.skill import skill
14
+ from schift_cli.commands.usage import usage
15
+
16
+
17
+ @click.group()
18
+ @click.version_option(version=__version__, prog_name="schift")
19
+ def cli() -> None:
20
+ """Schift CLI -- AI Agent Framework.
21
+
22
+ Manage agents, embedding models, vector collections, and migrations
23
+ from the command line.
24
+ """
25
+
26
+
27
+ cli.add_command(auth)
28
+ cli.add_command(catalog)
29
+ cli.add_command(embed)
30
+ cli.add_command(bench)
31
+ cli.add_command(migrate)
32
+ cli.add_command(db)
33
+ cli.add_command(query)
34
+ cli.add_command(skill)
35
+ cli.add_command(usage)
36
+
37
+
38
+ if __name__ == "__main__":
39
+ cli()
@@ -0,0 +1,12 @@
1
+ Metadata-Version: 2.4
2
+ Name: schift-cli
3
+ Version: 0.1.0
4
+ Summary: Schift CLI — manage agents, embeddings, and vector collections
5
+ License: MIT
6
+ Requires-Python: >=3.10
7
+ Requires-Dist: click>=8.1
8
+ Requires-Dist: httpx>=0.27
9
+ Requires-Dist: rich>=13.0
10
+ Provides-Extra: dev
11
+ Requires-Dist: pytest>=8.0; extra == "dev"
12
+ Requires-Dist: pytest-cov>=5.0; extra == "dev"