ctxos-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.
@@ -0,0 +1,7 @@
1
+ """
2
+ Context OS — CLI
3
+
4
+ Command-line interface for Context OS operations.
5
+ """
6
+
7
+ __version__ = "0.1.0"
context_cli/main.py ADDED
@@ -0,0 +1,384 @@
1
+ """
2
+ Context OS — CLI Main
3
+
4
+ Entry point for the context command.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ import os
10
+ import sys
11
+ from typing import Optional
12
+
13
+ import click
14
+ from rich.console import Console
15
+ from rich.table import Table
16
+ from rich.panel import Panel
17
+ from rich import print as rprint
18
+
19
+ from . import __version__
20
+
21
+ console = Console()
22
+
23
+ API_URL = os.getenv("CONTEXT_API_URL", os.getenv("CONTEXT_OS_URL", "http://localhost:8000"))
24
+ API_KEY = os.getenv("CONTEXT_API_KEY", os.getenv("CONTEXT_OS_API_KEY"))
25
+
26
+
27
+ def _get_client():
28
+ """Get HTTP client for API calls."""
29
+ import httpx
30
+
31
+ headers = {"Content-Type": "application/json"}
32
+ if API_KEY:
33
+ headers["Authorization"] = f"Bearer {API_KEY}"
34
+
35
+ return httpx.Client(
36
+ base_url=API_URL,
37
+ headers=headers,
38
+ timeout=30.0,
39
+ )
40
+
41
+
42
+ @click.group()
43
+ @click.version_option(version=__version__, prog_name="context-cli")
44
+ @click.option("--url", envvar="CONTEXT_API_URL", default="http://localhost:8000", help="API server URL")
45
+ @click.option("--key", envvar="CONTEXT_API_KEY", default=None, help="API key")
46
+ @click.pass_context
47
+ def cli(ctx: click.Context, url: str, key: Optional[str]):
48
+ """Context OS — AI Agent Infrastructure CLI"""
49
+ ctx.ensure_object(dict)
50
+ ctx.obj["url"] = url
51
+ ctx.obj["key"] = key
52
+
53
+
54
+ # --- Memory Commands ---
55
+
56
+ @cli.group()
57
+ def memory():
58
+ """Memory operations"""
59
+ pass
60
+
61
+
62
+ @memory.command("add")
63
+ @click.option("--content", "-c", required=True, help="Memory content")
64
+ @click.option("--type", "memory_type", default="episodic", help="Memory type (episodic/semantic/procedural)")
65
+ @click.option("--importance", default="medium", help="Importance (low/medium/high/critical)")
66
+ @click.option("--tags", help="Comma-separated tags")
67
+ @click.pass_context
68
+ def memory_add(ctx: click.Context, content: str, memory_type: str, importance: str, tags: Optional[str]):
69
+ """Add a new memory"""
70
+ tag_list = [t.strip() for t in tags.split(",")] if tags else []
71
+
72
+ payload = {
73
+ "content": content,
74
+ "memory_type": memory_type,
75
+ "importance": importance,
76
+ "tags": tag_list,
77
+ }
78
+
79
+ with _get_client() as client:
80
+ response = client.post("/api/v1/memory", json=payload)
81
+ data = response.json()
82
+
83
+ console.print(Panel(
84
+ f"[green]✓[/green] Memory created: [bold]{data['id']}[/bold]\n"
85
+ f"Type: {data['memory_type']} | Importance: {data['importance']}\n"
86
+ f"Tags: {', '.join(data.get('tags', []))}",
87
+ title="Memory Added",
88
+ ))
89
+
90
+
91
+ @memory.command("get")
92
+ @click.argument("memory_id")
93
+ @click.pass_context
94
+ def memory_get(ctx: click.Context, memory_id: str):
95
+ """Get a memory by ID"""
96
+ with _get_client() as client:
97
+ response = client.get(f"/api/v1/memory/{memory_id}")
98
+ data = response.json()
99
+
100
+ table = Table(title=f"Memory: {data['id']}")
101
+ table.add_column("Field", style="cyan")
102
+ table.add_column("Value")
103
+ table.add_row("Content", data["content"])
104
+ table.add_row("Type", data["memory_type"])
105
+ table.add_row("Importance", data["importance"])
106
+ table.add_row("Tags", ", ".join(data.get("tags", [])))
107
+ table.add_row("Created", data.get("created_at", "N/A"))
108
+
109
+ console.print(table)
110
+
111
+
112
+ @memory.command("search")
113
+ @click.argument("query")
114
+ @click.option("--limit", "-n", default=5, help="Max results")
115
+ @click.option("--type", "memory_type", default=None, help="Filter by type")
116
+ @click.pass_context
117
+ def memory_search(ctx: click.Context, query: str, limit: int, memory_type: Optional[str]):
118
+ """Search memories"""
119
+ payload = {
120
+ "query": query,
121
+ "top_k": limit,
122
+ }
123
+ if memory_type:
124
+ payload["memory_type"] = memory_type
125
+
126
+ with _get_client() as client:
127
+ response = client.post("/api/v1/memory/search", json=payload)
128
+ data = response.json()
129
+
130
+ results = data.get("results", [])
131
+ if not results:
132
+ console.print("[yellow]No results found[/yellow]")
133
+ return
134
+
135
+ table = Table(title=f"Search Results for '{query}'")
136
+ table.add_column("#", style="dim")
137
+ table.add_column("Score", style="green")
138
+ table.add_column("Content")
139
+ table.add_column("Type")
140
+
141
+ for i, r in enumerate(results, 1):
142
+ table.add_row(
143
+ str(i),
144
+ f"{r['score']:.2f}",
145
+ r["memory"]["content"][:80] + ("..." if len(r["memory"]["content"]) > 80 else ""),
146
+ r["memory"]["memory_type"],
147
+ )
148
+
149
+ console.print(table)
150
+
151
+
152
+ @memory.command("list")
153
+ @click.option("--type", "memory_type", default=None, help="Filter by type")
154
+ @click.option("--limit", "-n", default=20, help="Max results")
155
+ @click.pass_context
156
+ def memory_list(ctx: click.Context, memory_type: Optional[str], limit: int):
157
+ """List memories"""
158
+ params = {"limit": limit}
159
+ if memory_type:
160
+ params["memory_type"] = memory_type
161
+
162
+ with _get_client() as client:
163
+ response = client.get("/api/v1/memory", params=params)
164
+ data = response.json()
165
+
166
+ memories = data.get("memories", [])
167
+ if not memories:
168
+ console.print("[yellow]No memories found[/yellow]")
169
+ return
170
+
171
+ table = Table(title="Memories")
172
+ table.add_column("ID", style="cyan")
173
+ table.add_column("Content")
174
+ table.add_column("Type")
175
+ table.add_column("Importance")
176
+
177
+ for m in memories:
178
+ content = m["content"][:60] + ("..." if len(m["content"]) > 60 else "")
179
+ table.add_row(m["id"], content, m["memory_type"], m["importance"])
180
+
181
+ console.print(table)
182
+
183
+
184
+ @memory.command("delete")
185
+ @click.argument("memory_id")
186
+ @click.confirmation_option(prompt="Are you sure you want to delete this memory?")
187
+ @click.pass_context
188
+ def memory_delete(ctx: click.Context, memory_id: str):
189
+ """Delete a memory"""
190
+ with _get_client() as client:
191
+ response = client.delete(f"/api/v1/memory/{memory_id}")
192
+ if response.status_code == 200:
193
+ console.print(f"[green]✓[/green] Memory {memory_id} deleted")
194
+ else:
195
+ console.print(f"[red]✗[/red] Failed to delete memory")
196
+
197
+
198
+ # --- Search Commands ---
199
+
200
+ @cli.group()
201
+ def search():
202
+ """Search operations"""
203
+ pass
204
+
205
+
206
+ @search.command("web")
207
+ @click.argument("query")
208
+ @click.option("--limit", "-n", default=5, help="Max results")
209
+ @click.pass_context
210
+ def search_web(ctx: click.Context, query: str, limit: int):
211
+ """Web search"""
212
+ with _get_client() as client:
213
+ response = client.post("/api/v1/search/web", json={"query": query, "max_results": limit})
214
+ data = response.json()
215
+
216
+ results = data.get("results", [])
217
+ if not results:
218
+ console.print("[yellow]No results found[/yellow]")
219
+ return
220
+
221
+ for i, r in enumerate(results, 1):
222
+ console.print(f"\n[bold cyan]{i}. {r['title']}[/bold cyan]")
223
+ console.print(f" [link={r['url']}]{r['url']}[/link]")
224
+ console.print(f" {r['snippet'][:120]}...")
225
+
226
+
227
+ @search.command("internal")
228
+ @click.argument("query")
229
+ @click.option("--limit", "-n", default=10, help="Max results")
230
+ @click.pass_context
231
+ def search_internal(ctx: click.Context, query: str, limit: int):
232
+ """Internal hybrid search"""
233
+ with _get_client() as client:
234
+ response = client.post("/api/v1/search/internal", json={"query": query, "top_k": limit})
235
+ data = response.json()
236
+
237
+ results = data.get("results", [])
238
+ if not results:
239
+ console.print("[yellow]No results found[/yellow]")
240
+ return
241
+
242
+ table = Table(title=f"Internal Search: '{query}'")
243
+ table.add_column("#", style="dim")
244
+ table.add_column("Score", style="green")
245
+ table.add_column("Title")
246
+ table.add_column("URL")
247
+
248
+ for i, r in enumerate(results, 1):
249
+ table.add_row(str(i), f"{r.get('score', 0):.2f}", r["title"], r["url"])
250
+
251
+ console.print(table)
252
+
253
+
254
+ # --- Crawl Commands ---
255
+
256
+ @cli.group()
257
+ def crawl():
258
+ """Web crawl operations"""
259
+ pass
260
+
261
+
262
+ @crawl.command("scrape")
263
+ @click.argument("url")
264
+ @click.pass_context
265
+ def crawl_scrape(ctx: click.Context, url: str):
266
+ """Scrape a single URL"""
267
+ with _get_client() as client:
268
+ response = client.post("/api/v1/crawl/scrape", json={"url": url})
269
+ data = response.json()
270
+
271
+ console.print(Panel(
272
+ f"[bold]{data.get('title', 'N/A')}[/bold]\n\n"
273
+ f"{data['content'][:500]}{'...' if len(data['content']) > 500 else ''}",
274
+ title=f"Scraped: {url}",
275
+ ))
276
+
277
+
278
+ @crawl.command("map")
279
+ @click.argument("url")
280
+ @click.option("--limit", "-n", default=50, help="Max pages")
281
+ @click.pass_context
282
+ def crawl_map(ctx: click.Context, url: str, limit: int):
283
+ """Map a website (get all URLs)"""
284
+ with _get_client() as client:
285
+ response = client.post("/api/v1/crawl/map", json={"url": url, "max_pages": limit})
286
+ data = response.json()
287
+
288
+ urls = data.get("urls", [])
289
+ if not urls:
290
+ console.print("[yellow]No URLs found[/yellow]")
291
+ return
292
+
293
+ console.print(f"[green]Found {len(urls)} URLs:[/green]")
294
+ for u in urls[:limit]:
295
+ console.print(f" {u}")
296
+
297
+
298
+ # --- Knowledge Commands ---
299
+
300
+ @cli.group()
301
+ def knowledge():
302
+ """Knowledge graph operations"""
303
+ pass
304
+
305
+
306
+ @knowledge.command("create-entity")
307
+ @click.option("--name", "-n", required=True, help="Entity name")
308
+ @click.option("--type", "entity_type", default="concept", help="Entity type")
309
+ @click.option("--description", "-d", default=None, help="Description")
310
+ @click.pass_context
311
+ def knowledge_create_entity(ctx: click.Context, name: str, entity_type: str, description: Optional[str]):
312
+ """Create a knowledge graph entity"""
313
+ payload = {
314
+ "name": name,
315
+ "entity_type": entity_type,
316
+ }
317
+ if description:
318
+ payload["description"] = description
319
+
320
+ with _get_client() as client:
321
+ response = client.post("/api/v1/knowledge/entities", json=payload)
322
+ data = response.json()
323
+
324
+ console.print(Panel(
325
+ f"[green]✓[/green] Entity created: [bold]{data['id']}[/bold]\n"
326
+ f"Name: {data['name']} | Type: {data['entity_type']}\n"
327
+ f"Description: {data.get('description', 'N/A')}",
328
+ title="Entity Created",
329
+ ))
330
+
331
+
332
+ @knowledge.command("search")
333
+ @click.argument("query")
334
+ @click.option("--type", "entity_type", default=None, help="Filter by type")
335
+ @click.option("--limit", "-n", default=10, help="Max results")
336
+ @click.pass_context
337
+ def knowledge_search(ctx: click.Context, query: str, entity_type: Optional[str], limit: int):
338
+ """Search entities"""
339
+ payload = {"query": query, "top_k": limit}
340
+ if entity_type:
341
+ payload["entity_type"] = entity_type
342
+
343
+ with _get_client() as client:
344
+ response = client.post("/api/v1/knowledge/search", json=payload)
345
+ data = response.json()
346
+
347
+ entities = data.get("entities", [])
348
+ if not entities:
349
+ console.print("[yellow]No entities found[/yellow]")
350
+ return
351
+
352
+ table = Table(title=f"Entity Search: '{query}'")
353
+ table.add_column("ID", style="cyan")
354
+ table.add_column("Name", style="bold")
355
+ table.add_column("Type")
356
+ table.add_column("Description")
357
+
358
+ for e in entities:
359
+ desc = (e.get("description") or "N/A")[:50]
360
+ table.add_row(e["id"], e["name"], e["entity_type"], desc)
361
+
362
+ console.print(table)
363
+
364
+
365
+ # --- Health Command ---
366
+
367
+ @cli.command()
368
+ def health():
369
+ """Check API health"""
370
+ with _get_client() as client:
371
+ response = client.get("/api/v1/health")
372
+ data = response.json()
373
+
374
+ status_color = "green" if data.get("status") == "ok" else "red"
375
+ console.print(Panel(
376
+ f"Status: [{status_color}]{data.get('status', 'unknown')}[/{status_color}]\n"
377
+ f"Version: {data.get('version', 'N/A')}\n"
378
+ f"Services: {', '.join(data.get('services', {}).keys()) or 'N/A'}",
379
+ title="API Health",
380
+ ))
381
+
382
+
383
+ if __name__ == "__main__":
384
+ cli()
@@ -0,0 +1,112 @@
1
+ Metadata-Version: 2.4
2
+ Name: ctxos-cli
3
+ Version: 0.1.0
4
+ Summary: CLI for Context OS — Memory, Search, Crawl, Knowledge
5
+ Project-URL: Homepage, https://github.com/AmanSagar0607/Context-OS
6
+ Project-URL: Repository, https://github.com/AmanSagar0607/Context-OS
7
+ Author-email: Aman Sagar <aman@amansagar.in>
8
+ License-Expression: MIT
9
+ Keywords: ai,cli,context,crawl,knowledge-graph,mcp,memory,search
10
+ Classifier: Development Status :: 3 - Alpha
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: License :: OSI Approved :: MIT License
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
15
+ Requires-Python: >=3.10
16
+ Requires-Dist: click>=8.0.0
17
+ Requires-Dist: httpx>=0.27.0
18
+ Requires-Dist: pydantic>=2.0.0
19
+ Requires-Dist: rich>=13.0.0
20
+ Provides-Extra: dev
21
+ Requires-Dist: pytest>=8.0.0; extra == 'dev'
22
+ Requires-Dist: ruff>=0.4.0; extra == 'dev'
23
+ Description-Content-Type: text/markdown
24
+
25
+ # Context OS — CLI
26
+
27
+ Command-line interface for Context OS operations.
28
+
29
+ ## Installation
30
+
31
+ ```bash
32
+ pip install context-cli
33
+ ```
34
+
35
+ Or install from source:
36
+
37
+ ```bash
38
+ git clone https://github.com/AmanSagar0607/Context-OS.git
39
+ cd Context-OS/cli
40
+ pip install -e .
41
+ ```
42
+
43
+ ## Configuration
44
+
45
+ Set environment variables:
46
+
47
+ ```bash
48
+ export CONTEXT_API_URL="http://localhost:8000" # API server
49
+ export CONTEXT_API_KEY="your-api-key" # API key (optional)
50
+ ```
51
+
52
+ ## Commands
53
+
54
+ ### Memory
55
+
56
+ ```bash
57
+ # Add a memory
58
+ context memory add -c "User prefers dark mode" --type semantic --importance high --tags "ui,preferences"
59
+
60
+ # Get a memory
61
+ context memory get <memory-id>
62
+
63
+ # Search memories
64
+ context memory search "dark mode" --limit 10
65
+
66
+ # List memories
67
+ context memory list --type semantic --limit 20
68
+
69
+ # Delete a memory
70
+ context memory delete <memory-id>
71
+ ```
72
+
73
+ ### Search
74
+
75
+ ```bash
76
+ # Web search
77
+ context search web "AI news 2025" --limit 5
78
+
79
+ # Internal hybrid search
80
+ context search internal "user preferences" --limit 10
81
+ ```
82
+
83
+ ### Crawl
84
+
85
+ ```bash
86
+ # Scrape a URL
87
+ context crawl scrape https://example.com
88
+
89
+ # Map a website
90
+ context crawl map https://example.com --limit 50
91
+ ```
92
+
93
+ ### Knowledge
94
+
95
+ ```bash
96
+ # Create an entity
97
+ context knowledge create-entity --name "GPT-4" --type model --description "Large language model"
98
+
99
+ # Search entities
100
+ context knowledge search "language models" --limit 10
101
+ ```
102
+
103
+ ### Health
104
+
105
+ ```bash
106
+ # Check API health
107
+ context health
108
+ ```
109
+
110
+ ## License
111
+
112
+ MIT License
@@ -0,0 +1,6 @@
1
+ context_cli/__init__.py,sha256=hkr0-cFC4ZXlceMtsixIB95FqReF1gWdzP4zkI815UQ,100
2
+ context_cli/main.py,sha256=646MUe5XkyGH3bdlQOnsDggaHNPbbUwv1757qaBjBuA,12047
3
+ ctxos_cli-0.1.0.dist-info/METADATA,sha256=Pxu6GL-CFKnkdR_bbmCRCy8_Sz_HThCHubE9WGPAsLw,2375
4
+ ctxos_cli-0.1.0.dist-info/WHEEL,sha256=mffPy8wBnZQn2VnJUU5jE99KsxaSfiyMHV9Yt0aLVxs,87
5
+ ctxos_cli-0.1.0.dist-info/entry_points.txt,sha256=gAdmF2y8iCM6cw8-0nZYtqiccTUmIVoNd_tHmnSa2Os,51
6
+ ctxos_cli-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.30.1
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ contextos = context_cli.main:cli