instructvault 0.2.2__tar.gz → 0.2.3__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.
Files changed (61) hide show
  1. instructvault-0.2.3/.github/workflows/ci.yml +32 -0
  2. instructvault-0.2.3/.github/workflows/prompt-checks.yml +30 -0
  3. {instructvault-0.2.2 → instructvault-0.2.3}/PKG-INFO +20 -1
  4. {instructvault-0.2.2 → instructvault-0.2.3}/README.md +18 -0
  5. instructvault-0.2.3/docs/assets/playground.png +0 -0
  6. instructvault-0.2.3/docs/vision.md +76 -0
  7. instructvault-0.2.3/examples/prompts/hello_world.prompt.yml +14 -0
  8. {instructvault-0.2.2 → instructvault-0.2.3}/playground/README.md +15 -0
  9. instructvault-0.2.3/playground/ivault_playground/app.py +29 -0
  10. instructvault-0.2.3/playground/ivault_playground/routes/api.py +103 -0
  11. instructvault-0.2.3/playground/ivault_playground/routes/ui.py +12 -0
  12. instructvault-0.2.3/playground/ivault_playground/static/app.css +145 -0
  13. instructvault-0.2.3/playground/ivault_playground/static/app.js +116 -0
  14. instructvault-0.2.3/playground/ivault_playground/templates/index.html +70 -0
  15. {instructvault-0.2.2 → instructvault-0.2.3}/pyproject.toml +2 -1
  16. {instructvault-0.2.2 → instructvault-0.2.3}/src/instructvault/bundle.py +13 -4
  17. {instructvault-0.2.2 → instructvault-0.2.3}/src/instructvault/cli.py +9 -5
  18. {instructvault-0.2.2 → instructvault-0.2.3}/src/instructvault/io.py +6 -3
  19. {instructvault-0.2.2 → instructvault-0.2.3}/src/instructvault/sdk.py +3 -1
  20. {instructvault-0.2.2 → instructvault-0.2.3}/src/instructvault/spec.py +17 -1
  21. {instructvault-0.2.2 → instructvault-0.2.3}/tests/test_cli_basic.py +79 -1
  22. instructvault-0.2.3/tests/test_playground_api.py +80 -0
  23. instructvault-0.2.2/VISION_DOC.md +0 -426
  24. instructvault-0.2.2/playground/ivault_playground/app.py +0 -187
  25. {instructvault-0.2.2 → instructvault-0.2.3}/.github/CODEOWNERS +0 -0
  26. {instructvault-0.2.2 → instructvault-0.2.3}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
  27. {instructvault-0.2.2 → instructvault-0.2.3}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
  28. {instructvault-0.2.2 → instructvault-0.2.3}/.github/pull_request_template.md +0 -0
  29. {instructvault-0.2.2 → instructvault-0.2.3}/.github/workflows/release.yml +0 -0
  30. {instructvault-0.2.2 → instructvault-0.2.3}/CHANGELOG.md +0 -0
  31. {instructvault-0.2.2 → instructvault-0.2.3}/CODE_OF_CONDUCT.md +0 -0
  32. {instructvault-0.2.2 → instructvault-0.2.3}/CONTRIBUTING.md +0 -0
  33. {instructvault-0.2.2 → instructvault-0.2.3}/LICENSE +0 -0
  34. {instructvault-0.2.2 → instructvault-0.2.3}/SECURITY.md +0 -0
  35. {instructvault-0.2.2 → instructvault-0.2.3}/docs/ci.md +0 -0
  36. {instructvault-0.2.2 → instructvault-0.2.3}/docs/ci_templates/Jenkinsfile +0 -0
  37. {instructvault-0.2.2 → instructvault-0.2.3}/docs/ci_templates/gitlab-ci.yml +0 -0
  38. {instructvault-0.2.2 → instructvault-0.2.3}/docs/cookbooks.md +0 -0
  39. {instructvault-0.2.2 → instructvault-0.2.3}/docs/dropin_guide.md +0 -0
  40. {instructvault-0.2.2 → instructvault-0.2.3}/docs/governance.md +0 -0
  41. {instructvault-0.2.2 → instructvault-0.2.3}/docs/playground.md +0 -0
  42. {instructvault-0.2.2 → instructvault-0.2.3}/docs/release_checklist.md +0 -0
  43. {instructvault-0.2.2 → instructvault-0.2.3}/docs/templates/CODEOWNERS +0 -0
  44. {instructvault-0.2.2 → instructvault-0.2.3}/examples/datasets/classifier_cases.jsonl +0 -0
  45. {instructvault-0.2.2 → instructvault-0.2.3}/examples/datasets/rag_agent_cases.jsonl +0 -0
  46. {instructvault-0.2.2 → instructvault-0.2.3}/examples/datasets/rag_answer_cases.jsonl +0 -0
  47. {instructvault-0.2.2 → instructvault-0.2.3}/examples/datasets/support_cases.jsonl +0 -0
  48. {instructvault-0.2.2 → instructvault-0.2.3}/examples/prompts/classifier.prompt.yml +0 -0
  49. {instructvault-0.2.2 → instructvault-0.2.3}/examples/prompts/guardrail.prompt.json +0 -0
  50. {instructvault-0.2.2 → instructvault-0.2.3}/examples/prompts/rag_agent.prompt.yml +0 -0
  51. {instructvault-0.2.2 → instructvault-0.2.3}/examples/prompts/rag_answer.prompt.yml +0 -0
  52. {instructvault-0.2.2 → instructvault-0.2.3}/examples/prompts/support_reply.prompt.yml +0 -0
  53. {instructvault-0.2.2 → instructvault-0.2.3}/playground/ivault_playground/__init__.py +0 -0
  54. {instructvault-0.2.2 → instructvault-0.2.3}/playground/pyproject.toml +0 -0
  55. {instructvault-0.2.2 → instructvault-0.2.3}/src/instructvault/__init__.py +0 -0
  56. {instructvault-0.2.2 → instructvault-0.2.3}/src/instructvault/diff.py +0 -0
  57. {instructvault-0.2.2 → instructvault-0.2.3}/src/instructvault/eval.py +0 -0
  58. {instructvault-0.2.2 → instructvault-0.2.3}/src/instructvault/junit.py +0 -0
  59. {instructvault-0.2.2 → instructvault-0.2.3}/src/instructvault/render.py +0 -0
  60. {instructvault-0.2.2 → instructvault-0.2.3}/src/instructvault/scaffold.py +0 -0
  61. {instructvault-0.2.2 → instructvault-0.2.3}/src/instructvault/store.py +0 -0
