exemplar-cli 0.1.2__tar.gz → 0.1.4__tar.gz
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.
- {exemplar_cli-0.1.2 → exemplar_cli-0.1.4}/PKG-INFO +32 -3
- {exemplar_cli-0.1.2 → exemplar_cli-0.1.4}/README.md +29 -0
- exemplar_cli-0.1.4/exemplar_cli/_scopes.py +49 -0
- exemplar_cli-0.1.4/exemplar_cli/_skill_targets.py +205 -0
- exemplar_cli-0.1.4/exemplar_cli/gateway.py +111 -0
- exemplar_cli-0.1.4/exemplar_cli/main.py +76 -0
- exemplar_cli-0.1.4/exemplar_cli/memory.py +264 -0
- exemplar_cli-0.1.4/exemplar_cli/prompts.py +234 -0
- exemplar_cli-0.1.4/exemplar_cli/skills.py +334 -0
- {exemplar_cli-0.1.2 → exemplar_cli-0.1.4}/pyproject.toml +3 -3
- exemplar_cli-0.1.2/exemplar_cli/_scopes.py +0 -23
- exemplar_cli-0.1.2/exemplar_cli/main.py +0 -35
- exemplar_cli-0.1.2/exemplar_cli/memory.py +0 -196
- exemplar_cli-0.1.2/exemplar_cli/prompts.py +0 -171
- exemplar_cli-0.1.2/exemplar_cli/skills.py +0 -205
- {exemplar_cli-0.1.2 → exemplar_cli-0.1.4}/exemplar_cli/__init__.py +0 -0
- {exemplar_cli-0.1.2 → exemplar_cli-0.1.4}/exemplar_cli/doctor.py +0 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: exemplar-cli
|
|
3
|
-
Version: 0.1.
|
|
4
|
-
Summary: Exemplar terminal CLI for skills, prompts, and memory
|
|
3
|
+
Version: 0.1.4
|
|
4
|
+
Summary: Exemplar terminal CLI for skills, prompts, gateway catalog, and memory
|
|
5
5
|
License: LicenseRef-Proprietary
|
|
6
6
|
Author: Exemplar Dev LLC
|
|
7
7
|
Requires-Python: >=3.10,<4.0
|
|
@@ -10,7 +10,7 @@ Classifier: Programming Language :: Python :: 3
|
|
|
10
10
|
Classifier: Programming Language :: Python :: 3.10
|
|
11
11
|
Classifier: Programming Language :: Python :: 3.11
|
|
12
12
|
Classifier: Programming Language :: Python :: 3.12
|
|
13
|
-
Requires-Dist: exemplar-core (>=0.1.
|
|
13
|
+
Requires-Dist: exemplar-core (>=0.1.3,<0.2)
|
|
14
14
|
Description-Content-Type: text/markdown
|
|
15
15
|
|
|
16
16
|
# exemplar-cli
|
|
@@ -71,6 +71,7 @@ exemplar memory search "formatting" --user-id u1
|
|
|
71
71
|
| `EXEMPLAR_API_KEY` | Org API key (required) |
|
|
72
72
|
| `EXEMPLAR_BASE_URL` | API host override (optional) |
|
|
73
73
|
| `EXEMPLAR_ORGANIZATION_ID` | Org override for JWT auth (optional) |
|
|
74
|
+
| `EXEMPLAR_SKILLS_TARGET` | Default skill install preset: `cursor`, `cursor-global`, `claude`, `claude-global` |
|
|
74
75
|
|
|
75
76
|
## Commands
|
|
76
77
|
|
|
@@ -82,6 +83,34 @@ exemplar memory search "formatting" --user-id u1
|
|
|
82
83
|
|
|
83
84
|
`list`, `get`, `search`, `pull`, `push`, `init`, `export-zip`
|
|
84
85
|
|
|
86
|
+
**Skill install destinations** (`pull`, `init`):
|
|
87
|
+
|
|
88
|
+
| Target | Path | Confirmation |
|
|
89
|
+
|--------|------|----------------|
|
|
90
|
+
| `cursor` (default) | `.cursor/skills` | No |
|
|
91
|
+
| `cursor-global` | `~/.cursor/skills` | No |
|
|
92
|
+
| `claude` | `.claude/skills` | Yes |
|
|
93
|
+
| `claude-global` | `~/.claude/skills` | Yes |
|
|
94
|
+
|
|
95
|
+
```bash
|
|
96
|
+
# Default — Cursor project skills (no prompt)
|
|
97
|
+
exemplar skills pull refund-policy
|
|
98
|
+
|
|
99
|
+
# Global Cursor skills
|
|
100
|
+
exemplar skills pull refund-policy --global
|
|
101
|
+
|
|
102
|
+
# Claude Code — asks for confirmation (or use -y)
|
|
103
|
+
exemplar skills pull refund-policy --target claude-global
|
|
104
|
+
|
|
105
|
+
# Pick interactively when multiple agents are installed
|
|
106
|
+
exemplar skills pull --all -i
|
|
107
|
+
|
|
108
|
+
# Override with a custom directory
|
|
109
|
+
exemplar skills pull refund-policy --dest ./my-skills
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
Set a default via `EXEMPLAR_SKILLS_TARGET=cursor-global`.
|
|
113
|
+
|
|
85
114
|
### Prompts
|
|
86
115
|
|
|
87
116
|
`list`, `get`, `search`, `create`, `run`, `publish`, `delete`
|
|
@@ -56,6 +56,7 @@ exemplar memory search "formatting" --user-id u1
|
|
|
56
56
|
| `EXEMPLAR_API_KEY` | Org API key (required) |
|
|
57
57
|
| `EXEMPLAR_BASE_URL` | API host override (optional) |
|
|
58
58
|
| `EXEMPLAR_ORGANIZATION_ID` | Org override for JWT auth (optional) |
|
|
59
|
+
| `EXEMPLAR_SKILLS_TARGET` | Default skill install preset: `cursor`, `cursor-global`, `claude`, `claude-global` |
|
|
59
60
|
|
|
60
61
|
## Commands
|
|
61
62
|
|
|
@@ -67,6 +68,34 @@ exemplar memory search "formatting" --user-id u1
|
|
|
67
68
|
|
|
68
69
|
`list`, `get`, `search`, `pull`, `push`, `init`, `export-zip`
|
|
69
70
|
|
|
71
|
+
**Skill install destinations** (`pull`, `init`):
|
|
72
|
+
|
|
73
|
+
| Target | Path | Confirmation |
|
|
74
|
+
|--------|------|----------------|
|
|
75
|
+
| `cursor` (default) | `.cursor/skills` | No |
|
|
76
|
+
| `cursor-global` | `~/.cursor/skills` | No |
|
|
77
|
+
| `claude` | `.claude/skills` | Yes |
|
|
78
|
+
| `claude-global` | `~/.claude/skills` | Yes |
|
|
79
|
+
|
|
80
|
+
```bash
|
|
81
|
+
# Default — Cursor project skills (no prompt)
|
|
82
|
+
exemplar skills pull refund-policy
|
|
83
|
+
|
|
84
|
+
# Global Cursor skills
|
|
85
|
+
exemplar skills pull refund-policy --global
|
|
86
|
+
|
|
87
|
+
# Claude Code — asks for confirmation (or use -y)
|
|
88
|
+
exemplar skills pull refund-policy --target claude-global
|
|
89
|
+
|
|
90
|
+
# Pick interactively when multiple agents are installed
|
|
91
|
+
exemplar skills pull --all -i
|
|
92
|
+
|
|
93
|
+
# Override with a custom directory
|
|
94
|
+
exemplar skills pull refund-policy --dest ./my-skills
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
Set a default via `EXEMPLAR_SKILLS_TARGET=cursor-global`.
|
|
98
|
+
|
|
70
99
|
### Prompts
|
|
71
100
|
|
|
72
101
|
`list`, `get`, `search`, `create`, `run`, `publish`, `delete`
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
"""Shared CLI helpers."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import argparse
|
|
6
|
+
|
|
7
|
+
from exemplar_core.memory.models import MemoryScopes
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def add_scope_arguments(parser: argparse.ArgumentParser) -> None:
|
|
11
|
+
"""Add memory scope flags (at least one required for most memory commands)."""
|
|
12
|
+
scope = parser.add_argument_group(
|
|
13
|
+
"memory scope",
|
|
14
|
+
"Scope memories to a user, agent, session, or app. At least one scope flag is "
|
|
15
|
+
"required for add, list, search, recall, update, delete, and delete-bulk.",
|
|
16
|
+
)
|
|
17
|
+
scope.add_argument(
|
|
18
|
+
"--user-id",
|
|
19
|
+
dest="user_id",
|
|
20
|
+
metavar="ID",
|
|
21
|
+
help="End-user identifier (e.g. u123)",
|
|
22
|
+
)
|
|
23
|
+
scope.add_argument(
|
|
24
|
+
"--agent-id",
|
|
25
|
+
dest="agent_id",
|
|
26
|
+
metavar="ID",
|
|
27
|
+
help="Agent identifier within your org",
|
|
28
|
+
)
|
|
29
|
+
scope.add_argument(
|
|
30
|
+
"--session-id",
|
|
31
|
+
dest="session_id",
|
|
32
|
+
metavar="ID",
|
|
33
|
+
help="Conversation or eval session identifier",
|
|
34
|
+
)
|
|
35
|
+
scope.add_argument(
|
|
36
|
+
"--app-id",
|
|
37
|
+
dest="app_id",
|
|
38
|
+
metavar="ID",
|
|
39
|
+
help="Source application identifier",
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def scopes_from_args(args: argparse.Namespace) -> MemoryScopes:
|
|
44
|
+
return MemoryScopes(
|
|
45
|
+
user_id=args.user_id,
|
|
46
|
+
agent_id=args.agent_id,
|
|
47
|
+
session_id=args.session_id,
|
|
48
|
+
app_id=args.app_id,
|
|
49
|
+
)
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
"""Resolve local skill install destinations for Cursor, Claude, and custom paths."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import os
|
|
6
|
+
import sys
|
|
7
|
+
from dataclasses import dataclass
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
from typing import Literal
|
|
10
|
+
|
|
11
|
+
SkillTargetId = Literal["cursor", "cursor-global", "claude", "claude-global"]
|
|
12
|
+
|
|
13
|
+
DEFAULT_TARGET_ID: SkillTargetId = "cursor"
|
|
14
|
+
CONFIRM_TARGETS = frozenset({"claude", "claude-global"})
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@dataclass(frozen=True)
|
|
18
|
+
class SkillTarget:
|
|
19
|
+
target_id: SkillTargetId
|
|
20
|
+
agent: str
|
|
21
|
+
scope: str
|
|
22
|
+
relative: bool
|
|
23
|
+
|
|
24
|
+
@property
|
|
25
|
+
def label(self) -> str:
|
|
26
|
+
return f"{self.agent} ({self.scope})"
|
|
27
|
+
|
|
28
|
+
def resolve_path(self) -> Path:
|
|
29
|
+
if self.target_id == "cursor":
|
|
30
|
+
return Path(".cursor") / "skills"
|
|
31
|
+
if self.target_id == "cursor-global":
|
|
32
|
+
return Path.home() / ".cursor" / "skills"
|
|
33
|
+
if self.target_id == "claude":
|
|
34
|
+
return Path(".claude") / "skills"
|
|
35
|
+
if self.target_id == "claude-global":
|
|
36
|
+
return Path.home() / ".claude" / "skills"
|
|
37
|
+
raise ValueError(f"unknown target: {self.target_id}")
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
TARGETS: dict[SkillTargetId, SkillTarget] = {
|
|
41
|
+
"cursor": SkillTarget("cursor", "Cursor", "project", relative=True),
|
|
42
|
+
"cursor-global": SkillTarget("cursor-global", "Cursor", "global", relative=False),
|
|
43
|
+
"claude": SkillTarget("claude", "Claude Code", "project", relative=True),
|
|
44
|
+
"claude-global": SkillTarget("claude-global", "Claude Code", "global", relative=False),
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def _env_target() -> SkillTargetId | None:
|
|
49
|
+
raw = (os.environ.get("EXEMPLAR_SKILLS_TARGET") or "").strip().lower()
|
|
50
|
+
if not raw:
|
|
51
|
+
return None
|
|
52
|
+
if raw in ("cursor", "cursor-global", "claude", "claude-global"):
|
|
53
|
+
return raw # type: ignore[return-value]
|
|
54
|
+
if raw == "global":
|
|
55
|
+
return "cursor-global"
|
|
56
|
+
raise ValueError(
|
|
57
|
+
f"invalid EXEMPLAR_SKILLS_TARGET={raw!r}; "
|
|
58
|
+
"use cursor, cursor-global, claude, or claude-global"
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def _agent_hint_present(target_id: SkillTargetId) -> bool:
|
|
63
|
+
if target_id.startswith("cursor"):
|
|
64
|
+
home = Path.home() / ".cursor"
|
|
65
|
+
return home.is_dir() or (Path.cwd() / ".cursor").is_dir()
|
|
66
|
+
if target_id.startswith("claude"):
|
|
67
|
+
home = Path.home() / ".claude"
|
|
68
|
+
return home.is_dir() or (Path.cwd() / ".claude").is_dir()
|
|
69
|
+
return False
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def detect_available_targets() -> list[SkillTargetId]:
|
|
73
|
+
"""Targets whose agent directories appear to exist on this machine."""
|
|
74
|
+
available: list[SkillTargetId] = []
|
|
75
|
+
for target_id in TARGETS:
|
|
76
|
+
if _agent_hint_present(target_id):
|
|
77
|
+
available.append(target_id)
|
|
78
|
+
return available or [DEFAULT_TARGET_ID]
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def _is_interactive() -> bool:
|
|
82
|
+
if os.environ.get("EXEMPLAR_SKILLS_INTERACTIVE", "").lower() in ("1", "true", "yes"):
|
|
83
|
+
return True
|
|
84
|
+
return sys.stdin.isatty() and sys.stdout.isatty()
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def _confirm(message: str) -> bool:
|
|
88
|
+
try:
|
|
89
|
+
answer = input(f"{message} [y/N]: ").strip().lower()
|
|
90
|
+
except EOFError:
|
|
91
|
+
return False
|
|
92
|
+
return answer in ("y", "yes")
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def _pick_target_interactively() -> SkillTargetId:
|
|
96
|
+
options = detect_available_targets()
|
|
97
|
+
if len(options) == 1:
|
|
98
|
+
return options[0]
|
|
99
|
+
|
|
100
|
+
print("Where should skills be installed?\n")
|
|
101
|
+
for index, target_id in enumerate(options, start=1):
|
|
102
|
+
target = TARGETS[target_id]
|
|
103
|
+
path = target.resolve_path()
|
|
104
|
+
print(f" [{index}] {target.label:<22} {path}")
|
|
105
|
+
print(f" [0] {TARGETS[DEFAULT_TARGET_ID].label:<22} {TARGETS[DEFAULT_TARGET_ID].resolve_path()} (default)")
|
|
106
|
+
|
|
107
|
+
default_choice = "0"
|
|
108
|
+
if DEFAULT_TARGET_ID in options:
|
|
109
|
+
default_choice = str(options.index(DEFAULT_TARGET_ID) + 1)
|
|
110
|
+
|
|
111
|
+
try:
|
|
112
|
+
raw = input(f"\nChoose destination [{default_choice}]: ").strip()
|
|
113
|
+
except EOFError:
|
|
114
|
+
return DEFAULT_TARGET_ID
|
|
115
|
+
|
|
116
|
+
if not raw:
|
|
117
|
+
if default_choice == "0":
|
|
118
|
+
return DEFAULT_TARGET_ID
|
|
119
|
+
return options[int(default_choice) - 1]
|
|
120
|
+
|
|
121
|
+
if raw == "0":
|
|
122
|
+
return DEFAULT_TARGET_ID
|
|
123
|
+
if raw.isdigit() and 1 <= int(raw) <= len(options):
|
|
124
|
+
return options[int(raw) - 1]
|
|
125
|
+
|
|
126
|
+
lowered = raw.lower()
|
|
127
|
+
if lowered in TARGETS:
|
|
128
|
+
return lowered # type: ignore[return-value]
|
|
129
|
+
return DEFAULT_TARGET_ID
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def _normalize_legacy_global(
|
|
133
|
+
*,
|
|
134
|
+
target: SkillTargetId | None,
|
|
135
|
+
global_flag: bool,
|
|
136
|
+
) -> SkillTargetId | None:
|
|
137
|
+
if target is not None:
|
|
138
|
+
return target
|
|
139
|
+
if global_flag:
|
|
140
|
+
return "cursor-global"
|
|
141
|
+
return None
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
def resolve_skill_target(
|
|
145
|
+
*,
|
|
146
|
+
dest: str | None = None,
|
|
147
|
+
target: str | None = None,
|
|
148
|
+
global_flag: bool = False,
|
|
149
|
+
yes: bool = False,
|
|
150
|
+
no_input: bool = False,
|
|
151
|
+
interactive: bool = False,
|
|
152
|
+
) -> tuple[Path, SkillTarget | None]:
|
|
153
|
+
"""
|
|
154
|
+
Resolve pull/init destination.
|
|
155
|
+
|
|
156
|
+
Returns (path, target_meta). target_meta is None when `dest` is explicit.
|
|
157
|
+
"""
|
|
158
|
+
if dest:
|
|
159
|
+
return Path(dest), None
|
|
160
|
+
|
|
161
|
+
target_id: SkillTargetId | None
|
|
162
|
+
try:
|
|
163
|
+
target_id = _env_target()
|
|
164
|
+
except ValueError as exc:
|
|
165
|
+
raise SystemExit(str(exc)) from exc
|
|
166
|
+
|
|
167
|
+
if target:
|
|
168
|
+
lowered = target.strip().lower()
|
|
169
|
+
if lowered == "global":
|
|
170
|
+
lowered = "cursor-global"
|
|
171
|
+
if lowered not in TARGETS:
|
|
172
|
+
raise SystemExit(
|
|
173
|
+
f"unknown --target {target!r}; choose from: cursor, cursor-global, claude, claude-global"
|
|
174
|
+
)
|
|
175
|
+
target_id = lowered # type: ignore[assignment]
|
|
176
|
+
|
|
177
|
+
target_id = _normalize_legacy_global(target=target_id, global_flag=global_flag)
|
|
178
|
+
|
|
179
|
+
if target_id is None and interactive and _is_interactive() and not no_input:
|
|
180
|
+
target_id = _pick_target_interactively()
|
|
181
|
+
elif target_id is None:
|
|
182
|
+
target_id = DEFAULT_TARGET_ID
|
|
183
|
+
|
|
184
|
+
meta = TARGETS[target_id]
|
|
185
|
+
path = meta.resolve_path()
|
|
186
|
+
|
|
187
|
+
if target_id in CONFIRM_TARGETS and not yes:
|
|
188
|
+
if no_input:
|
|
189
|
+
raise SystemExit(
|
|
190
|
+
f"{meta.label} requires confirmation; re-run with --yes or use --target cursor"
|
|
191
|
+
)
|
|
192
|
+
if not _is_interactive():
|
|
193
|
+
raise SystemExit(
|
|
194
|
+
f"{meta.label} ({path}) requires confirmation; re-run with --yes in non-interactive mode"
|
|
195
|
+
)
|
|
196
|
+
if not _confirm(f"Install skills to {path} for {meta.agent}?"):
|
|
197
|
+
raise SystemExit("cancelled")
|
|
198
|
+
|
|
199
|
+
return path, meta
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
def format_destination_banner(path: Path, meta: SkillTarget | None) -> str:
|
|
203
|
+
if meta is None:
|
|
204
|
+
return f"Destination: {path}"
|
|
205
|
+
return f"Destination: {meta.label} → {path}"
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
"""CLI: exemplar gateway — list AI gateway providers and live models."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import argparse
|
|
6
|
+
import json
|
|
7
|
+
import sys
|
|
8
|
+
|
|
9
|
+
from exemplar_core import gateway_from_env
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def _cmd_providers(args: argparse.Namespace) -> int:
|
|
13
|
+
providers = gateway_from_env().list_providers(llm_only=not args.all_providers)
|
|
14
|
+
if args.json:
|
|
15
|
+
print(
|
|
16
|
+
json.dumps(
|
|
17
|
+
[
|
|
18
|
+
{
|
|
19
|
+
"provider_key": p.provider_key,
|
|
20
|
+
"display_name": p.display_name,
|
|
21
|
+
"categories": p.categories,
|
|
22
|
+
}
|
|
23
|
+
for p in providers
|
|
24
|
+
],
|
|
25
|
+
indent=2,
|
|
26
|
+
)
|
|
27
|
+
)
|
|
28
|
+
return 0
|
|
29
|
+
for item in providers:
|
|
30
|
+
cats = ",".join(item.categories) or "-"
|
|
31
|
+
print(f"{item.provider_key}\t{item.display_name}\t{cats}")
|
|
32
|
+
return 0
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def _cmd_models(args: argparse.Namespace) -> int:
|
|
36
|
+
result = gateway_from_env().list_models(args.provider)
|
|
37
|
+
if args.json:
|
|
38
|
+
print(
|
|
39
|
+
json.dumps(
|
|
40
|
+
{
|
|
41
|
+
"provider_key": result.provider_key,
|
|
42
|
+
"source": result.source,
|
|
43
|
+
"error": result.error,
|
|
44
|
+
"models": [
|
|
45
|
+
{
|
|
46
|
+
"gateway_model_id": m.gateway_model_id,
|
|
47
|
+
"name": m.name,
|
|
48
|
+
}
|
|
49
|
+
for m in result.models
|
|
50
|
+
],
|
|
51
|
+
},
|
|
52
|
+
indent=2,
|
|
53
|
+
)
|
|
54
|
+
)
|
|
55
|
+
return 0
|
|
56
|
+
if result.error:
|
|
57
|
+
print(f"unavailable: {result.error}", file=sys.stderr)
|
|
58
|
+
return 1
|
|
59
|
+
for model in result.models:
|
|
60
|
+
print(f"{model.gateway_model_id}\t{model.name}")
|
|
61
|
+
return 0
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def main(argv: list[str] | None = None) -> int:
|
|
65
|
+
parser = argparse.ArgumentParser(
|
|
66
|
+
prog="exemplar gateway",
|
|
67
|
+
description=(
|
|
68
|
+
"List AI gateway providers and live upstream models from the Exemplar "
|
|
69
|
+
"integration-service catalog (/api/gateway)."
|
|
70
|
+
),
|
|
71
|
+
epilog=(
|
|
72
|
+
"Examples:\n"
|
|
73
|
+
" exemplar gateway providers\n"
|
|
74
|
+
" exemplar gateway models --provider gemini\n"
|
|
75
|
+
),
|
|
76
|
+
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
77
|
+
)
|
|
78
|
+
sub = parser.add_subparsers(dest="command", required=True)
|
|
79
|
+
|
|
80
|
+
providers_p = sub.add_parser(
|
|
81
|
+
"providers",
|
|
82
|
+
help="List gateway LLM providers",
|
|
83
|
+
description="List providers from integrations/gateway_providers.yaml.",
|
|
84
|
+
)
|
|
85
|
+
providers_p.add_argument(
|
|
86
|
+
"--all-providers",
|
|
87
|
+
action="store_true",
|
|
88
|
+
help="Include non-LLM providers (default: LLM only)",
|
|
89
|
+
)
|
|
90
|
+
providers_p.add_argument("--json", action="store_true", help="Output JSON")
|
|
91
|
+
providers_p.set_defaults(func=_cmd_providers)
|
|
92
|
+
|
|
93
|
+
models_p = sub.add_parser(
|
|
94
|
+
"models",
|
|
95
|
+
help="List live models for a provider",
|
|
96
|
+
description="Proxy live model list from exemplar-ai-gateway for a connected provider.",
|
|
97
|
+
)
|
|
98
|
+
models_p.add_argument(
|
|
99
|
+
"provider",
|
|
100
|
+
metavar="PROVIDER",
|
|
101
|
+
help="Provider key, e.g. openai or gemini",
|
|
102
|
+
)
|
|
103
|
+
models_p.add_argument("--json", action="store_true", help="Output JSON")
|
|
104
|
+
models_p.set_defaults(func=_cmd_models)
|
|
105
|
+
|
|
106
|
+
args = parser.parse_args(argv)
|
|
107
|
+
return int(args.func(args))
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
if __name__ == "__main__":
|
|
111
|
+
raise SystemExit(main())
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
"""Exemplar CLI entrypoint."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import argparse
|
|
6
|
+
import sys
|
|
7
|
+
|
|
8
|
+
from exemplar_cli.doctor import main as doctor_main
|
|
9
|
+
from exemplar_cli.gateway import main as gateway_main
|
|
10
|
+
from exemplar_cli.memory import main as memory_main
|
|
11
|
+
from exemplar_cli.prompts import main as prompts_main
|
|
12
|
+
from exemplar_cli.skills import main as skills_main
|
|
13
|
+
|
|
14
|
+
_EPILOG = """
|
|
15
|
+
Commands:
|
|
16
|
+
skills Manage org agent skills (pull to Cursor/Claude, push from disk)
|
|
17
|
+
prompts Manage versioned prompt templates (create, run, publish)
|
|
18
|
+
gateway List AI gateway providers and live models
|
|
19
|
+
memory Manage long-term agent memory (add, search, recall)
|
|
20
|
+
doctor Verify install, PATH, and EXEMPLAR_API_KEY
|
|
21
|
+
|
|
22
|
+
Examples:
|
|
23
|
+
exemplar doctor
|
|
24
|
+
exemplar skills pull refund-policy
|
|
25
|
+
exemplar prompts list
|
|
26
|
+
exemplar gateway providers
|
|
27
|
+
exemplar memory add "User prefers bullets" --user-id u1
|
|
28
|
+
|
|
29
|
+
Run `exemplar <command> --help` for full subcommand reference.
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def _build_root_parser() -> argparse.ArgumentParser:
|
|
34
|
+
parser = argparse.ArgumentParser(
|
|
35
|
+
prog="exemplar",
|
|
36
|
+
description=(
|
|
37
|
+
"Exemplar terminal CLI for skills, prompts, and memory. "
|
|
38
|
+
"Requires EXEMPLAR_API_KEY (run `exemplar doctor` to verify setup)."
|
|
39
|
+
),
|
|
40
|
+
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
41
|
+
epilog=_EPILOG,
|
|
42
|
+
)
|
|
43
|
+
parser.add_argument(
|
|
44
|
+
"command",
|
|
45
|
+
nargs="?",
|
|
46
|
+
choices=["skills", "memory", "prompts", "gateway", "doctor"],
|
|
47
|
+
help="Command group to run",
|
|
48
|
+
)
|
|
49
|
+
parser.add_argument("args", nargs=argparse.REMAINDER, help=argparse.SUPPRESS)
|
|
50
|
+
return parser
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def main(argv: list[str] | None = None) -> int:
|
|
54
|
+
raw = list(argv if argv is not None else sys.argv[1:])
|
|
55
|
+
if raw and raw[0] in ("skills", "memory", "prompts", "gateway", "doctor"):
|
|
56
|
+
if raw[0] == "skills":
|
|
57
|
+
return skills_main(raw[1:])
|
|
58
|
+
if raw[0] == "memory":
|
|
59
|
+
return memory_main(raw[1:])
|
|
60
|
+
if raw[0] == "prompts":
|
|
61
|
+
return prompts_main(raw[1:])
|
|
62
|
+
if raw[0] == "gateway":
|
|
63
|
+
return gateway_main(raw[1:])
|
|
64
|
+
if raw[0] == "doctor":
|
|
65
|
+
return doctor_main(raw[1:])
|
|
66
|
+
|
|
67
|
+
parser = _build_root_parser()
|
|
68
|
+
args = parser.parse_args(raw)
|
|
69
|
+
if args.command is None:
|
|
70
|
+
parser.print_help()
|
|
71
|
+
return 2
|
|
72
|
+
return main([args.command, *args.args])
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
if __name__ == "__main__":
|
|
76
|
+
raise SystemExit(main())
|