exemplar-cli 0.1.1__tar.gz → 0.1.2__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.
@@ -1,7 +1,7 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: exemplar-cli
3
- Version: 0.1.1
4
- Summary: Exemplar terminal CLI for skills and memory
3
+ Version: 0.1.2
4
+ Summary: Exemplar terminal CLI for skills, prompts, and memory
5
5
  License: LicenseRef-Proprietary
6
6
  Author: Exemplar Dev LLC
7
7
  Requires-Python: >=3.10,<4.0
@@ -10,17 +10,55 @@ 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.1,<0.2)
13
+ Requires-Dist: exemplar-core (>=0.1.2,<0.2)
14
14
  Description-Content-Type: text/markdown
15
15
 
16
16
  # exemplar-cli
17
17
 
18
18
  Terminal CLI for Exemplar **skills** and **memory**.
19
19
 
20
+ ## Install
21
+
22
+ **Recommended** — isolated install with automatic PATH setup:
23
+
24
+ ```bash
25
+ pipx install exemplar-cli
26
+ ```
27
+
28
+ Other options:
29
+
20
30
  ```bash
31
+ # System / python.org install (macOS, Linux)
21
32
  pip install exemplar-cli
33
+
34
+ # User install (no sudo)
35
+ pip install --user exemplar-cli
36
+ ```
37
+
38
+ After install, verify everything:
39
+
40
+ ```bash
41
+ exemplar doctor
42
+ ```
43
+
44
+ If you see `command not found: exemplar`, the package is installed but its `bin/`
45
+ directory is not on your `PATH`. `exemplar doctor` prints the exact fix for your
46
+ machine. Common cases:
47
+
48
+ | Install method | Scripts directory |
49
+ |----------------|-------------------|
50
+ | `pipx install` | `~/.local/bin` (pipx adds this automatically) |
51
+ | python.org macOS | `/Library/Frameworks/Python.framework/Versions/3.x/bin` |
52
+ | `pip install --user` | `~/Library/Python/3.x/bin` (macOS) or `~/.local/bin` (Linux) |
53
+
54
+ Open a **new terminal** after changing shell profile files.
55
+
56
+ ## Quick start
57
+
58
+ ```bash
22
59
  export EXEMPLAR_API_KEY=eis_...
23
60
 
61
+ exemplar doctor
24
62
  exemplar skills list
25
63
  exemplar memory add "User prefers bullet points" --user-id u1 --type preference
26
64
  exemplar memory search "formatting" --user-id u1
@@ -36,10 +74,18 @@ exemplar memory search "formatting" --user-id u1
36
74
 
37
75
  ## Commands
38
76
 
77
+ ### Doctor
78
+
79
+ `exemplar doctor` — check package install, `PATH`, and required environment variables.
80
+
39
81
  ### Skills
40
82
 
41
83
  `list`, `get`, `search`, `pull`, `push`, `init`, `export-zip`
42
84
 
85
+ ### Prompts
86
+
87
+ `list`, `get`, `search`, `create`, `run`, `publish`, `delete`
88
+
43
89
  ### Memory
44
90
 
45
91
  `add`, `list`, `get`, `search`, `recall`, `update`, `delete`, `delete-bulk`
@@ -0,0 +1,78 @@
1
+ # exemplar-cli
2
+
3
+ Terminal CLI for Exemplar **skills** and **memory**.
4
+
5
+ ## Install
6
+
7
+ **Recommended** — isolated install with automatic PATH setup:
8
+
9
+ ```bash
10
+ pipx install exemplar-cli
11
+ ```
12
+
13
+ Other options:
14
+
15
+ ```bash
16
+ # System / python.org install (macOS, Linux)
17
+ pip install exemplar-cli
18
+
19
+ # User install (no sudo)
20
+ pip install --user exemplar-cli
21
+ ```
22
+
23
+ After install, verify everything:
24
+
25
+ ```bash
26
+ exemplar doctor
27
+ ```
28
+
29
+ If you see `command not found: exemplar`, the package is installed but its `bin/`
30
+ directory is not on your `PATH`. `exemplar doctor` prints the exact fix for your
31
+ machine. Common cases:
32
+
33
+ | Install method | Scripts directory |
34
+ |----------------|-------------------|
35
+ | `pipx install` | `~/.local/bin` (pipx adds this automatically) |
36
+ | python.org macOS | `/Library/Frameworks/Python.framework/Versions/3.x/bin` |
37
+ | `pip install --user` | `~/Library/Python/3.x/bin` (macOS) or `~/.local/bin` (Linux) |
38
+
39
+ Open a **new terminal** after changing shell profile files.
40
+
41
+ ## Quick start
42
+
43
+ ```bash
44
+ export EXEMPLAR_API_KEY=eis_...
45
+
46
+ exemplar doctor
47
+ exemplar skills list
48
+ exemplar memory add "User prefers bullet points" --user-id u1 --type preference
49
+ exemplar memory search "formatting" --user-id u1
50
+ ```
51
+
52
+ ## Environment
53
+
54
+ | Variable | Purpose |
55
+ |----------|---------|
56
+ | `EXEMPLAR_API_KEY` | Org API key (required) |
57
+ | `EXEMPLAR_BASE_URL` | API host override (optional) |
58
+ | `EXEMPLAR_ORGANIZATION_ID` | Org override for JWT auth (optional) |
59
+
60
+ ## Commands
61
+
62
+ ### Doctor
63
+
64
+ `exemplar doctor` — check package install, `PATH`, and required environment variables.
65
+
66
+ ### Skills
67
+
68
+ `list`, `get`, `search`, `pull`, `push`, `init`, `export-zip`
69
+
70
+ ### Prompts
71
+
72
+ `list`, `get`, `search`, `create`, `run`, `publish`, `delete`
73
+
74
+ ### Memory
75
+
76
+ `add`, `list`, `get`, `search`, `recall`, `update`, `delete`, `delete-bulk`
77
+
78
+ Memory commands accept scope flags: `--user-id`, `--agent-id`, `--session-id`, `--app-id`.
@@ -0,0 +1,166 @@
1
+ """exemplar doctor — diagnose CLI install, PATH, and environment."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import os
6
+ import platform
7
+ import shutil
8
+ import site
9
+ import sys
10
+ from importlib.metadata import PackageNotFoundError, distribution
11
+ from pathlib import Path
12
+
13
+
14
+ def _path_entries() -> list[Path]:
15
+ entries: list[Path] = []
16
+ for entry in os.environ.get("PATH", "").split(os.pathsep):
17
+ if not entry:
18
+ continue
19
+ try:
20
+ entries.append(Path(entry).resolve())
21
+ except OSError:
22
+ continue
23
+ return entries
24
+
25
+
26
+ def _locate_installed_script() -> Path | None:
27
+ """Find the exemplar console script when it is not on PATH."""
28
+ candidates: list[Path] = [
29
+ Path(sys.prefix) / "bin" / "exemplar",
30
+ Path(sys.executable).resolve().parent / "exemplar",
31
+ ]
32
+
33
+ user_base = site.USER_BASE
34
+ if user_base:
35
+ candidates.append(Path(user_base) / "bin" / "exemplar")
36
+
37
+ if sys.platform == "darwin":
38
+ version = f"{sys.version_info.major}.{sys.version_info.minor}"
39
+ candidates.append(
40
+ Path(f"/Library/Frameworks/Python.framework/Versions/{version}/bin/exemplar")
41
+ )
42
+
43
+ try:
44
+ dist = distribution("exemplar-cli")
45
+ for path in dist.files or ():
46
+ normalized = str(path).replace("\\", "/")
47
+ if normalized.endswith("/bin/exemplar") or normalized == "../../../bin/exemplar":
48
+ root = Path(dist.locate_file(""))
49
+ candidates.append((root / path).resolve())
50
+ except PackageNotFoundError:
51
+ pass
52
+
53
+ seen: set[Path] = set()
54
+ for candidate in candidates:
55
+ try:
56
+ resolved = candidate.resolve()
57
+ except OSError:
58
+ continue
59
+ if resolved in seen:
60
+ continue
61
+ seen.add(resolved)
62
+ if resolved.is_file():
63
+ return resolved
64
+ return None
65
+
66
+
67
+ def _script_dir_on_path(script_dir: Path) -> bool:
68
+ try:
69
+ resolved = script_dir.resolve()
70
+ except OSError:
71
+ return False
72
+ return resolved in _path_entries()
73
+
74
+
75
+ def _path_fix_lines(script_dir: Path) -> list[str]:
76
+ export_line = f'export PATH="{script_dir}:$PATH"'
77
+ return [
78
+ "Add one of the following to ~/.zprofile (macOS login shells) or ~/.zshrc:",
79
+ f" {export_line}",
80
+ "",
81
+ "Or symlink into a directory already on PATH:",
82
+ f" ln -sf {script_dir / 'exemplar'} ~/.local/bin/exemplar",
83
+ "",
84
+ "Recommended for CLI tools:",
85
+ " pipx install exemplar-cli",
86
+ ]
87
+
88
+
89
+ def _check_api_key() -> tuple[bool, str]:
90
+ key = os.environ.get("EXEMPLAR_API_KEY", "").strip()
91
+ if key:
92
+ return True, "EXEMPLAR_API_KEY is set"
93
+ return False, "EXEMPLAR_API_KEY is not set (required for skills/memory commands)"
94
+
95
+
96
+ def main(argv: list[str] | None = None) -> int:
97
+ del argv # doctor accepts no subcommands yet
98
+ issues = 0
99
+
100
+ print("Exemplar doctor")
101
+ print("===============")
102
+
103
+ print(
104
+ f"Python: {platform.python_version()} "
105
+ f"({platform.machine()}, {sys.executable})"
106
+ )
107
+
108
+ try:
109
+ version = distribution("exemplar-cli").version
110
+ print(f"exemplar-cli package: installed ({version})")
111
+ except PackageNotFoundError:
112
+ issues += 1
113
+ print("exemplar-cli package: NOT INSTALLED")
114
+ print(" Install with: pipx install exemplar-cli")
115
+ print(" or: pip install exemplar-cli")
116
+ return 1
117
+
118
+ command = shutil.which("exemplar")
119
+ if command:
120
+ print(f"exemplar command: OK ({command})")
121
+ else:
122
+ issues += 1
123
+ script = _locate_installed_script()
124
+ print("exemplar command: NOT ON PATH")
125
+ if script:
126
+ print(f" Script installed at: {script}")
127
+ if not _script_dir_on_path(script.parent):
128
+ print(" Fix:")
129
+ for line in _path_fix_lines(script.parent):
130
+ print(f" {line}" if line else "")
131
+ else:
132
+ print(" Script directory is on PATH; open a new terminal or run:")
133
+ print(" source ~/.zprofile")
134
+ else:
135
+ print(" Could not locate the exemplar script.")
136
+ print(" Reinstall with: pipx install exemplar-cli")
137
+
138
+ api_ok, api_msg = _check_api_key()
139
+ if api_ok:
140
+ print(f"API key: OK — {api_msg}")
141
+ else:
142
+ issues += 1
143
+ print(f"API key: MISSING — {api_msg}")
144
+ print(" Set with: export EXEMPLAR_API_KEY=eis_...")
145
+
146
+ base_url = os.environ.get("EXEMPLAR_BASE_URL", "").strip()
147
+ if base_url:
148
+ print(f"API base URL: {base_url}")
149
+ else:
150
+ print("API base URL: default (https://api.exemplar.dev)")
151
+
152
+ org_id = os.environ.get("EXEMPLAR_ORGANIZATION_ID", "").strip()
153
+ if org_id:
154
+ print(f"Organization override: {org_id}")
155
+
156
+ print()
157
+ if issues:
158
+ print(f"{issues} issue(s) found.")
159
+ return 1
160
+
161
+ print("All checks passed.")
162
+ return 0
163
+
164
+
165
+ if __name__ == "__main__":
166
+ raise SystemExit(main())
@@ -4,7 +4,9 @@ from __future__ import annotations
4
4
 
5
5
  import sys
6
6
 
7
+ from exemplar_cli.doctor import main as doctor_main
7
8
  from exemplar_cli.memory import main as memory_main
9
+ from exemplar_cli.prompts import main as prompts_main
8
10
  from exemplar_cli.skills import main as skills_main
9
11
 
10
12
 
@@ -14,10 +16,16 @@ def main(argv: list[str] | None = None) -> int:
14
16
  return skills_main(args[1:])
15
17
  if args and args[0] == "memory":
16
18
  return memory_main(args[1:])
19
+ if args and args[0] == "prompts":
20
+ return prompts_main(args[1:])
21
+ if args and args[0] == "doctor":
22
+ return doctor_main(args[1:])
17
23
  print(
18
- "Usage: exemplar <skills|memory> <subcommand> ...\n"
24
+ "Usage: exemplar <skills|memory|prompts|doctor> <subcommand> ...\n"
19
25
  " exemplar skills list|get|search|pull|push|init|export-zip\n"
20
- " exemplar memory add|list|get|search|recall|update|delete|delete-bulk",
26
+ " exemplar memory add|list|get|search|recall|update|delete|delete-bulk\n"
27
+ " exemplar prompts list|get|search|create|run|publish|delete\n"
28
+ " exemplar doctor",
21
29
  file=sys.stderr,
22
30
  )
23
31
  return 2
@@ -0,0 +1,171 @@
1
+ """CLI: exemplar prompts — manage org prompt registry from the terminal."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import argparse
6
+ import json
7
+ import sys
8
+ from dataclasses import asdict
9
+
10
+ from exemplar_core import prompts_from_env
11
+
12
+
13
+ def _record_dict(record) -> dict:
14
+ data = asdict(record)
15
+ if hasattr(record, "messages"):
16
+ data["messages"] = [{"role": m.role, "content": m.content} for m in record.messages]
17
+ return data
18
+
19
+
20
+ def _cmd_list(args: argparse.Namespace) -> int:
21
+ items = prompts_from_env().list(limit=args.limit, q=args.q)
22
+ if args.json:
23
+ print(json.dumps([item.__dict__ for item in items], indent=2))
24
+ return 0
25
+ for item in items:
26
+ tags = ",".join(item.tags) if item.tags else ""
27
+ suffix = f" [{tags}]" if tags else ""
28
+ print(f"{item.name}\t{item.prompt_id}\tv{item.current_version}{suffix}")
29
+ return 0
30
+
31
+
32
+ def _cmd_get(args: argparse.Namespace) -> int:
33
+ record = prompts_from_env().get(args.prompt_id)
34
+ if args.json:
35
+ print(json.dumps(_record_dict(record), indent=2))
36
+ return 0
37
+ for message in record.messages:
38
+ print(f"[{message.role}]\n{message.content}\n")
39
+ return 0
40
+
41
+
42
+ def _cmd_search(args: argparse.Namespace) -> int:
43
+ results = prompts_from_env().search(args.query, top_k=args.top_k)
44
+ if args.json:
45
+ print(json.dumps([item.__dict__ for item in results], indent=2))
46
+ return 0
47
+ for item in results:
48
+ print(f"{item.name}\t{item.title}")
49
+ return 0
50
+
51
+
52
+ def _cmd_create(args: argparse.Namespace) -> int:
53
+ messages = [{"role": "system", "content": args.system}]
54
+ if args.user:
55
+ messages.append({"role": "user", "content": args.user})
56
+ variables = [v.strip() for v in args.variables.split(",") if v.strip()] if args.variables else []
57
+ record = prompts_from_env().create(
58
+ name=args.name,
59
+ title=args.title,
60
+ description=args.description,
61
+ messages=messages,
62
+ variables=variables,
63
+ tags=[t.strip() for t in args.tags.split(",") if t.strip()] if args.tags else [],
64
+ )
65
+ if args.json:
66
+ print(json.dumps(_record_dict(record), indent=2))
67
+ else:
68
+ print(f"Created {record.name} ({record.prompt_id})")
69
+ return 0
70
+
71
+
72
+ def _cmd_run(args: argparse.Namespace) -> int:
73
+ variables = {}
74
+ for pair in args.var or []:
75
+ if "=" not in pair:
76
+ print(f"expected KEY=VALUE, got {pair!r}", file=sys.stderr)
77
+ return 1
78
+ key, value = pair.split("=", 1)
79
+ variables[key.strip()] = value
80
+ result = prompts_from_env().run(args.prompt_id, variables=variables, model=args.model)
81
+ if args.json:
82
+ print(json.dumps(result, indent=2))
83
+ else:
84
+ print(result.get("content") or json.dumps(result))
85
+ return 0
86
+
87
+
88
+ def _cmd_publish(args: argparse.Namespace) -> int:
89
+ messages = [{"role": "system", "content": args.system}]
90
+ if args.user:
91
+ messages.append({"role": "user", "content": args.user})
92
+ record = prompts_from_env().publish_version(
93
+ args.prompt_id,
94
+ messages=messages,
95
+ change_notes=args.notes,
96
+ )
97
+ if args.json:
98
+ print(json.dumps(_record_dict(record), indent=2))
99
+ else:
100
+ print(f"Published v{record.current_version} for {record.name}")
101
+ return 0
102
+
103
+
104
+ def _cmd_delete(args: argparse.Namespace) -> int:
105
+ prompts_from_env().delete(args.prompt_id)
106
+ print(f"Deleted {args.prompt_id}")
107
+ return 0
108
+
109
+
110
+ def build_parser() -> argparse.ArgumentParser:
111
+ parser = argparse.ArgumentParser(prog="exemplar prompts")
112
+ sub = parser.add_subparsers(dest="command", required=True)
113
+
114
+ list_p = sub.add_parser("list", help="List org prompts")
115
+ list_p.add_argument("--q")
116
+ list_p.add_argument("--limit", type=int, default=100)
117
+ list_p.add_argument("--json", action="store_true")
118
+ list_p.set_defaults(func=_cmd_list)
119
+
120
+ get_p = sub.add_parser("get", help="Get prompt by ID")
121
+ get_p.add_argument("prompt_id")
122
+ get_p.add_argument("--json", action="store_true")
123
+ get_p.set_defaults(func=_cmd_get)
124
+
125
+ search_p = sub.add_parser("search", help="Search prompts")
126
+ search_p.add_argument("query")
127
+ search_p.add_argument("--top-k", type=int, default=20)
128
+ search_p.add_argument("--json", action="store_true")
129
+ search_p.set_defaults(func=_cmd_search)
130
+
131
+ create_p = sub.add_parser("create", help="Create a prompt")
132
+ create_p.add_argument("name")
133
+ create_p.add_argument("--title", required=True)
134
+ create_p.add_argument("--description", default="")
135
+ create_p.add_argument("--system", required=True, help="System message content")
136
+ create_p.add_argument("--user", help="Optional user message template")
137
+ create_p.add_argument("--variables", help="Comma-separated variable names")
138
+ create_p.add_argument("--tags", help="Comma-separated tags")
139
+ create_p.add_argument("--json", action="store_true")
140
+ create_p.set_defaults(func=_cmd_create)
141
+
142
+ run_p = sub.add_parser("run", help="Run a prompt with variables")
143
+ run_p.add_argument("prompt_id")
144
+ run_p.add_argument("--var", action="append", metavar="KEY=VALUE", help="Template variable")
145
+ run_p.add_argument("--model")
146
+ run_p.add_argument("--json", action="store_true")
147
+ run_p.set_defaults(func=_cmd_run)
148
+
149
+ publish_p = sub.add_parser("publish", help="Publish a new prompt version")
150
+ publish_p.add_argument("prompt_id")
151
+ publish_p.add_argument("--system", required=True)
152
+ publish_p.add_argument("--user")
153
+ publish_p.add_argument("--notes", default="CLI publish")
154
+ publish_p.add_argument("--json", action="store_true")
155
+ publish_p.set_defaults(func=_cmd_publish)
156
+
157
+ delete_p = sub.add_parser("delete", help="Delete a prompt")
158
+ delete_p.add_argument("prompt_id")
159
+ delete_p.set_defaults(func=_cmd_delete)
160
+
161
+ return parser
162
+
163
+
164
+ def main(argv: list[str] | None = None) -> int:
165
+ parser = build_parser()
166
+ args = parser.parse_args(argv)
167
+ return int(args.func(args))
168
+
169
+
170
+ if __name__ == "__main__":
171
+ raise SystemExit(main())
@@ -56,60 +56,59 @@ def _cmd_pull(args: argparse.Namespace) -> int:
56
56
  if args.all:
57
57
  items = client.list(limit=500)
58
58
  for item in items:
59
- _write_skill_md(client, item.name, dest_root / item.name)
60
- print(f"Pulled {len(items)} skill(s) to {dest_root}")
59
+ out = dest_root / item.name
60
+ client.export_folder(item.name, out)
61
+ print(f"Pulled {len(items)} skill folder(s) to {dest_root}")
61
62
  return 0
62
63
 
63
64
  if not args.name:
64
65
  print("skill name required unless --all is set", file=sys.stderr)
65
66
  return 1
66
67
 
67
- _write_skill_md(client, args.name, dest_root / args.name)
68
- print(f"Pulled {args.name} to {dest_root / args.name / 'SKILL.md'}")
68
+ out = dest_root / args.name
69
+ client.export_folder(args.name, out)
70
+ print(f"Pulled {args.name} to {out}")
69
71
  return 0
70
72
 
71
73
 
72
- def _write_skill_md(client, name: str, dest_dir: Path) -> None:
73
- dest_dir.mkdir(parents=True, exist_ok=True)
74
- content = client.export_skill_md(name)
75
- (dest_dir / "SKILL.md").write_text(content, encoding="utf-8")
74
+ def _resolve_skill_dir(path: Path) -> tuple[Path, Path]:
75
+ if path.is_dir():
76
+ return path, path / "SKILL.md"
77
+ return path.parent, path
76
78
 
77
79
 
78
80
  def _cmd_push(args: argparse.Namespace) -> int:
79
81
  path = Path(args.path)
80
- if path.is_dir():
81
- skill_file = path / "SKILL.md"
82
- else:
83
- skill_file = path
82
+ skill_dir, skill_file = _resolve_skill_dir(path)
84
83
  if not skill_file.is_file():
85
84
  print(f"SKILL.md not found: {skill_file}", file=sys.stderr)
86
85
  return 1
87
86
 
88
87
  parsed = parse_skill_md(skill_file.read_text(encoding="utf-8"))
89
- name = (parsed.get("name") or path.parent.name or path.stem).strip()
88
+ name = (parsed.get("name") or skill_dir.name or path.stem).strip()
90
89
  if not name:
91
90
  print("could not determine skill name from frontmatter or path", file=sys.stderr)
92
91
  return 1
93
92
 
94
93
  skills = skills_from_env()
95
- body = {
96
- "instructions": parsed.get("instructions") or "",
97
- "description": parsed.get("description") or "",
98
- }
94
+ instructions = parsed.get("instructions") or ""
95
+ description = parsed.get("description") or ""
99
96
  try:
100
97
  existing = skills.get(name)
101
- record = skills.update(
98
+ skills.update(
102
99
  existing.skill_id,
103
- instructions=body["instructions"],
104
- description=body["description"],
100
+ instructions=instructions,
101
+ description=description or None,
105
102
  )
103
+ record = skills.import_folder(existing.skill_id, skill_dir, prune=args.prune)
106
104
  print(f"Updated {record.name} ({record.skill_id})")
107
105
  except ValueError:
108
106
  record = skills.create(
109
107
  name=name,
110
- instructions=body["instructions"],
111
- description=body.get("description"),
108
+ instructions=instructions,
109
+ description=description or None,
112
110
  )
111
+ record = skills.import_folder(record.skill_id, skill_dir, prune=args.prune)
113
112
  print(f"Created {record.name} ({record.skill_id})")
114
113
  return 0
115
114
 
@@ -163,15 +162,20 @@ def build_parser() -> argparse.ArgumentParser:
163
162
  search_p.add_argument("--json", action="store_true")
164
163
  search_p.set_defaults(func=_cmd_search)
165
164
 
166
- pull_p = sub.add_parser("pull", help="Export skill(s) to local SKILL.md")
165
+ pull_p = sub.add_parser("pull", help="Export skill folder(s) from registry")
167
166
  pull_p.add_argument("name", nargs="?")
168
167
  pull_p.add_argument("--all", action="store_true")
169
168
  pull_p.add_argument("--dest")
170
169
  pull_p.add_argument("--global", dest="global_dest", action="store_true")
171
170
  pull_p.set_defaults(func=_cmd_pull)
172
171
 
173
- push_p = sub.add_parser("push", help="Upload local SKILL.md to registry")
172
+ push_p = sub.add_parser("push", help="Upload local skill folder to registry")
174
173
  push_p.add_argument("path")
174
+ push_p.add_argument(
175
+ "--prune",
176
+ action="store_true",
177
+ help="Remove remote files not present in local folder",
178
+ )
175
179
  push_p.set_defaults(func=_cmd_push)
176
180
 
177
181
  init_p = sub.add_parser("init", help="Scaffold local skill directory")
@@ -1,7 +1,7 @@
1
1
  [tool.poetry]
2
2
  name = "exemplar-cli"
3
- version = "0.1.1"
4
- description = "Exemplar terminal CLI for skills and memory"
3
+ version = "0.1.2"
4
+ description = "Exemplar terminal CLI for skills, prompts, and memory"
5
5
  authors = ["Exemplar Dev LLC"]
6
6
  license = "LicenseRef-Proprietary"
7
7
  readme = "README.md"
@@ -9,7 +9,7 @@ packages = [{ include = "exemplar_cli" }]
9
9
 
10
10
  [tool.poetry.dependencies]
11
11
  python = ">=3.10,<4.0"
12
- exemplar-core = ">=0.1.1,<0.2"
12
+ exemplar-core = ">=0.1.2,<0.2"
13
13
 
14
14
  [tool.poetry.scripts]
15
15
  exemplar = "exemplar_cli.main:main"
@@ -1,32 +0,0 @@
1
- # exemplar-cli
2
-
3
- Terminal CLI for Exemplar **skills** and **memory**.
4
-
5
- ```bash
6
- pip install exemplar-cli
7
- export EXEMPLAR_API_KEY=eis_...
8
-
9
- exemplar skills list
10
- exemplar memory add "User prefers bullet points" --user-id u1 --type preference
11
- exemplar memory search "formatting" --user-id u1
12
- ```
13
-
14
- ## Environment
15
-
16
- | Variable | Purpose |
17
- |----------|---------|
18
- | `EXEMPLAR_API_KEY` | Org API key (required) |
19
- | `EXEMPLAR_BASE_URL` | API host override (optional) |
20
- | `EXEMPLAR_ORGANIZATION_ID` | Org override for JWT auth (optional) |
21
-
22
- ## Commands
23
-
24
- ### Skills
25
-
26
- `list`, `get`, `search`, `pull`, `push`, `init`, `export-zip`
27
-
28
- ### Memory
29
-
30
- `add`, `list`, `get`, `search`, `recall`, `update`, `delete`, `delete-bulk`
31
-
32
- Memory commands accept scope flags: `--user-id`, `--agent-id`, `--session-id`, `--app-id`.