@@ -0,0 +1,32 @@
1
+ name: ci
2
+ on:
3
+ pull_request:
4
+ push:
5
+ branches: [ main ]
6
+
7
+ jobs:
8
+ core-tests:
9
+ runs-on: ubuntu-latest
10
+ steps:
11
+ - uses: actions/checkout@v4
12
+ - uses: actions/setup-python@v5
13
+ with:
14
+ python-version: "3.11"
15
+ - run: pip install -e ".[dev]"
16
+ - run: pytest
17
+
18
+ prompt-checks:
19
+ runs-on: ubuntu-latest
20
+ steps:
21
+ - uses: actions/checkout@v4
22
+ - uses: actions/setup-python@v5
23
+ with:
24
+ python-version: "3.11"
25
+ - run: pip install instructvault
26
+ - run: ivault validate prompts
27
+ - run: ivault eval prompts/hello_world.prompt.yml --report out/report.json --junit out/junit.xml
28
+ - uses: actions/upload-artifact@v4
29
+ if: always()
30
+ with:
31
+ name: ivault-reports
32
+ path: out/
@@ -0,0 +1,30 @@
1
+ name: prompt-checks
2
+ on:
3
+ pull_request:
4
+ paths:
5
+ - "prompts/**"
6
+ - "datasets/**"
7
+ - ".github/workflows/prompt-checks.yml"
8
+ push:
9
+ branches: [ main ]
10
+ paths:
11
+ - "prompts/**"
12
+ - "datasets/**"
13
+ - ".github/workflows/prompt-checks.yml"
14
+
15
+ jobs:
16
+ prompt-checks:
17
+ runs-on: ubuntu-latest
18
+ steps:
19
+ - uses: actions/checkout@v4
20
+ - uses: actions/setup-python@v5
21
+ with:
22
+ python-version: "3.11"
23
+ - run: pip install instructvault
24
+ - run: ivault validate prompts
25
+ - run: ivault eval prompts/hello_world.prompt.yml --report out/report.json --junit out/junit.xml
26
+ - uses: actions/upload-artifact@v4
27
+ if: always()
28
+ with:
29
+ name: ivault-reports
30
+ path: out/
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: instructvault
3
- Version: 0.2.2
3
+ Version: 0.2.3
4
4
  Summary: Git-first prompt registry + CI evals + lightweight runtime SDK (ivault).
