mindsystem-cc 3.0.0
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.
- package/LICENSE +21 -0
- package/README.md +501 -0
- package/agents/ms-codebase-mapper.md +739 -0
- package/agents/ms-debugger.md +1184 -0
- package/agents/ms-designer.md +414 -0
- package/agents/ms-executor.md +760 -0
- package/agents/ms-integration-checker.md +423 -0
- package/agents/ms-milestone-auditor.md +448 -0
- package/agents/ms-mock-generator.md +182 -0
- package/agents/ms-plan-checker.md +746 -0
- package/agents/ms-research-synthesizer.md +248 -0
- package/agents/ms-researcher.md +962 -0
- package/agents/ms-roadmapper.md +606 -0
- package/agents/ms-verifier.md +779 -0
- package/agents/ms-verify-fixer.md +124 -0
- package/bin/install.js +296 -0
- package/commands/ms/add-phase.md +207 -0
- package/commands/ms/add-todo.md +182 -0
- package/commands/ms/audit-milestone.md +318 -0
- package/commands/ms/check-phase.md +162 -0
- package/commands/ms/check-todos.md +217 -0
- package/commands/ms/complete-milestone.md +137 -0
- package/commands/ms/create-roadmap.md +273 -0
- package/commands/ms/debug.md +149 -0
- package/commands/ms/define-requirements.md +121 -0
- package/commands/ms/design-phase.md +341 -0
- package/commands/ms/discuss-milestone.md +48 -0
- package/commands/ms/discuss-phase.md +60 -0
- package/commands/ms/do-work.md +90 -0
- package/commands/ms/execute-phase.md +289 -0
- package/commands/ms/help.md +623 -0
- package/commands/ms/insert-phase.md +227 -0
- package/commands/ms/list-phase-assumptions.md +50 -0
- package/commands/ms/map-codebase.md +71 -0
- package/commands/ms/new-milestone.md +193 -0
- package/commands/ms/new-project.md +338 -0
- package/commands/ms/pause-work.md +123 -0
- package/commands/ms/plan-milestone-gaps.md +285 -0
- package/commands/ms/plan-phase.md +105 -0
- package/commands/ms/progress.md +370 -0
- package/commands/ms/remove-phase.md +338 -0
- package/commands/ms/research-phase.md +175 -0
- package/commands/ms/research-project.md +339 -0
- package/commands/ms/resume-work.md +40 -0
- package/commands/ms/review-design.md +484 -0
- package/commands/ms/simplify-flutter.md +193 -0
- package/commands/ms/update.md +159 -0
- package/commands/ms/verify-work.md +92 -0
- package/commands/ms/whats-new.md +124 -0
- package/mindsystem/references/checkpoints.md +788 -0
- package/mindsystem/references/continuation-format.md +255 -0
- package/mindsystem/references/debugging/debugging-mindset.md +11 -0
- package/mindsystem/references/debugging/hypothesis-testing.md +11 -0
- package/mindsystem/references/debugging/investigation-techniques.md +11 -0
- package/mindsystem/references/debugging/verification-patterns.md +11 -0
- package/mindsystem/references/debugging/when-to-research.md +11 -0
- package/mindsystem/references/git-integration.md +254 -0
- package/mindsystem/references/goal-backward.md +286 -0
- package/mindsystem/references/mock-patterns.md +294 -0
- package/mindsystem/references/plan-format.md +473 -0
- package/mindsystem/references/principles.md +73 -0
- package/mindsystem/references/questioning.md +140 -0
- package/mindsystem/references/research-pitfalls.md +233 -0
- package/mindsystem/references/scope-estimation.md +256 -0
- package/mindsystem/references/tdd.md +263 -0
- package/mindsystem/references/verification-patterns.md +595 -0
- package/mindsystem/templates/DEBUG.md +159 -0
- package/mindsystem/templates/UAT.md +403 -0
- package/mindsystem/templates/adhoc-summary.md +153 -0
- package/mindsystem/templates/codebase/architecture.md +255 -0
- package/mindsystem/templates/codebase/concerns.md +310 -0
- package/mindsystem/templates/codebase/conventions.md +307 -0
- package/mindsystem/templates/codebase/integrations.md +280 -0
- package/mindsystem/templates/codebase/stack.md +186 -0
- package/mindsystem/templates/codebase/structure.md +285 -0
- package/mindsystem/templates/codebase/testing.md +480 -0
- package/mindsystem/templates/config.json +26 -0
- package/mindsystem/templates/context.md +140 -0
- package/mindsystem/templates/continue-here.md +78 -0
- package/mindsystem/templates/debug-subagent-prompt.md +91 -0
- package/mindsystem/templates/design-iteration.md +208 -0
- package/mindsystem/templates/design.md +417 -0
- package/mindsystem/templates/discovery.md +146 -0
- package/mindsystem/templates/milestone-archive.md +123 -0
- package/mindsystem/templates/milestone-context.md +93 -0
- package/mindsystem/templates/milestone.md +115 -0
- package/mindsystem/templates/phase-prompt.md +574 -0
- package/mindsystem/templates/project.md +184 -0
- package/mindsystem/templates/requirements.md +231 -0
- package/mindsystem/templates/research-project/ARCHITECTURE.md +204 -0
- package/mindsystem/templates/research-project/FEATURES.md +147 -0
- package/mindsystem/templates/research-project/PITFALLS.md +200 -0
- package/mindsystem/templates/research-project/STACK.md +120 -0
- package/mindsystem/templates/research-project/SUMMARY.md +170 -0
- package/mindsystem/templates/research-subagent-prompt.md +92 -0
- package/mindsystem/templates/research.md +529 -0
- package/mindsystem/templates/roadmap.md +214 -0
- package/mindsystem/templates/state.md +224 -0
- package/mindsystem/templates/summary.md +269 -0
- package/mindsystem/templates/user-setup.md +323 -0
- package/mindsystem/templates/verification-report.md +322 -0
- package/mindsystem/workflows/complete-milestone.md +759 -0
- package/mindsystem/workflows/create-milestone.md +203 -0
- package/mindsystem/workflows/debug.md +14 -0
- package/mindsystem/workflows/define-requirements.md +330 -0
- package/mindsystem/workflows/diagnose-issues.md +241 -0
- package/mindsystem/workflows/discovery-phase.md +293 -0
- package/mindsystem/workflows/discuss-milestone.md +310 -0
- package/mindsystem/workflows/discuss-phase.md +237 -0
- package/mindsystem/workflows/do-work.md +359 -0
- package/mindsystem/workflows/execute-phase.md +644 -0
- package/mindsystem/workflows/execute-plan.md +1828 -0
- package/mindsystem/workflows/generate-mocks.md +187 -0
- package/mindsystem/workflows/list-phase-assumptions.md +178 -0
- package/mindsystem/workflows/map-codebase.md +289 -0
- package/mindsystem/workflows/plan-phase.md +876 -0
- package/mindsystem/workflows/research-phase.md +17 -0
- package/mindsystem/workflows/research-project.md +23 -0
- package/mindsystem/workflows/resume-project.md +311 -0
- package/mindsystem/workflows/transition.md +564 -0
- package/mindsystem/workflows/verify-phase.md +629 -0
- package/mindsystem/workflows/verify-work.md +823 -0
- package/package.json +32 -0
- package/scripts/generate-phase-patch.sh +169 -0
- package/scripts/ms-lookup/README.md +112 -0
- package/scripts/ms-lookup/ms_lookup/__init__.py +3 -0
- package/scripts/ms-lookup/ms_lookup/__main__.py +6 -0
- package/scripts/ms-lookup/ms_lookup/backends/__init__.py +6 -0
- package/scripts/ms-lookup/ms_lookup/backends/context7.py +219 -0
- package/scripts/ms-lookup/ms_lookup/backends/perplexity.py +145 -0
- package/scripts/ms-lookup/ms_lookup/cache.py +48 -0
- package/scripts/ms-lookup/ms_lookup/cli.py +219 -0
- package/scripts/ms-lookup/ms_lookup/config.py +23 -0
- package/scripts/ms-lookup/ms_lookup/errors.py +24 -0
- package/scripts/ms-lookup/ms_lookup/output.py +49 -0
- package/scripts/ms-lookup/ms_lookup/tokens.py +56 -0
- package/scripts/ms-lookup/pyproject.toml +17 -0
- package/scripts/ms-lookup/uv.lock +207 -0
- package/scripts/ms-lookup-wrapper.sh +21 -0
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
"""Caching layer using diskcache."""
|
|
2
|
+
|
|
3
|
+
import hashlib
|
|
4
|
+
from typing import Any
|
|
5
|
+
|
|
6
|
+
import diskcache
|
|
7
|
+
|
|
8
|
+
from ms_lookup.config import CACHE_DIR, CACHE_TTL_DOCS, CACHE_TTL_DEEP
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def _get_cache() -> diskcache.Cache:
|
|
12
|
+
"""Get or create cache instance."""
|
|
13
|
+
CACHE_DIR.mkdir(parents=True, exist_ok=True)
|
|
14
|
+
return diskcache.Cache(str(CACHE_DIR))
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def _make_key(command: str, *args: str, max_tokens: int | None = None) -> str:
|
|
18
|
+
"""Create cache key from command, args, and optionally max_tokens."""
|
|
19
|
+
key_parts = [command] + list(args)
|
|
20
|
+
if max_tokens is not None:
|
|
21
|
+
key_parts.append(str(max_tokens))
|
|
22
|
+
key_string = ":".join(key_parts)
|
|
23
|
+
key_hash = hashlib.sha256(key_string.encode()).hexdigest()[:16]
|
|
24
|
+
return f"{command}:{key_hash}"
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def get_cached(command: str, *args: str, max_tokens: int | None = None) -> Any | None:
|
|
28
|
+
"""Get cached result if exists and not expired."""
|
|
29
|
+
cache = _get_cache()
|
|
30
|
+
key = _make_key(command, *args, max_tokens=max_tokens)
|
|
31
|
+
return cache.get(key)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def set_cached(command: str, *args: str, max_tokens: int | None = None, value: Any) -> None:
|
|
35
|
+
"""Cache a result with appropriate TTL."""
|
|
36
|
+
cache = _get_cache()
|
|
37
|
+
key = _make_key(command, *args, max_tokens=max_tokens)
|
|
38
|
+
|
|
39
|
+
# Select TTL based on command
|
|
40
|
+
ttl = CACHE_TTL_DOCS if command == "docs" else CACHE_TTL_DEEP
|
|
41
|
+
|
|
42
|
+
cache.set(key, value, expire=ttl)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def clear_cache() -> None:
|
|
46
|
+
"""Clear all cached entries."""
|
|
47
|
+
cache = _get_cache()
|
|
48
|
+
cache.clear()
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
"""Main CLI entry point for ms-lookup."""
|
|
2
|
+
|
|
3
|
+
import sys
|
|
4
|
+
from typing import Optional
|
|
5
|
+
|
|
6
|
+
import typer
|
|
7
|
+
|
|
8
|
+
from ms_lookup import __version__
|
|
9
|
+
from ms_lookup.backends.context7 import Context7Client
|
|
10
|
+
from ms_lookup.backends.perplexity import PerplexityClient
|
|
11
|
+
from ms_lookup.cache import get_cached, set_cached
|
|
12
|
+
from ms_lookup.config import DEFAULT_MAX_TOKENS
|
|
13
|
+
from ms_lookup.errors import MsLookupError
|
|
14
|
+
from ms_lookup.output import format_error, format_success, output_json
|
|
15
|
+
from ms_lookup.tokens import estimate_tokens, truncate_results
|
|
16
|
+
|
|
17
|
+
app = typer.Typer(
|
|
18
|
+
name="ms-lookup",
|
|
19
|
+
help="Mindsystem research CLI - Context7 docs and Perplexity deep research",
|
|
20
|
+
add_completion=False,
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def version_callback(value: bool) -> None:
|
|
25
|
+
"""Print version and exit."""
|
|
26
|
+
if value:
|
|
27
|
+
typer.echo(f"ms-lookup {__version__}")
|
|
28
|
+
raise typer.Exit()
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
@app.callback()
|
|
32
|
+
def main(
|
|
33
|
+
version: Optional[bool] = typer.Option(
|
|
34
|
+
None,
|
|
35
|
+
"--version",
|
|
36
|
+
"-v",
|
|
37
|
+
callback=version_callback,
|
|
38
|
+
is_eager=True,
|
|
39
|
+
help="Show version and exit",
|
|
40
|
+
),
|
|
41
|
+
) -> None:
|
|
42
|
+
"""Mindsystem research CLI - Context7 docs and Perplexity deep research."""
|
|
43
|
+
pass
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
@app.command()
|
|
47
|
+
def docs(
|
|
48
|
+
library: str = typer.Argument(..., help="Library name (e.g., 'nextjs', 'react')"),
|
|
49
|
+
query: str = typer.Argument(..., help="Documentation query"),
|
|
50
|
+
max_tokens: int = typer.Option(
|
|
51
|
+
DEFAULT_MAX_TOKENS,
|
|
52
|
+
"--max-tokens",
|
|
53
|
+
"-t",
|
|
54
|
+
help="Maximum tokens in response",
|
|
55
|
+
),
|
|
56
|
+
no_cache: bool = typer.Option(
|
|
57
|
+
False,
|
|
58
|
+
"--no-cache",
|
|
59
|
+
help="Skip cache lookup",
|
|
60
|
+
),
|
|
61
|
+
json_pretty: bool = typer.Option(
|
|
62
|
+
False,
|
|
63
|
+
"--json-pretty",
|
|
64
|
+
"-p",
|
|
65
|
+
help="Pretty-print JSON output",
|
|
66
|
+
),
|
|
67
|
+
) -> None:
|
|
68
|
+
"""Query library documentation via Context7.
|
|
69
|
+
|
|
70
|
+
Use for authoritative, version-aware API documentation.
|
|
71
|
+
|
|
72
|
+
Examples:
|
|
73
|
+
ms-lookup docs nextjs "app router setup"
|
|
74
|
+
ms-lookup docs react "hooks useEffect cleanup"
|
|
75
|
+
ms-lookup docs "react-three-fiber" "physics integration"
|
|
76
|
+
"""
|
|
77
|
+
command = "docs"
|
|
78
|
+
|
|
79
|
+
# Check cache first
|
|
80
|
+
if not no_cache:
|
|
81
|
+
cached = get_cached(command, library, query, max_tokens=max_tokens)
|
|
82
|
+
if cached is not None:
|
|
83
|
+
cached["metadata"]["cache_hit"] = True
|
|
84
|
+
typer.echo(output_json(cached, pretty=json_pretty))
|
|
85
|
+
return
|
|
86
|
+
|
|
87
|
+
try:
|
|
88
|
+
client = Context7Client()
|
|
89
|
+
|
|
90
|
+
# Resolve library name to ID
|
|
91
|
+
library_info = client.resolve_library(library, query)
|
|
92
|
+
library_id = library_info["library_id"]
|
|
93
|
+
|
|
94
|
+
# Query documentation
|
|
95
|
+
raw_response = client.query_docs(library_id, query)
|
|
96
|
+
|
|
97
|
+
# Format results
|
|
98
|
+
results = client.format_results(raw_response)
|
|
99
|
+
total_available = len(results)
|
|
100
|
+
|
|
101
|
+
# Truncate to token budget
|
|
102
|
+
truncated_results, tokens_used = truncate_results(results, max_tokens)
|
|
103
|
+
|
|
104
|
+
# Build response
|
|
105
|
+
metadata = {
|
|
106
|
+
"library_id": library_id,
|
|
107
|
+
"total_available": total_available,
|
|
108
|
+
"returned": len(truncated_results),
|
|
109
|
+
"tokens_used": tokens_used,
|
|
110
|
+
"max_tokens": max_tokens,
|
|
111
|
+
"cache_hit": False,
|
|
112
|
+
"confidence": "HIGH",
|
|
113
|
+
"backend": "context7",
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
response = format_success(
|
|
117
|
+
command=command,
|
|
118
|
+
query=query,
|
|
119
|
+
results=truncated_results,
|
|
120
|
+
metadata=metadata,
|
|
121
|
+
library=library,
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
# Cache result
|
|
125
|
+
if not no_cache:
|
|
126
|
+
set_cached(command, library, query, max_tokens=max_tokens, value=response)
|
|
127
|
+
|
|
128
|
+
typer.echo(output_json(response, pretty=json_pretty))
|
|
129
|
+
|
|
130
|
+
except MsLookupError as e:
|
|
131
|
+
error_response = format_error(command, e)
|
|
132
|
+
typer.echo(output_json(error_response, pretty=json_pretty))
|
|
133
|
+
raise typer.Exit(code=1)
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
@app.command()
|
|
137
|
+
def deep(
|
|
138
|
+
query: str = typer.Argument(..., help="Research query"),
|
|
139
|
+
no_cache: bool = typer.Option(
|
|
140
|
+
False,
|
|
141
|
+
"--no-cache",
|
|
142
|
+
help="Skip cache lookup",
|
|
143
|
+
),
|
|
144
|
+
json_pretty: bool = typer.Option(
|
|
145
|
+
False,
|
|
146
|
+
"--json-pretty",
|
|
147
|
+
"-p",
|
|
148
|
+
help="Pretty-print JSON output",
|
|
149
|
+
),
|
|
150
|
+
) -> None:
|
|
151
|
+
"""Perform research via Perplexity's reasoning model.
|
|
152
|
+
|
|
153
|
+
Uses chain-of-thought reasoning with web search to synthesize
|
|
154
|
+
actionable findings from multiple sources. Faster than exhaustive
|
|
155
|
+
deep research while maintaining quality.
|
|
156
|
+
|
|
157
|
+
Cost: ~$0.005 per query. Use for high-value technical questions.
|
|
158
|
+
|
|
159
|
+
Examples:
|
|
160
|
+
ms-lookup deep "authentication patterns for SaaS applications"
|
|
161
|
+
ms-lookup deep "WebGPU browser support and performance 2026"
|
|
162
|
+
ms-lookup deep "best practices for real-time collaboration"
|
|
163
|
+
"""
|
|
164
|
+
command = "deep"
|
|
165
|
+
|
|
166
|
+
# Check cache first
|
|
167
|
+
if not no_cache:
|
|
168
|
+
cached = get_cached(command, query)
|
|
169
|
+
if cached is not None:
|
|
170
|
+
cached["metadata"]["cache_hit"] = True
|
|
171
|
+
typer.echo(output_json(cached, pretty=json_pretty))
|
|
172
|
+
return
|
|
173
|
+
|
|
174
|
+
try:
|
|
175
|
+
client = PerplexityClient()
|
|
176
|
+
|
|
177
|
+
# Perform research query
|
|
178
|
+
raw_response = client.query(query)
|
|
179
|
+
|
|
180
|
+
# Format results - no truncation for deep, output controlled via prompt
|
|
181
|
+
results, citations = client.format_results(raw_response)
|
|
182
|
+
|
|
183
|
+
# Calculate tokens for metadata (no truncation)
|
|
184
|
+
tokens_used = sum(estimate_tokens(r.get("content", "")) for r in results)
|
|
185
|
+
|
|
186
|
+
# Build response
|
|
187
|
+
metadata = {
|
|
188
|
+
"total_available": len(results),
|
|
189
|
+
"returned": len(results),
|
|
190
|
+
"tokens_used": tokens_used,
|
|
191
|
+
"cache_hit": False,
|
|
192
|
+
"confidence": "MEDIUM-HIGH",
|
|
193
|
+
"backend": "perplexity-reasoning",
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
if citations:
|
|
197
|
+
metadata["citations"] = citations
|
|
198
|
+
|
|
199
|
+
response = format_success(
|
|
200
|
+
command=command,
|
|
201
|
+
query=query,
|
|
202
|
+
results=results,
|
|
203
|
+
metadata=metadata,
|
|
204
|
+
)
|
|
205
|
+
|
|
206
|
+
# Cache result
|
|
207
|
+
if not no_cache:
|
|
208
|
+
set_cached(command, query, value=response)
|
|
209
|
+
|
|
210
|
+
typer.echo(output_json(response, pretty=json_pretty))
|
|
211
|
+
|
|
212
|
+
except MsLookupError as e:
|
|
213
|
+
error_response = format_error(command, e)
|
|
214
|
+
typer.echo(output_json(error_response, pretty=json_pretty))
|
|
215
|
+
raise typer.Exit(code=1)
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
if __name__ == "__main__":
|
|
219
|
+
app()
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
"""Configuration and environment variables."""
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
# API Keys from environment
|
|
7
|
+
CONTEXT7_API_KEY = os.environ.get("CONTEXT7_API_KEY", "")
|
|
8
|
+
PERPLEXITY_API_KEY = os.environ.get("PERPLEXITY_API_KEY", "")
|
|
9
|
+
|
|
10
|
+
# API URLs
|
|
11
|
+
CONTEXT7_BASE_URL = "https://context7.com/api/v2"
|
|
12
|
+
PERPLEXITY_BASE_URL = "https://api.perplexity.ai"
|
|
13
|
+
|
|
14
|
+
# Cache configuration
|
|
15
|
+
CACHE_DIR = Path.home() / ".cache" / "ms-lookup"
|
|
16
|
+
CACHE_TTL_DOCS = 60 * 60 * 24 # 24 hours for docs
|
|
17
|
+
CACHE_TTL_DEEP = 60 * 60 * 6 # 6 hours for deep research
|
|
18
|
+
|
|
19
|
+
# Default token limits
|
|
20
|
+
DEFAULT_MAX_TOKENS = 2000
|
|
21
|
+
|
|
22
|
+
# Perplexity model - sonar-reasoning-pro for faster chain-of-thought research
|
|
23
|
+
PERPLEXITY_DEEP_MODEL = "sonar-reasoning-pro"
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"""Error codes and error handling."""
|
|
2
|
+
|
|
3
|
+
from enum import Enum
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class ErrorCode(str, Enum):
|
|
7
|
+
"""Error codes for CLI responses."""
|
|
8
|
+
|
|
9
|
+
MISSING_API_KEY = "MISSING_API_KEY"
|
|
10
|
+
LIBRARY_NOT_FOUND = "LIBRARY_NOT_FOUND"
|
|
11
|
+
API_ERROR = "API_ERROR"
|
|
12
|
+
RATE_LIMITED = "RATE_LIMITED"
|
|
13
|
+
NETWORK_ERROR = "NETWORK_ERROR"
|
|
14
|
+
INVALID_RESPONSE = "INVALID_RESPONSE"
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class MsLookupError(Exception):
|
|
18
|
+
"""Base exception for ms-lookup errors."""
|
|
19
|
+
|
|
20
|
+
def __init__(self, code: ErrorCode, message: str, suggestions: list[str] | None = None):
|
|
21
|
+
self.code = code
|
|
22
|
+
self.message = message
|
|
23
|
+
self.suggestions = suggestions or []
|
|
24
|
+
super().__init__(message)
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
"""JSON output formatting."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
from typing import Any
|
|
5
|
+
|
|
6
|
+
from ms_lookup.errors import MsLookupError
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def format_success(
|
|
10
|
+
command: str,
|
|
11
|
+
query: str,
|
|
12
|
+
results: list[dict],
|
|
13
|
+
metadata: dict,
|
|
14
|
+
library: str | None = None,
|
|
15
|
+
) -> dict:
|
|
16
|
+
"""Format successful response."""
|
|
17
|
+
response = {
|
|
18
|
+
"success": True,
|
|
19
|
+
"command": command,
|
|
20
|
+
"query": query,
|
|
21
|
+
"results": results,
|
|
22
|
+
"metadata": metadata,
|
|
23
|
+
}
|
|
24
|
+
if library:
|
|
25
|
+
response["library"] = library
|
|
26
|
+
return response
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def format_error(command: str, error: MsLookupError) -> dict:
|
|
30
|
+
"""Format error response."""
|
|
31
|
+
error_dict: dict[str, Any] = {
|
|
32
|
+
"code": error.code.value,
|
|
33
|
+
"message": error.message,
|
|
34
|
+
}
|
|
35
|
+
if error.suggestions:
|
|
36
|
+
error_dict["suggestions"] = error.suggestions
|
|
37
|
+
|
|
38
|
+
return {
|
|
39
|
+
"success": False,
|
|
40
|
+
"command": command,
|
|
41
|
+
"error": error_dict,
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def output_json(data: dict, pretty: bool = False) -> str:
|
|
46
|
+
"""Convert data to JSON string."""
|
|
47
|
+
if pretty:
|
|
48
|
+
return json.dumps(data, indent=2, ensure_ascii=False)
|
|
49
|
+
return json.dumps(data, ensure_ascii=False)
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
"""Token estimation and truncation utilities."""
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def estimate_tokens(text: str) -> int:
|
|
5
|
+
"""Estimate token count using word count * 1.3."""
|
|
6
|
+
if not text:
|
|
7
|
+
return 0
|
|
8
|
+
word_count = len(text.split())
|
|
9
|
+
return int(word_count * 1.3)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def truncate_results(results: list[dict], max_tokens: int) -> tuple[list[dict], int]:
|
|
13
|
+
"""Truncate results to fit within token budget.
|
|
14
|
+
|
|
15
|
+
Args:
|
|
16
|
+
results: List of result dicts with 'content' field
|
|
17
|
+
max_tokens: Maximum token budget
|
|
18
|
+
|
|
19
|
+
Returns:
|
|
20
|
+
Tuple of (truncated_results, total_tokens_used)
|
|
21
|
+
"""
|
|
22
|
+
truncated = []
|
|
23
|
+
tokens_used = 0
|
|
24
|
+
|
|
25
|
+
for result in results:
|
|
26
|
+
content = result.get("content", "")
|
|
27
|
+
result_tokens = estimate_tokens(content)
|
|
28
|
+
|
|
29
|
+
# Add metadata overhead estimate (title, url, etc.)
|
|
30
|
+
overhead = 50
|
|
31
|
+
total_result_tokens = result_tokens + overhead
|
|
32
|
+
|
|
33
|
+
if tokens_used + total_result_tokens <= max_tokens:
|
|
34
|
+
result_with_tokens = {**result, "tokens": result_tokens}
|
|
35
|
+
truncated.append(result_with_tokens)
|
|
36
|
+
tokens_used += total_result_tokens
|
|
37
|
+
else:
|
|
38
|
+
# Try to fit partial content if we have room
|
|
39
|
+
remaining = max_tokens - tokens_used - overhead
|
|
40
|
+
if remaining > 100: # Only include if meaningful content fits
|
|
41
|
+
words = content.split()
|
|
42
|
+
truncated_word_count = int(remaining / 1.3)
|
|
43
|
+
if truncated_word_count > 0:
|
|
44
|
+
truncated_content = " ".join(words[:truncated_word_count]) + "..."
|
|
45
|
+
truncated_tokens = estimate_tokens(truncated_content)
|
|
46
|
+
result_with_tokens = {
|
|
47
|
+
**result,
|
|
48
|
+
"content": truncated_content,
|
|
49
|
+
"tokens": truncated_tokens,
|
|
50
|
+
"truncated": True
|
|
51
|
+
}
|
|
52
|
+
truncated.append(result_with_tokens)
|
|
53
|
+
tokens_used += truncated_tokens + overhead
|
|
54
|
+
break
|
|
55
|
+
|
|
56
|
+
return truncated, tokens_used
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "ms-lookup"
|
|
3
|
+
version = "1.0.0"
|
|
4
|
+
description = "CLI tool for Mindsystem research - Context7 docs and Perplexity deep research"
|
|
5
|
+
requires-python = ">=3.10"
|
|
6
|
+
dependencies = [
|
|
7
|
+
"typer>=0.9.0",
|
|
8
|
+
"httpx>=0.25.0",
|
|
9
|
+
"diskcache>=5.6.0",
|
|
10
|
+
]
|
|
11
|
+
|
|
12
|
+
[project.scripts]
|
|
13
|
+
ms-lookup = "ms_lookup.cli:app"
|
|
14
|
+
|
|
15
|
+
[build-system]
|
|
16
|
+
requires = ["hatchling"]
|
|
17
|
+
build-backend = "hatchling.build"
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
version = 1
|
|
2
|
+
revision = 3
|
|
3
|
+
requires-python = ">=3.10"
|
|
4
|
+
|
|
5
|
+
[[package]]
|
|
6
|
+
name = "anyio"
|
|
7
|
+
version = "4.12.1"
|
|
8
|
+
source = { registry = "https://pypi.org/simple" }
|
|
9
|
+
dependencies = [
|
|
10
|
+
{ name = "exceptiongroup", marker = "python_full_version < '3.11'" },
|
|
11
|
+
{ name = "idna" },
|
|
12
|
+
{ name = "typing-extensions", marker = "python_full_version < '3.13'" },
|
|
13
|
+
]
|
|
14
|
+
sdist = { url = "https://files.pythonhosted.org/packages/96/f0/5eb65b2bb0d09ac6776f2eb54adee6abe8228ea05b20a5ad0e4945de8aac/anyio-4.12.1.tar.gz", hash = "sha256:41cfcc3a4c85d3f05c932da7c26d0201ac36f72abd4435ba90d0464a3ffed703", size = 228685, upload-time = "2026-01-06T11:45:21.246Z" }
|
|
15
|
+
wheels = [
|
|
16
|
+
{ url = "https://files.pythonhosted.org/packages/38/0e/27be9fdef66e72d64c0cdc3cc2823101b80585f8119b5c112c2e8f5f7dab/anyio-4.12.1-py3-none-any.whl", hash = "sha256:d405828884fc140aa80a3c667b8beed277f1dfedec42ba031bd6ac3db606ab6c", size = 113592, upload-time = "2026-01-06T11:45:19.497Z" },
|
|
17
|
+
]
|
|
18
|
+
|
|
19
|
+
[[package]]
|
|
20
|
+
name = "certifi"
|
|
21
|
+
version = "2026.1.4"
|
|
22
|
+
source = { registry = "https://pypi.org/simple" }
|
|
23
|
+
sdist = { url = "https://files.pythonhosted.org/packages/e0/2d/a891ca51311197f6ad14a7ef42e2399f36cf2f9bd44752b3dc4eab60fdc5/certifi-2026.1.4.tar.gz", hash = "sha256:ac726dd470482006e014ad384921ed6438c457018f4b3d204aea4281258b2120", size = 154268, upload-time = "2026-01-04T02:42:41.825Z" }
|
|
24
|
+
wheels = [
|
|
25
|
+
{ url = "https://files.pythonhosted.org/packages/e6/ad/3cc14f097111b4de0040c83a525973216457bbeeb63739ef1ed275c1c021/certifi-2026.1.4-py3-none-any.whl", hash = "sha256:9943707519e4add1115f44c2bc244f782c0249876bf51b6599fee1ffbedd685c", size = 152900, upload-time = "2026-01-04T02:42:40.15Z" },
|
|
26
|
+
]
|
|
27
|
+
|
|
28
|
+
[[package]]
|
|
29
|
+
name = "click"
|
|
30
|
+
version = "8.3.1"
|
|
31
|
+
source = { registry = "https://pypi.org/simple" }
|
|
32
|
+
dependencies = [
|
|
33
|
+
{ name = "colorama", marker = "sys_platform == 'win32'" },
|
|
34
|
+
]
|
|
35
|
+
sdist = { url = "https://files.pythonhosted.org/packages/3d/fa/656b739db8587d7b5dfa22e22ed02566950fbfbcdc20311993483657a5c0/click-8.3.1.tar.gz", hash = "sha256:12ff4785d337a1bb490bb7e9c2b1ee5da3112e94a8622f26a6c77f5d2fc6842a", size = 295065, upload-time = "2025-11-15T20:45:42.706Z" }
|
|
36
|
+
wheels = [
|
|
37
|
+
{ url = "https://files.pythonhosted.org/packages/98/78/01c019cdb5d6498122777c1a43056ebb3ebfeef2076d9d026bfe15583b2b/click-8.3.1-py3-none-any.whl", hash = "sha256:981153a64e25f12d547d3426c367a4857371575ee7ad18df2a6183ab0545b2a6", size = 108274, upload-time = "2025-11-15T20:45:41.139Z" },
|
|
38
|
+
]
|
|
39
|
+
|
|
40
|
+
[[package]]
|
|
41
|
+
name = "colorama"
|
|
42
|
+
version = "0.4.6"
|
|
43
|
+
source = { registry = "https://pypi.org/simple" }
|
|
44
|
+
sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" }
|
|
45
|
+
wheels = [
|
|
46
|
+
{ url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" },
|
|
47
|
+
]
|
|
48
|
+
|
|
49
|
+
[[package]]
|
|
50
|
+
name = "diskcache"
|
|
51
|
+
version = "5.6.3"
|
|
52
|
+
source = { registry = "https://pypi.org/simple" }
|
|
53
|
+
sdist = { url = "https://files.pythonhosted.org/packages/3f/21/1c1ffc1a039ddcc459db43cc108658f32c57d271d7289a2794e401d0fdb6/diskcache-5.6.3.tar.gz", hash = "sha256:2c3a3fa2743d8535d832ec61c2054a1641f41775aa7c556758a109941e33e4fc", size = 67916, upload-time = "2023-08-31T06:12:00.316Z" }
|
|
54
|
+
wheels = [
|
|
55
|
+
{ url = "https://files.pythonhosted.org/packages/3f/27/4570e78fc0bf5ea0ca45eb1de3818a23787af9b390c0b0a0033a1b8236f9/diskcache-5.6.3-py3-none-any.whl", hash = "sha256:5e31b2d5fbad117cc363ebaf6b689474db18a1f6438bc82358b024abd4c2ca19", size = 45550, upload-time = "2023-08-31T06:11:58.822Z" },
|
|
56
|
+
]
|
|
57
|
+
|
|
58
|
+
[[package]]
|
|
59
|
+
name = "exceptiongroup"
|
|
60
|
+
version = "1.3.1"
|
|
61
|
+
source = { registry = "https://pypi.org/simple" }
|
|
62
|
+
dependencies = [
|
|
63
|
+
{ name = "typing-extensions", marker = "python_full_version < '3.13'" },
|
|
64
|
+
]
|
|
65
|
+
sdist = { url = "https://files.pythonhosted.org/packages/50/79/66800aadf48771f6b62f7eb014e352e5d06856655206165d775e675a02c9/exceptiongroup-1.3.1.tar.gz", hash = "sha256:8b412432c6055b0b7d14c310000ae93352ed6754f70fa8f7c34141f91c4e3219", size = 30371, upload-time = "2025-11-21T23:01:54.787Z" }
|
|
66
|
+
wheels = [
|
|
67
|
+
{ url = "https://files.pythonhosted.org/packages/8a/0e/97c33bf5009bdbac74fd2beace167cab3f978feb69cc36f1ef79360d6c4e/exceptiongroup-1.3.1-py3-none-any.whl", hash = "sha256:a7a39a3bd276781e98394987d3a5701d0c4edffb633bb7a5144577f82c773598", size = 16740, upload-time = "2025-11-21T23:01:53.443Z" },
|
|
68
|
+
]
|
|
69
|
+
|
|
70
|
+
[[package]]
|
|
71
|
+
name = "gsd-lookup"
|
|
72
|
+
version = "1.0.0"
|
|
73
|
+
source = { editable = "." }
|
|
74
|
+
dependencies = [
|
|
75
|
+
{ name = "diskcache" },
|
|
76
|
+
{ name = "httpx" },
|
|
77
|
+
{ name = "typer" },
|
|
78
|
+
]
|
|
79
|
+
|
|
80
|
+
[package.metadata]
|
|
81
|
+
requires-dist = [
|
|
82
|
+
{ name = "diskcache", specifier = ">=5.6.0" },
|
|
83
|
+
{ name = "httpx", specifier = ">=0.25.0" },
|
|
84
|
+
{ name = "typer", specifier = ">=0.9.0" },
|
|
85
|
+
]
|
|
86
|
+
|
|
87
|
+
[[package]]
|
|
88
|
+
name = "h11"
|
|
89
|
+
version = "0.16.0"
|
|
90
|
+
source = { registry = "https://pypi.org/simple" }
|
|
91
|
+
sdist = { url = "https://files.pythonhosted.org/packages/01/ee/02a2c011bdab74c6fb3c75474d40b3052059d95df7e73351460c8588d963/h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1", size = 101250, upload-time = "2025-04-24T03:35:25.427Z" }
|
|
92
|
+
wheels = [
|
|
93
|
+
{ url = "https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86", size = 37515, upload-time = "2025-04-24T03:35:24.344Z" },
|
|
94
|
+
]
|
|
95
|
+
|
|
96
|
+
[[package]]
|
|
97
|
+
name = "httpcore"
|
|
98
|
+
version = "1.0.9"
|
|
99
|
+
source = { registry = "https://pypi.org/simple" }
|
|
100
|
+
dependencies = [
|
|
101
|
+
{ name = "certifi" },
|
|
102
|
+
{ name = "h11" },
|
|
103
|
+
]
|
|
104
|
+
sdist = { url = "https://files.pythonhosted.org/packages/06/94/82699a10bca87a5556c9c59b5963f2d039dbd239f25bc2a63907a05a14cb/httpcore-1.0.9.tar.gz", hash = "sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8", size = 85484, upload-time = "2025-04-24T22:06:22.219Z" }
|
|
105
|
+
wheels = [
|
|
106
|
+
{ url = "https://files.pythonhosted.org/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55", size = 78784, upload-time = "2025-04-24T22:06:20.566Z" },
|
|
107
|
+
]
|
|
108
|
+
|
|
109
|
+
[[package]]
|
|
110
|
+
name = "httpx"
|
|
111
|
+
version = "0.28.1"
|
|
112
|
+
source = { registry = "https://pypi.org/simple" }
|
|
113
|
+
dependencies = [
|
|
114
|
+
{ name = "anyio" },
|
|
115
|
+
{ name = "certifi" },
|
|
116
|
+
{ name = "httpcore" },
|
|
117
|
+
{ name = "idna" },
|
|
118
|
+
]
|
|
119
|
+
sdist = { url = "https://files.pythonhosted.org/packages/b1/df/48c586a5fe32a0f01324ee087459e112ebb7224f646c0b5023f5e79e9956/httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc", size = 141406, upload-time = "2024-12-06T15:37:23.222Z" }
|
|
120
|
+
wheels = [
|
|
121
|
+
{ url = "https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad", size = 73517, upload-time = "2024-12-06T15:37:21.509Z" },
|
|
122
|
+
]
|
|
123
|
+
|
|
124
|
+
[[package]]
|
|
125
|
+
name = "idna"
|
|
126
|
+
version = "3.11"
|
|
127
|
+
source = { registry = "https://pypi.org/simple" }
|
|
128
|
+
sdist = { url = "https://files.pythonhosted.org/packages/6f/6d/0703ccc57f3a7233505399edb88de3cbd678da106337b9fcde432b65ed60/idna-3.11.tar.gz", hash = "sha256:795dafcc9c04ed0c1fb032c2aa73654d8e8c5023a7df64a53f39190ada629902", size = 194582, upload-time = "2025-10-12T14:55:20.501Z" }
|
|
129
|
+
wheels = [
|
|
130
|
+
{ url = "https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl", hash = "sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea", size = 71008, upload-time = "2025-10-12T14:55:18.883Z" },
|
|
131
|
+
]
|
|
132
|
+
|
|
133
|
+
[[package]]
|
|
134
|
+
name = "markdown-it-py"
|
|
135
|
+
version = "4.0.0"
|
|
136
|
+
source = { registry = "https://pypi.org/simple" }
|
|
137
|
+
dependencies = [
|
|
138
|
+
{ name = "mdurl" },
|
|
139
|
+
]
|
|
140
|
+
sdist = { url = "https://files.pythonhosted.org/packages/5b/f5/4ec618ed16cc4f8fb3b701563655a69816155e79e24a17b651541804721d/markdown_it_py-4.0.0.tar.gz", hash = "sha256:cb0a2b4aa34f932c007117b194e945bd74e0ec24133ceb5bac59009cda1cb9f3", size = 73070, upload-time = "2025-08-11T12:57:52.854Z" }
|
|
141
|
+
wheels = [
|
|
142
|
+
{ url = "https://files.pythonhosted.org/packages/94/54/e7d793b573f298e1c9013b8c4dade17d481164aa517d1d7148619c2cedbf/markdown_it_py-4.0.0-py3-none-any.whl", hash = "sha256:87327c59b172c5011896038353a81343b6754500a08cd7a4973bb48c6d578147", size = 87321, upload-time = "2025-08-11T12:57:51.923Z" },
|
|
143
|
+
]
|
|
144
|
+
|
|
145
|
+
[[package]]
|
|
146
|
+
name = "mdurl"
|
|
147
|
+
version = "0.1.2"
|
|
148
|
+
source = { registry = "https://pypi.org/simple" }
|
|
149
|
+
sdist = { url = "https://files.pythonhosted.org/packages/d6/54/cfe61301667036ec958cb99bd3efefba235e65cdeb9c84d24a8293ba1d90/mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba", size = 8729, upload-time = "2022-08-14T12:40:10.846Z" }
|
|
150
|
+
wheels = [
|
|
151
|
+
{ url = "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979, upload-time = "2022-08-14T12:40:09.779Z" },
|
|
152
|
+
]
|
|
153
|
+
|
|
154
|
+
[[package]]
|
|
155
|
+
name = "pygments"
|
|
156
|
+
version = "2.19.2"
|
|
157
|
+
source = { registry = "https://pypi.org/simple" }
|
|
158
|
+
sdist = { url = "https://files.pythonhosted.org/packages/b0/77/a5b8c569bf593b0140bde72ea885a803b82086995367bf2037de0159d924/pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887", size = 4968631, upload-time = "2025-06-21T13:39:12.283Z" }
|
|
159
|
+
wheels = [
|
|
160
|
+
{ url = "https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b", size = 1225217, upload-time = "2025-06-21T13:39:07.939Z" },
|
|
161
|
+
]
|
|
162
|
+
|
|
163
|
+
[[package]]
|
|
164
|
+
name = "rich"
|
|
165
|
+
version = "14.2.0"
|
|
166
|
+
source = { registry = "https://pypi.org/simple" }
|
|
167
|
+
dependencies = [
|
|
168
|
+
{ name = "markdown-it-py" },
|
|
169
|
+
{ name = "pygments" },
|
|
170
|
+
]
|
|
171
|
+
sdist = { url = "https://files.pythonhosted.org/packages/fb/d2/8920e102050a0de7bfabeb4c4614a49248cf8d5d7a8d01885fbb24dc767a/rich-14.2.0.tar.gz", hash = "sha256:73ff50c7c0c1c77c8243079283f4edb376f0f6442433aecb8ce7e6d0b92d1fe4", size = 219990, upload-time = "2025-10-09T14:16:53.064Z" }
|
|
172
|
+
wheels = [
|
|
173
|
+
{ url = "https://files.pythonhosted.org/packages/25/7a/b0178788f8dc6cafce37a212c99565fa1fe7872c70c6c9c1e1a372d9d88f/rich-14.2.0-py3-none-any.whl", hash = "sha256:76bc51fe2e57d2b1be1f96c524b890b816e334ab4c1e45888799bfaab0021edd", size = 243393, upload-time = "2025-10-09T14:16:51.245Z" },
|
|
174
|
+
]
|
|
175
|
+
|
|
176
|
+
[[package]]
|
|
177
|
+
name = "shellingham"
|
|
178
|
+
version = "1.5.4"
|
|
179
|
+
source = { registry = "https://pypi.org/simple" }
|
|
180
|
+
sdist = { url = "https://files.pythonhosted.org/packages/58/15/8b3609fd3830ef7b27b655beb4b4e9c62313a4e8da8c676e142cc210d58e/shellingham-1.5.4.tar.gz", hash = "sha256:8dbca0739d487e5bd35ab3ca4b36e11c4078f3a234bfce294b0a0291363404de", size = 10310, upload-time = "2023-10-24T04:13:40.426Z" }
|
|
181
|
+
wheels = [
|
|
182
|
+
{ url = "https://files.pythonhosted.org/packages/e0/f9/0595336914c5619e5f28a1fb793285925a8cd4b432c9da0a987836c7f822/shellingham-1.5.4-py2.py3-none-any.whl", hash = "sha256:7ecfff8f2fd72616f7481040475a65b2bf8af90a56c89140852d1120324e8686", size = 9755, upload-time = "2023-10-24T04:13:38.866Z" },
|
|
183
|
+
]
|
|
184
|
+
|
|
185
|
+
[[package]]
|
|
186
|
+
name = "typer"
|
|
187
|
+
version = "0.21.1"
|
|
188
|
+
source = { registry = "https://pypi.org/simple" }
|
|
189
|
+
dependencies = [
|
|
190
|
+
{ name = "click" },
|
|
191
|
+
{ name = "rich" },
|
|
192
|
+
{ name = "shellingham" },
|
|
193
|
+
{ name = "typing-extensions" },
|
|
194
|
+
]
|
|
195
|
+
sdist = { url = "https://files.pythonhosted.org/packages/36/bf/8825b5929afd84d0dabd606c67cd57b8388cb3ec385f7ef19c5cc2202069/typer-0.21.1.tar.gz", hash = "sha256:ea835607cd752343b6b2b7ce676893e5a0324082268b48f27aa058bdb7d2145d", size = 110371, upload-time = "2026-01-06T11:21:10.989Z" }
|
|
196
|
+
wheels = [
|
|
197
|
+
{ url = "https://files.pythonhosted.org/packages/a0/1d/d9257dd49ff2ca23ea5f132edf1281a0c4f9de8a762b9ae399b670a59235/typer-0.21.1-py3-none-any.whl", hash = "sha256:7985e89081c636b88d172c2ee0cfe33c253160994d47bdfdc302defd7d1f1d01", size = 47381, upload-time = "2026-01-06T11:21:09.824Z" },
|
|
198
|
+
]
|
|
199
|
+
|
|
200
|
+
[[package]]
|
|
201
|
+
name = "typing-extensions"
|
|
202
|
+
version = "4.15.0"
|
|
203
|
+
source = { registry = "https://pypi.org/simple" }
|
|
204
|
+
sdist = { url = "https://files.pythonhosted.org/packages/72/94/1a15dd82efb362ac84269196e94cf00f187f7ed21c242792a923cdb1c61f/typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", size = 109391, upload-time = "2025-08-25T13:49:26.313Z" }
|
|
205
|
+
wheels = [
|
|
206
|
+
{ url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614, upload-time = "2025-08-25T13:49:24.86Z" },
|
|
207
|
+
]
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# Wrapper script for ms-lookup CLI
|
|
3
|
+
# Handles uv/pip installation transparently
|
|
4
|
+
|
|
5
|
+
# Source uv environment if available
|
|
6
|
+
if [ -f "$HOME/.local/bin/env" ]; then
|
|
7
|
+
source "$HOME/.local/bin/env"
|
|
8
|
+
fi
|
|
9
|
+
|
|
10
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
11
|
+
cd "$SCRIPT_DIR/ms-lookup"
|
|
12
|
+
|
|
13
|
+
if command -v uv &> /dev/null; then
|
|
14
|
+
# Prefer uv if available (faster)
|
|
15
|
+
uv sync --quiet 2>/dev/null
|
|
16
|
+
uv run python -m ms_lookup "$@"
|
|
17
|
+
else
|
|
18
|
+
# Fall back to pip
|
|
19
|
+
pip install -q -e . 2>/dev/null
|
|
20
|
+
python3 -m ms_lookup "$@"
|
|
21
|
+
fi
|