5
5
  Project-URL: Homepage, https://github.com/05satyam/instruct_vault
6
6
  Project-URL: Repository, https://github.com/05satyam/instruct_vault
@@ -14,6 +14,7 @@ Requires-Dist: pyyaml>=6.0
14
14
  Requires-Dist: rich>=13.7
15
15
  Requires-Dist: typer>=0.12
16
16
  Provides-Extra: dev
17
+ Requires-Dist: httpx>=0.27; extra == 'dev'
17
18
  Requires-Dist: mypy>=1.10; extra == 'dev'
18
19
  Requires-Dist: pytest-cov>=5.0; extra == 'dev'
19
20
  Requires-Dist: pytest>=8.0; extra == 'dev'
@@ -48,6 +49,10 @@ flowchart LR
48
49
  Enterprises already have Git + PR reviews + CI/CD. Prompts usually don’t.
49
50
  InstructVault brings **prompt‑as‑code** without requiring a server, database, or platform.
50
51
 
52
+ ## Vision
53
+ Short version: Git‑first prompts with CI governance and zero‑latency runtime.
54
+ Full vision: `docs/vision.md`
55
+
51
56
  ## Features
52
57
  - ✅ Git‑native versioning (tags/SHAs = releases)
53
58
  - ✅ CLI‑first (`init`, `validate`, `render`, `eval`, `diff`, `resolve`, `bundle`)
@@ -128,6 +133,10 @@ ivault render prompts/support_reply.prompt.yml --vars '{"ticket_text":"My app cr
128
133
  ivault eval prompts/support_reply.prompt.yml --dataset datasets/support_cases.jsonl --report out/report.json --junit out/junit.xml
129
134
  ```
130
135
 
136
+ Note: Prompts must include at least one inline test. Datasets are optional.
137
+ Migration tip: if you need to render a prompt that doesn’t yet include tests, use
138
+ `ivault render --allow-no-tests` or add a minimal test first.
139
+
131
140
  ### 5) Version prompts with tags
132
141
  ```bash
133
142
  git add prompts datasets
@@ -176,7 +185,17 @@ export IVAULT_REPO_ROOT=/path/to/your/repo
176
185
  PYTHONPATH=. uvicorn ivault_playground.app:app --reload
177
186
  ```
178
187
 
188
+ ![Playground screenshot](docs/assets/playground.png)
189
+
190
+ Optional auth:
191
+ ```bash
192
+ export IVAULT_PLAYGROUND_API_KEY=your-secret
193
+ ```
194
+ Then send `x-ivault-api-key` in requests (or keep it behind your org gateway).
195
+ If you don’t set the env var, no auth is required.
196
+
179
197
  ## Docs
198
+ - `docs/vision.md`
180
199
  - `docs/governance.md`
181
200
  - `docs/ci.md`
182
201
  - `docs/playground.md`
@@ -25,6 +25,10 @@ flowchart LR
25
25
  Enterprises already have Git + PR reviews + CI/CD. Prompts usually don’t.
26
26
  InstructVault brings **prompt‑as‑code** without requiring a server, database, or platform.
27
27
 
28
+ ## Vision
29
+ Short version: Git‑first prompts with CI governance and zero‑latency runtime.
30
+ Full vision: `docs/vision.md`
31
+
28
32
  ## Features
29
33
  - ✅ Git‑native versioning (tags/SHAs = releases)
30
34
  - ✅ CLI‑first (`init`, `validate`, `render`, `eval`, `diff`, `resolve`, `bundle`)
@@ -105,6 +109,10 @@ ivault render prompts/support_reply.prompt.yml --vars '{"ticket_text":"My app cr
105
109
  ivault eval prompts/support_reply.prompt.yml --dataset datasets/support_cases.jsonl --report out/report.json --junit out/junit.xml
106
110
  ```
107
111
 
112
+ Note: Prompts must include at least one inline test. Datasets are optional.
113
+ Migration tip: if you need to render a prompt that doesn’t yet include tests, use
114
+ `ivault render --allow-no-tests` or add a minimal test first.
115
+
108
116
  ### 5) Version prompts with tags
109
117
  ```bash
110
118
  git add prompts datasets
@@ -153,7 +161,17 @@ export IVAULT_REPO_ROOT=/path/to/your/repo
153
161
  PYTHONPATH=. uvicorn ivault_playground.app:app --reload
154
162
  ```
155
163
 
164
+ ![Playground screenshot](docs/assets/playground.png)
165
+
166
+ Optional auth:
167
+ ```bash
168
+ export IVAULT_PLAYGROUND_API_KEY=your-secret
169
+ ```
170
+ Then send `x-ivault-api-key` in requests (or keep it behind your org gateway).
171
+ If you don’t set the env var, no auth is required.
172
+
156
173
  ## Docs
174
+ - `docs/vision.md`
157
175
  - `docs/governance.md`
158
176
  - `docs/ci.md`
159
177
  - `docs/playground.md`
@@ -0,0 +1,76 @@
1
+ # InstructVault (ivault) — Vision
2
+
3
+ ## Purpose
4
+ InstructVault makes prompts **first‑class, governed, testable, versioned artifacts** — just like code — while keeping runtime **fast and local**.
5
+
6
+ ## North Star
7
+ **Prompts live in Git.**
8
+ **Prompt changes flow through CI/CD.**
9
+ **Prompt releases are immutable and reproducible.**
10
+ **Runtime stays fast, local, and framework‑agnostic.**
11
+
12
+ If InstructVault ever violates this, it is a bug — not a feature.
13
+
14
+ ## Non‑Negotiables
15
+ 1) **Git is the source of truth**
16
+ Prompts are files; versions are tags/SHAs/branches. No prompt database required.
17
+
18
+ 2) **Zero runtime latency**
19
+ No network calls at inference time. Load from local repo or build‑time bundles.
20
+
21
+ 3) **Framework & vendor agnostic**
22
+ Output is standard `{ role, content }` messages. Works with any LLM stack.
23
+
24
+ 4) **Governance first**
25
+ Prompt changes go through PRs, reviews, and CI checks. No direct writes.
26
+
27
+ 5) **Small core, extensible edges**
28
+ Core stays tiny and auditable. Heavy integrations are optional and separate.
29
+
30
+ ## What the Core Provides
31
+ - Prompt spec + validation
32
+ - Deterministic rendering
33
+ - Git ref loading
34
+ - Deterministic evals (inline + dataset)
35
+ - CLI (`ivault`) and runtime SDK
36
+
37
+ ## What the Core Explicitly Does Not Provide
38
+ - Hosted services or databases
39
+ - Forced LLM calls
40
+ - Cloud SDK dependencies in core
41
+
42
+ ## Prompt Spec (YAML/JSON)
43
+ ```yaml
44
+ spec_version: "1.0"
45
+ name: support_reply
46
+ variables:
47
+ required: [ticket_text]
48
+ messages:
49
+ - role: system
50
+ content: "You are a support engineer."
51
+ - role: user
52
+ content: "Ticket: {{ ticket_text }}"
53
+ tests:
54
+ - name: must_include_ticket
55
+ vars: { ticket_text: "Order damaged" }
56
+ assert: { contains_any: ["Ticket:"] }
57
+ ```
58
+
59
+ ## CLI Contract
60
+ `init`, `validate`, `render`, `eval`, `diff`, `resolve`, `bundle`
61
+ Deterministic behavior, CI‑friendly exit codes, JSON/JUnit outputs where applicable.
62
+
63
+ ## CI/CD Model (PromptOps)
64
+ - Run checks only when prompt paths change
65
+ - Stages: **validate → eval → report**
66
+ - Fail fast and clearly
67
+
68
+ ## Governance Model
69
+ Use existing Git governance: CODEOWNERS, branch protection, required CI checks.
70
+
71
+ ## Success Criteria
72
+ - Teams adopt without friction
73
+ - Prompt changes are reviewed like code
74
+ - CI catches regressions
75
+ - Rollbacks are instant
76
+ - Runtime stays fast and local
@@ -0,0 +1,14 @@
1
+ spec_version: "1.0"
2
+ name: hello_world
3
+ description: Minimal example prompt.
4
+ variables:
5
+ required: [name]
6
+ messages:
7
+ - role: system
8
+ content: "You are a helpful assistant."
9
+ - role: user
10
+ content: "Say hello to {{ name }}."
11
+ tests:
12
+ - name: includes_name
13
+ vars: { name: "Ava" }
14
+ assert: { contains_any: ["Ava"] }
@@ -17,5 +17,20 @@ export IVAULT_REPO_ROOT=/path/to/your/repo
17
17
  PYTHONPATH=.. uvicorn ivault_playground.app:app --reload
18
18
  ```
19
19
 
20
+ Alternative (from repo root):
21
+ ```
22
+ export IVAULT_REPO_ROOT=/path/to/your/repo
23
+ PYTHONPATH=. uvicorn ivault_playground.app:app --reload
24
+ ```
25
+
26
+ Then open:
27
+ - `http://127.0.0.1:8000/` (landing page)
28
+ - `http://127.0.0.1:8000/docs` (API docs)
29
+
20
30
  Environment:
21
31
  - `IVAULT_REPO_ROOT` (default: current working directory)
32
+ - `IVAULT_PLAYGROUND_API_KEY` (optional; if set, require `x-ivault-api-key` header)
33
+
34
+ Notes:
35
+ - This minimal playground has no auth; put it behind your org auth if hosted.
36
+ - PR-only writes are not implemented yet (API is read-only + eval).
@@ -0,0 +1,29 @@
1
+ from __future__ import annotations
2
+ import os
3
+ from fastapi import FastAPI, Request
4
+ from fastapi.responses import HTMLResponse
5
+ from fastapi.staticfiles import StaticFiles
6
+ from pathlib import Path
7
+ from .routes.api import router as api_router
8
+ from .routes.ui import router as ui_router
9
+
10
+ app = FastAPI(title="ivault-playground", version="0.1.0")
11
+
12
+ _API_KEY = os.getenv("IVAULT_PLAYGROUND_API_KEY")
13
+
14
+ @app.middleware("http")
15
+ async def _api_key_guard(request: Request, call_next):
16
+ if not _API_KEY:
17
+ return await call_next(request)
18
+ if request.url.path in ("/health",):
19
+ return await call_next(request)
20
+ key = request.headers.get("x-ivault-api-key")
21
+ if key != _API_KEY:
22
+ return HTMLResponse("Unauthorized", status_code=401)
23
+ return await call_next(request)
24
+
25
+ app.include_router(ui_router)
26
+ app.include_router(api_router)
27
+
28
+ _STATIC_DIR = Path(__file__).resolve().parent / "static"
29
+ app.mount("/static", StaticFiles(directory=_STATIC_DIR), name="static")
@@ -0,0 +1,103 @@
1
+ from __future__ import annotations
2
+ import os
3
+ import subprocess
4
+ from pathlib import Path
5
+ from typing import Any, Dict, List, Optional
6
+
7
+ from fastapi import APIRouter, HTTPException
8
+ from pydantic import BaseModel
9
+
10
+ from instructvault import InstructVault
11
+ from instructvault.io import load_prompt_spec, load_dataset_jsonl
12
+ from instructvault.store import PromptStore
13
+ from instructvault.eval import run_dataset, run_inline_tests
14
+
15
+ router = APIRouter()
16
+
17
+ class RenderRequest(BaseModel):
18
+ prompt_path: str
19
+ vars: Dict[str, Any]
20
+ ref: Optional[str] = None
21
+
22
+ class EvalRequest(BaseModel):
23
+ prompt_path: str
24
+ dataset_path: Optional[str] = None
25
+ ref: Optional[str] = None
26
+
27
+ def _repo_root() -> Path:
28
+ return Path(os.getenv("IVAULT_REPO_ROOT", ".")).resolve()
29
+
30
+ @router.get("/health")
31
+ def health() -> Dict[str, str]:
32
+ return {"status": "ok"}
33
+
34
+ @router.get("/prompts")
35
+ def list_prompts(ref: Optional[str] = None) -> List[str]:
36
+ repo = _repo_root()
37
+ prompts_dir = repo / "prompts"
38
+ if ref:
39
+ cmd = ["git", "-C", str(repo), "ls-tree", "-r", "--name-only", ref, "prompts"]
40
+ res = subprocess.run(cmd, capture_output=True, text=True)
41
+ if res.returncode != 0:
42
+ return []
43
+ files = [line.strip() for line in res.stdout.splitlines() if line.strip()]
44
+ return [p for p in files if p.endswith((".prompt.yml", ".prompt.yaml", ".prompt.json"))]
45
+ if not prompts_dir.exists():
46
+ return []
47
+ files = sorted(prompts_dir.rglob("*.prompt.y*ml")) + sorted(prompts_dir.rglob("*.prompt.json"))
48
+ return [p.relative_to(repo).as_posix() for p in files]
49
+
50
+ @router.get("/refs")
51
+ def list_refs() -> List[str]:
52
+ repo = _repo_root()
53
+ cmd = ["git", "-C", str(repo), "tag", "--list", "prompts/*"]
54
+ res = subprocess.run(cmd, capture_output=True, text=True)
55
+ if res.returncode != 0:
56
+ return []
57
+ return [r.strip() for r in res.stdout.splitlines() if r.strip()]
58
+
59
+ @router.get("/prompt")
60
+ def get_prompt(prompt_path: str, ref: Optional[str] = None) -> Dict[str, Any]:
61
+ repo = _repo_root()
62
+ if ref:
63
+ store = PromptStore(repo)
64
+ try:
65
+ spec = load_prompt_spec(store.read_text(prompt_path, ref=ref))
66
+ except Exception:
67
+ raise HTTPException(status_code=404, detail="Prompt not found at ref")
68
+ else:
69
+ p = repo / prompt_path
70
+ if not p.exists():
71
+ raise HTTPException(status_code=404, detail="Prompt not found")
72
+ spec = load_prompt_spec(p.read_text(encoding="utf-8"))
73
+ return spec.model_dump(by_alias=True)
74
+
75
+ @router.post("/render")
76
+ def render(req: RenderRequest) -> List[Dict[str, str]]:
77
+ repo = _repo_root()
78
+ vault = InstructVault(repo_root=repo)
79
+ msgs = vault.render(req.prompt_path, vars=req.vars, ref=req.ref)
80
+ return [{"role": m.role, "content": m.content} for m in msgs]
81
+
82
+ @router.post("/eval")
83
+ def eval_prompt(req: EvalRequest) -> Dict[str, Any]:
84
+ repo = _repo_root()
85
+ store = PromptStore(repo)
86
+ spec = load_prompt_spec(store.read_text(req.prompt_path, ref=req.ref))
87
+
88
+ ok1, r1 = run_inline_tests(spec)
89
+ results = list(r1)
90
+ ok = ok1
91
+
92
+ if req.dataset_path:
93
+ rows = load_dataset_jsonl((repo / req.dataset_path).read_text(encoding="utf-8"))
94
+ ok2, r2 = run_dataset(spec, rows)
95
+ ok = ok and ok2
96
+ results.extend(r2)
97
+
98
+ return {
99
+ "prompt": spec.name,
100
+ "ref": req.ref or "WORKTREE",
101
+ "pass": ok,
102
+ "results": [{"test": r.name, "pass": r.passed, "error": r.error} for r in results],
103
+ }
@@ -0,0 +1,12 @@
1
+ from __future__ import annotations
2
+ from fastapi import APIRouter
3
+ from fastapi.responses import HTMLResponse
4
+ from pathlib import Path
5
+
6
+ router = APIRouter()
7
+
8
+ _TEMPLATE = Path(__file__).resolve().parent.parent / "templates" / "index.html"
9
+
10
+ @router.get("/", response_class=HTMLResponse)
11
+ def index() -> str:
12
+ return _TEMPLATE.read_text(encoding="utf-8")
@@ -0,0 +1,145 @@
1
+ :root {
2
+ --bg: #0f172a;
3
+ --panel: #111827;
4
+ --muted: #94a3b8;
5
+ --text: #e2e8f0;
6
+ --accent: #22c55e;
7
+ --line: #1f2937;
8
+ }
9
+ body {
10
+ margin: 0;
11
+ font-family: "IBM Plex Sans", "Segoe UI", "Helvetica Neue", Arial, sans-serif;
12
+ background: radial-gradient(1200px 600px at 10% -10%, #1e293b, #0f172a);
13
+ color: var(--text);
14
+ }
15
+ .wrap { max-width: 980px; margin: 48px auto; padding: 0 24px; }
16
+ .hero {
17
+ background: linear-gradient(140deg, #0b1220, #111827);
18
+ border: 1px solid var(--line);
19
+ border-radius: 16px;
20
+ padding: 32px;
21
+ box-shadow: 0 10px 30px rgba(0,0,0,0.35);
22
+ }
23
+ h1 { margin: 0 0 8px 0; font-size: 32px; letter-spacing: 0.3px; }
24
+ .sub { color: var(--muted); margin: 0 0 24px 0; }
25
+ .grid {
26
+ display: grid;
27
+ grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
28
+ gap: 16px;
29
+ }
30
+ .split {
31
+ display: grid;
32
+ grid-template-columns: 1fr 1fr;
33
+ gap: 16px;
34
+ margin-top: 20px;
35
+ }
36
+ .panel {
37
+ background: var(--panel);
38
+ border: 1px solid var(--line);
39
+ border-radius: 12px;
40
+ padding: 16px;
41
+ }
42
+ .panel h3 { margin: 0 0 8px 0; font-size: 16px; }
43
+ .row {
44
+ display: flex;
45
+ gap: 8px;
46
+ align-items: center;
47
+ margin-bottom: 12px;
48
+ }
49
+ .col {
50
+ display: flex;
51
+ flex-direction: column;
52
+ gap: 8px;
53
+ }
54
+ select {
55
+ background: #0b1220;
56
+ color: var(--text);
57
+ border: 1px solid var(--line);
58
+ border-radius: 8px;
59
+ padding: 6px 8px;
60
+ }
61
+ textarea {
62
+ width: 100%;
63
+ min-height: 90px;
64
+ background: #0b1220;
65
+ color: var(--text);
66
+ border: 1px solid var(--line);
67
+ border-radius: 8px;
68
+ padding: 10px;
69
+ font-family: "IBM Plex Mono", ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", monospace;
70
+ font-size: 12px;
71
+ }
72
+ .btn {
73
+ background: #16a34a;
74
+ color: #0b1220;
75
+ border: none;
76
+ border-radius: 8px;
77
+ padding: 8px 12px;
78
+ cursor: pointer;
79
+ font-weight: 600;
80
+ }
81
+ .btn.secondary {
82
+ background: transparent;
83
+ color: var(--text);
84
+ border: 1px solid var(--line);
85
+ }
86
+ .list {
87
+ max-height: 320px;
88
+ overflow: auto;
89
+ border: 1px solid var(--line);
90
+ border-radius: 8px;
91
+ }
92
+ .list button {
93
+ display: block;
94
+ width: 100%;
95
+ text-align: left;
96
+ padding: 10px 12px;
97
+ border: none;
98
+ background: transparent;
99
+ color: var(--text);
100
+ border-bottom: 1px solid var(--line);
101
+ cursor: pointer;
102
+ }
103
+ .list button:hover { background: rgba(148,163,184,0.08); }
104
+ pre {
105
+ background: #0b1220;
106
+ border: 1px solid var(--line);
107
+ border-radius: 8px;
108
+ padding: 12px;
109
+ color: #e5e7eb;
110
+ overflow: auto;
111
+ max-height: 320px;
112
+ }
113
+ .card {
114
+ background: var(--panel);
115
+ border: 1px solid var(--line);
116
+ border-radius: 12px;
117
+ padding: 16px;
118
+ }
119
+ .card h3 { margin: 0 0 8px 0; font-size: 16px; }
120
+ .card p { margin: 0; color: var(--muted); font-size: 14px; }
121
+ .links a {
122
+ display: inline-block;
123
+ margin-right: 12px;
124
+ color: var(--text);
125
+ text-decoration: none;
126
+ border-bottom: 1px solid transparent;
127
+ }
128
+ .links a:hover { border-bottom-color: var(--accent); }
129
+ .badge {
130
+ display: inline-block;
131
+ padding: 4px 10px;
132
+ border-radius: 999px;
133
+ background: rgba(34,197,94,0.15);
134
+ color: #86efac;
135
+ font-size: 12px;
136
+ margin-left: 8px;
137
+ }
138
+ footer { margin-top: 24px; color: var(--muted); font-size: 12px; }
139
+ @media (max-width: 900px) {
140
+ .split { grid-template-columns: 1fr; }
141
+ }
142
+ @media (max-width: 640px) {
143
+ .wrap { margin: 24px auto; }
144
+ h1 { font-size: 26px; }
145
+ }
@@ -0,0 +1,116 @@
1
+ async function loadPrompts() {
2
+ const list = document.getElementById("promptList");
3
+ list.innerHTML = "Loading...";
4
+ try {
5
+ const ref = document.getElementById("refSelect").value;
6
+ const url = ref ? `/prompts?ref=${encodeURIComponent(ref)}` : "/prompts";
7
+ const res = await fetch(url);
8
+ const data = await res.json();
9
+ list.innerHTML = "";
10
+ if (!data.length) {
11
+ list.innerHTML = "<div style='padding:10px;color:#94a3b8;'>No prompts found.</div>";
12
+ return;
13
+ }
14
+ data.forEach((p) => {
15
+ const btn = document.createElement("button");
16
+ btn.textContent = p;
17
+ btn.onclick = () => loadPrompt(p);
18
+ list.appendChild(btn);
19
+ });
20
+ } catch (e) {
21
+ list.innerHTML = "<div style='padding:10px;color:#f87171;'>Failed to load prompts.</div>";
22
+ }
23
+ }
24
+
25
+ async function loadPrompt(path) {
26
+ const preview = document.getElementById("promptPreview");
27
+ preview.textContent = "Loading...";
28
+ try {
29
+ const ref = document.getElementById("refSelect").value;
30
+ const url = ref
31
+ ? `/prompt?prompt_path=${encodeURIComponent(path)}&ref=${encodeURIComponent(ref)}`
32
+ : `/prompt?prompt_path=${encodeURIComponent(path)}`;
33
+ const res = await fetch(url);
34
+ const data = await res.json();
35
+ preview.textContent = JSON.stringify(data, null, 2);
36
+ window.__currentPromptPath = path;
37
+ window.__currentPromptRef = ref || null;
38
+ } catch (e) {
39
+ preview.textContent = "Failed to load prompt.";
40
+ }
41
+ }
42
+
43
+ async function loadRefs() {
44
+ try {
45
+ const res = await fetch("/refs");
46
+ const data = await res.json();
47
+ const sel = document.getElementById("refSelect");
48
+ data.forEach((r) => {
49
+ const opt = document.createElement("option");
50
+ opt.value = r;
51
+ opt.textContent = r;
52
+ sel.appendChild(opt);
53
+ });
54
+ sel.onchange = () => {
55
+ document.getElementById("promptPreview").textContent = "Select a prompt to view its spec.";
56
+ document.getElementById("renderOutput").textContent = "Rendered messages will appear here.";
57
+ window.__currentPromptPath = null;
58
+ window.__currentPromptRef = sel.value || null;
59
+ loadPrompts();
60
+ };
61
+ } catch (e) {}
62
+ }
63
+
64
+ function attachHandlers() {
65
+ document.getElementById("varsInput").value = JSON.stringify({name: "ivault"});
66
+
67
+ document.getElementById("copyBtn").onclick = async () => {
68
+ const text = document.getElementById("promptPreview").textContent;
69
+ try {
70
+ await navigator.clipboard.writeText(text);
71
+ document.getElementById("copyBtn").textContent = "Copied";
72
+ setTimeout(() => (document.getElementById("copyBtn").textContent = "Copy JSON"), 1200);
73
+ } catch (e) {}
74
+ };
75
+
76
+ document.getElementById("renderBtn").onclick = async () => {
77
+ const out = document.getElementById("renderOutput");
78
+ out.textContent = "Rendering...";
79
+ try {
80
+ const path = window.__currentPromptPath;
81
+ if (!path) {
82
+ out.textContent = "Select a prompt first.";
83
+ return;
84
+ }
85
+ const varsText = document.getElementById("varsInput").value || "{}";
86
+ let vars;
87
+ try {
88
+ vars = JSON.parse(varsText);
89
+ } catch (e) {
90
+ out.textContent = 'Invalid JSON in vars. Example: {"name":"Ava"}';
91
+ return;
92
+ }
93
+ const ref = window.__currentPromptRef;
94
+ const payload = { prompt_path: path, vars, ref };
95
+ const res = await fetch("/render", {
96
+ method: "POST",
97
+ headers: { "Content-Type": "application/json" },
98
+ body: JSON.stringify(payload),
99
+ });
100
+ const data = await res.json();
101
+ if (!res.ok) {
102
+ out.textContent = data.detail || "Render failed.";
103
+ return;
104
+ }
105
+ out.textContent = JSON.stringify(data, null, 2);
106
+ } catch (e) {
107
+ out.textContent = "Render failed. Check JSON vars.";
108
+ }
109
+ };
110
+ }
111
+
112
+ window.addEventListener("load", () => {
113
+ loadPrompts();
114
+ loadRefs();
115
+ attachHandlers();
116
+ });