agent-scaffold-cli 0.1.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (66) hide show
  1. agent_scaffold/__init__.py +8 -0
  2. agent_scaffold/__main__.py +6 -0
  3. agent_scaffold/_bundled_deployments/__init__.py +15 -0
  4. agent_scaffold/_bundled_deployments/docs/cross-cutting/README.md +15 -0
  5. agent_scaffold/_bundled_deployments/docs/cross-cutting/auth-jwt.md +235 -0
  6. agent_scaffold/_bundled_deployments/docs/cross-cutting/logging-structured.md +196 -0
  7. agent_scaffold/_bundled_deployments/docs/cross-cutting/observability.md +259 -0
  8. agent_scaffold/_bundled_deployments/docs/cross-cutting/rate-limiting.md +171 -0
  9. agent_scaffold/_bundled_deployments/docs/cross-cutting/testing-strategy.md +261 -0
  10. agent_scaffold/_bundled_deployments/docs/frameworks/README.md +22 -0
  11. agent_scaffold/_bundled_deployments/docs/frameworks/crewai.md +91 -0
  12. agent_scaffold/_bundled_deployments/docs/frameworks/langgraph.md +79 -0
  13. agent_scaffold/_bundled_deployments/docs/frameworks/mastra.md +74 -0
  14. agent_scaffold/_bundled_deployments/docs/frameworks/pydantic-ai.md +77 -0
  15. agent_scaffold/_bundled_deployments/docs/frameworks/vercel-ai-sdk.md +83 -0
  16. agent_scaffold/_bundled_deployments/docs/patterns/README.md +26 -0
  17. agent_scaffold/_bundled_deployments/docs/patterns/memory.md +82 -0
  18. agent_scaffold/_bundled_deployments/docs/patterns/multi-agent-flat.md +72 -0
  19. agent_scaffold/_bundled_deployments/docs/patterns/multi-agent-hierarchical.md +83 -0
  20. agent_scaffold/_bundled_deployments/docs/patterns/parallel-calls.md +73 -0
  21. agent_scaffold/_bundled_deployments/docs/patterns/plan-execute-reflect.md +77 -0
  22. agent_scaffold/_bundled_deployments/docs/patterns/prompt-chaining.md +73 -0
  23. agent_scaffold/_bundled_deployments/docs/patterns/rag.md +84 -0
  24. agent_scaffold/_bundled_deployments/docs/patterns/react.md +77 -0
  25. agent_scaffold/_bundled_deployments/docs/patterns/routing-tool-use.md +69 -0
  26. agent_scaffold/_bundled_deployments/docs/recipes/README.md +39 -0
  27. agent_scaffold/_bundled_deployments/docs/recipes/code-review-agent.md +518 -0
  28. agent_scaffold/_bundled_deployments/docs/recipes/content-pipeline.md +525 -0
  29. agent_scaffold/_bundled_deployments/docs/recipes/customer-support-triage.md +1679 -0
  30. agent_scaffold/_bundled_deployments/docs/recipes/docs-rag-qa.md +1254 -0
  31. agent_scaffold/_bundled_deployments/docs/recipes/hierarchical-agent.md +554 -0
  32. agent_scaffold/_bundled_deployments/docs/recipes/memory-assistant.md +499 -0
  33. agent_scaffold/_bundled_deployments/docs/recipes/ops-crew.md +457 -0
  34. agent_scaffold/_bundled_deployments/docs/recipes/parallel-enricher.md +457 -0
  35. agent_scaffold/_bundled_deployments/docs/recipes/research-assistant.md +1096 -0
  36. agent_scaffold/_bundled_deployments/docs/stack/README.md +19 -0
  37. agent_scaffold/_bundled_deployments/docs/stack/api-fastapi.md +112 -0
  38. agent_scaffold/_bundled_deployments/docs/stack/api-hono.md +108 -0
  39. agent_scaffold/_bundled_deployments/docs/stack/cache-redis.md +85 -0
  40. agent_scaffold/_bundled_deployments/docs/stack/eval-deepeval-ragas-promptfoo.md +164 -0
  41. agent_scaffold/_bundled_deployments/docs/stack/llm-claude.md +105 -0
  42. agent_scaffold/_bundled_deployments/docs/stack/relational-postgres.md +122 -0
  43. agent_scaffold/_bundled_deployments/docs/stack/tool-protocol-mcp.md +275 -0
  44. agent_scaffold/_bundled_deployments/docs/stack/tracing-langfuse.md +108 -0
  45. agent_scaffold/_bundled_deployments/docs/stack/vector-qdrant.md +121 -0
  46. agent_scaffold/cache.py +32 -0
  47. agent_scaffold/cli.py +512 -0
  48. agent_scaffold/config.py +117 -0
  49. agent_scaffold/context.py +253 -0
  50. agent_scaffold/contract.py +141 -0
  51. agent_scaffold/discovery.py +112 -0
  52. agent_scaffold/generator.py +213 -0
  53. agent_scaffold/languages/__init__.py +0 -0
  54. agent_scaffold/languages/python.yaml +28 -0
  55. agent_scaffold/languages/typescript.yaml +25 -0
  56. agent_scaffold/prompts/__init__.py +0 -0
  57. agent_scaffold/prompts/repair.md +9 -0
  58. agent_scaffold/prompts/system.md +21 -0
  59. agent_scaffold/prompts/user_template.md +43 -0
  60. agent_scaffold/validator.py +133 -0
  61. agent_scaffold/writer.py +171 -0
  62. agent_scaffold_cli-0.1.1.dist-info/METADATA +147 -0
  63. agent_scaffold_cli-0.1.1.dist-info/RECORD +66 -0
  64. agent_scaffold_cli-0.1.1.dist-info/WHEEL +4 -0
  65. agent_scaffold_cli-0.1.1.dist-info/entry_points.txt +2 -0
  66. agent_scaffold_cli-0.1.1.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,8 @@
1
+ """agent-scaffold: generate runnable AI agent projects from markdown specs."""
2
+
3
+ from importlib.metadata import PackageNotFoundError, version
4
+
5
+ try:
6
+ __version__ = version("agent-scaffold-cli")
7
+ except PackageNotFoundError:
8
+ __version__ = "0.0.0+unknown"
@@ -0,0 +1,6 @@
1
+ """Enable `python -m agent_scaffold`."""
2
+
3
+ from agent_scaffold.cli import app
4
+
5
+ if __name__ == "__main__":
6
+ app()
@@ -0,0 +1,15 @@
1
+ """Bundled agent-deployments docs for zero-config usage.
2
+
3
+ The docs/ directory is populated at build time by scripts/sync_deployments.sh.
4
+ When installed via PyPI/Homebrew, the bundled docs allow agent-scaffold to work
5
+ without requiring a separate agent-deployments clone.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ from pathlib import Path
11
+
12
+
13
+ def bundled_docs_path() -> Path:
14
+ """Return the path to the bundled deployments root (contains docs/)."""
15
+ return Path(__file__).parent
@@ -0,0 +1,15 @@
1
+ # Cross-cutting Concerns
2
+
3
+ Shared production plumbing used by all agents. Each file answers: **"What production scaffolding do I need?"**
4
+
5
+ | Concern | Library (Py / TS) | Reference |
6
+ |---------|-------------------|-----------|
7
+ | [Auth](auth-jwt.md) | python-jose / hono-jwt | Inline implementation below |
8
+ | [Logging](logging-structured.md) | structlog / pino | Inline implementation below |
9
+ | [Observability](observability.md) | Langfuse SDK | Inline implementation below |
10
+ | [Rate Limiting](rate-limiting.md) | slowapi / hono-rate-limiter | Inline implementation below |
11
+ | [Testing](testing-strategy.md) | pytest + DeepEval / vitest + Promptfoo | Inline implementation below |
12
+
13
+ ## The 11-point production checklist
14
+
15
+ Every blueprint specifies these concerns. See [playbook/production-checklist.md](../playbook/production-checklist.md) for the full checklist.
@@ -0,0 +1,235 @@
1
+ # Cross-cutting: JWT Authentication
2
+
3
+ **Concern:** Protect all agent endpoints with bearer-token authentication.
4
+ **Library:** `python-jose` (Py) / `jose` (TS)
5
+ **Lives in:** Inline below (formerly `common/python/agent_common/auth/` and `common/typescript/src/auth/`)
6
+
7
+ ## What it provides
8
+
9
+ - **Token creation** -- `create_token()` / `createToken()` signs a JWT with a user ID, expiry, and optional extra claims.
10
+ - **Token verification** -- `verify_token()` / `verifyToken()` decodes and validates a JWT, returning a typed payload.
11
+ - **FastAPI dependency** -- `get_current_user(secret)` returns a FastAPI `Depends()` that extracts the bearer token from the `Authorization` header, verifies it, and returns a `TokenPayload`. Returns 401 on failure.
12
+ - **Typed payload** -- `TokenPayload` model (Pydantic / TS interface) with `sub`, `exp`, and extra claims.
13
+
14
+ ## How to use
15
+
16
+ ### Python (FastAPI)
17
+
18
+ ```python
19
+ from agent_common.auth import create_token, get_current_user, TokenPayload
20
+
21
+ # Create a token (for testing or a /login endpoint)
22
+ token = create_token("user-123", secret="my-secret", expires_hours=24)
23
+
24
+ # Protect an endpoint
25
+ @app.post("/query")
26
+ async def query(
27
+ request: QueryRequest,
28
+ user: TokenPayload = Depends(get_current_user("my-secret")),
29
+ ):
30
+ # user.sub is the authenticated user ID
31
+ return await handle_query(request, user_id=user.sub)
32
+ ```
33
+
34
+ ### TypeScript (Hono)
35
+
36
+ ```typescript
37
+ import { createToken, verifyToken } from "@agent-deployments/common";
38
+
39
+ // Create a token
40
+ const token = await createToken("user-123", "my-secret");
41
+
42
+ // Verify in middleware
43
+ app.use("/query/*", async (c, next) => {
44
+ const auth = c.req.header("Authorization");
45
+ const token = auth?.replace("Bearer ", "");
46
+ if (!token) return c.json({ error: "Unauthorized" }, 401);
47
+
48
+ try {
49
+ const payload = await verifyToken(token, "my-secret");
50
+ c.set("userId", payload.sub);
51
+ await next();
52
+ } catch {
53
+ return c.json({ error: "Invalid token" }, 401);
54
+ }
55
+ });
56
+ ```
57
+
58
+ ## Configuration via env
59
+
60
+ | Var | Default | Effect |
61
+ |-----|---------|--------|
62
+ | `JWT_SECRET` | `change-me-in-production` | Signing secret (HS256) |
63
+ | Algorithm | `HS256` | Symmetric signing for local dev. Switch to RS256 with a key pair for production |
64
+ | Expiry | 24 hours | Token lifetime |
65
+
66
+ ## Tests
67
+
68
+ Test token creation, verification, expiry, invalid tokens, and the FastAPI dependency (Py) / token round-trip and invalid signature (TS).
69
+
70
+ ## Production considerations
71
+
72
+ - **HS256 is fine for local dev** where the secret is in `.env`. For production, switch to **RS256** with asymmetric keys so the API only needs the public key.
73
+ - **Token rotation:** The current implementation has no refresh token flow. For production, add a `/refresh` endpoint or use short-lived tokens with an external auth provider.
74
+ - **Extra claims:** Pass `extra={"role": "admin"}` to embed authorization data in the token. The payload is available in the endpoint handler.
75
+
76
+ ## Swapping to an external auth provider
77
+
78
+ To use Auth0, Clerk, or Supabase Auth instead of self-managed JWT:
79
+
80
+ 1. Replace `create_token()` with the provider's token issuance (usually handled by their SDK).
81
+ 2. Replace `verify_token()` with JWKS-based verification against the provider's public keys.
82
+ 3. Keep the `get_current_user` dependency shape -- just change the verification logic inside.
83
+
84
+ This is a **single-file swap** (only the auth module changes).
85
+
86
+ ## Reference Implementation
87
+
88
+ <details>
89
+ <summary>Python — <code>jwt.py</code></summary>
90
+
91
+ ```python
92
+ """JWT utilities for FastAPI-based agent prototypes."""
93
+
94
+ from datetime import UTC, datetime, timedelta
95
+ from typing import Any
96
+
97
+ from fastapi import Depends, HTTPException, status
98
+ from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer
99
+ from jose import JWTError, jwt
100
+ from pydantic import BaseModel
101
+
102
+ _security = HTTPBearer()
103
+
104
+ _DEFAULT_ALGORITHM = "HS256"
105
+ _DEFAULT_EXPIRY_HOURS = 24
106
+
107
+
108
+ class TokenPayload(BaseModel):
109
+ sub: str
110
+ exp: datetime
111
+ extra: dict[str, Any] = {}
112
+
113
+
114
+ def create_token(
115
+ user_id: str,
116
+ secret: str,
117
+ *,
118
+ algorithm: str = _DEFAULT_ALGORITHM,
119
+ expires_hours: int = _DEFAULT_EXPIRY_HOURS,
120
+ extra: dict[str, Any] | None = None,
121
+ ) -> str:
122
+ """Create a signed JWT."""
123
+ now = datetime.now(UTC)
124
+ payload = {
125
+ "sub": user_id,
126
+ "iat": now,
127
+ "exp": now + timedelta(hours=expires_hours),
128
+ **(extra or {}),
129
+ }
130
+ return jwt.encode(payload, secret, algorithm=algorithm)
131
+
132
+
133
+ def verify_token(
134
+ token: str,
135
+ secret: str,
136
+ *,
137
+ algorithm: str = _DEFAULT_ALGORITHM,
138
+ ) -> TokenPayload:
139
+ """Verify and decode a JWT. Raises ValueError on failure."""
140
+ try:
141
+ payload = jwt.decode(token, secret, algorithms=[algorithm])
142
+ return TokenPayload(
143
+ sub=payload["sub"],
144
+ exp=datetime.fromtimestamp(payload["exp"], tz=UTC),
145
+ extra={k: v for k, v in payload.items() if k not in ("sub", "exp", "iat")},
146
+ )
147
+ except JWTError as exc:
148
+ raise ValueError(f"Invalid token: {exc}") from exc
149
+
150
+
151
+ def get_current_user(
152
+ secret: str,
153
+ algorithm: str = _DEFAULT_ALGORITHM,
154
+ ):
155
+ """Return a FastAPI dependency that extracts and verifies the JWT bearer token."""
156
+
157
+ async def _dependency(
158
+ credentials: HTTPAuthorizationCredentials = Depends(_security),
159
+ ) -> TokenPayload:
160
+ try:
161
+ return verify_token(credentials.credentials, secret, algorithm=algorithm)
162
+ except ValueError:
163
+ raise HTTPException(
164
+ status_code=status.HTTP_401_UNAUTHORIZED,
165
+ detail="Invalid or expired token",
166
+ )
167
+
168
+ return _dependency
169
+ ```
170
+
171
+ </details>
172
+
173
+ <details>
174
+ <summary>TypeScript — <code>jwt.ts</code></summary>
175
+
176
+ ```typescript
177
+ /**
178
+ * JWT utilities for Hono-based agent prototypes.
179
+ */
180
+
181
+ import * as jose from "jose";
182
+
183
+ export interface TokenPayload {
184
+ sub: string;
185
+ exp: number;
186
+ iat: number;
187
+ [key: string]: unknown;
188
+ }
189
+
190
+ const DEFAULT_ALGORITHM = "HS256";
191
+ const DEFAULT_EXPIRY_HOURS = 24;
192
+
193
+ /**
194
+ * Create a signed JWT.
195
+ */
196
+ export async function createToken(
197
+ userId: string,
198
+ secret: string,
199
+ options: {
200
+ algorithm?: string;
201
+ expiresHours?: number;
202
+ extra?: Record<string, unknown>;
203
+ } = {},
204
+ ): Promise<string> {
205
+ const { expiresHours = DEFAULT_EXPIRY_HOURS, extra = {} } = options;
206
+
207
+ const secretKey = new TextEncoder().encode(secret);
208
+ const now = Math.floor(Date.now() / 1000);
209
+
210
+ return new jose.SignJWT({ sub: userId, ...extra })
211
+ .setProtectedHeader({ alg: DEFAULT_ALGORITHM })
212
+ .setIssuedAt(now)
213
+ .setExpirationTime(now + expiresHours * 3600)
214
+ .sign(secretKey);
215
+ }
216
+
217
+ /**
218
+ * Verify and decode a JWT. Throws on failure.
219
+ */
220
+ export async function verifyToken(
221
+ token: string,
222
+ secret: string,
223
+ options: { algorithm?: string } = {},
224
+ ): Promise<TokenPayload> {
225
+ const secretKey = new TextEncoder().encode(secret);
226
+
227
+ const { payload } = await jose.jwtVerify(token, secretKey, {
228
+ algorithms: [DEFAULT_ALGORITHM],
229
+ });
230
+
231
+ return payload as TokenPayload;
232
+ }
233
+ ```
234
+
235
+ </details>
@@ -0,0 +1,196 @@
1
+ # Cross-cutting: Structured Logging
2
+
3
+ **Concern:** JSON-structured logs with request/session/user context on every line.
4
+ **Library:** `structlog` (Py) / `pino` (TS)
5
+ **Lives in:** Inline below (formerly `common/python/agent_common/logs/` and `common/typescript/src/logging/`)
6
+
7
+ ## What it provides
8
+
9
+ - **One-call setup** -- `configure(service_name, env, log_level)` (Py) / `createLogger(config)` (TS) configures the logger for the entire app.
10
+ - **Environment-aware output** -- Development mode renders human-readable colored output. Production mode renders JSON for log aggregators.
11
+ - **Contextual binding** -- `structlog.contextvars` (Py) / `pino.child()` (TS) lets you attach request-scoped context (trace ID, user ID, session ID) that appears on every subsequent log line.
12
+ - **Standard fields** -- Every log line includes: `service`, `env`, `level`, `timestamp` (ISO 8601), `msg`.
13
+
14
+ ## How to use
15
+
16
+ ### Python
17
+
18
+ ```python
19
+ from agent_common.logs import configure
20
+ import structlog
21
+
22
+ # At app startup (typically in lifespan)
23
+ configure("docs-rag-qa", env="production", log_level="INFO")
24
+
25
+ # Get a logger anywhere
26
+ logger = structlog.get_logger()
27
+
28
+ # Basic logging
29
+ logger.info("query_received", question="What is MCP?")
30
+
31
+ # Bind context for a request scope
32
+ log = logger.bind(trace_id="abc-123", user_id="user-1")
33
+ log.info("processing_query")
34
+ log.info("query_answered", citation_count=3)
35
+ # Both lines include trace_id and user_id
36
+ ```
37
+
38
+ Output (production):
39
+
40
+ ```json
41
+ {"service":"docs-rag-qa","env":"production","level":"info","timestamp":"2026-04-27T10:00:00Z","msg":"query_received","question":"What is MCP?"}
42
+ ```
43
+
44
+ ### TypeScript
45
+
46
+ ```typescript
47
+ import { createLogger } from "@agent-deployments/common";
48
+
49
+ const logger = createLogger({
50
+ serviceName: "docs-rag-qa",
51
+ env: "production",
52
+ level: "info",
53
+ });
54
+
55
+ // Basic logging
56
+ logger.info({ question: "What is MCP?" }, "query_received");
57
+
58
+ // Child logger with request context
59
+ const reqLog = logger.child({ traceId: "abc-123", userId: "user-1" });
60
+ reqLog.info("processing_query");
61
+ reqLog.info({ citationCount: 3 }, "query_answered");
62
+ ```
63
+
64
+ ## Configuration via env
65
+
66
+ | Var | Default | Effect |
67
+ |-----|---------|--------|
68
+ | `LOG_LEVEL` | `INFO` | Minimum level to emit (`DEBUG`, `INFO`, `WARNING`, `ERROR`) |
69
+ | `APP_ENV` | `development` | Controls output format: `development` = colored console, anything else = JSON |
70
+
71
+ ## Tests
72
+
73
+ Test configure in both modes and verify output format (Py). Test logger creation, child loggers, and level filtering (TS).
74
+
75
+ ## Logging conventions
76
+
77
+ Use these patterns consistently across prototypes:
78
+
79
+ | Event | Key | Example |
80
+ |-------|-----|---------|
81
+ | Request received | `{endpoint}_received` | `logger.info("query_received", ...)` |
82
+ | Processing step | `{step}_completed` | `logger.info("retrieval_completed", chunk_count=5)` |
83
+ | Error | `{operation}_failed` | `logger.error("query_failed", error=str(exc))` |
84
+ | External call | `{service}_called` | `logger.info("llm_called", model="claude-sonnet-4-6", tokens=150)` |
85
+
86
+ Always use **snake_case** event names. Always include the **trace_id** and **user_id** via context binding, not per-call arguments.
87
+
88
+ ## Swapping to OpenTelemetry-native logging
89
+
90
+ If your deployment already uses an OTel collector:
91
+
92
+ 1. Replace structlog/pino with `opentelemetry-sdk` (Py) / `@opentelemetry/sdk-logs` (TS).
93
+ 2. Remove the `configure()` / `createLogger()` call.
94
+ 3. Set `OTEL_EXPORTER_OTLP_ENDPOINT` in env.
95
+
96
+ This is a **multi-file swap** (common module + app startup + docker-compose for the OTel collector).
97
+
98
+ ## Reference Implementation
99
+
100
+ <details>
101
+ <summary>Python — <code>structlog_config.py</code></summary>
102
+
103
+ ```python
104
+ """Structured logging configuration using structlog."""
105
+
106
+ import logging
107
+ import sys
108
+
109
+ import structlog
110
+
111
+
112
+ def configure(
113
+ service_name: str,
114
+ *,
115
+ env: str = "development",
116
+ log_level: str = "INFO",
117
+ ) -> None:
118
+ """Configure structlog for the application.
119
+
120
+ In production, outputs JSON. In development, outputs colored human-readable logs.
121
+ """
122
+ shared_processors: list[structlog.types.Processor] = [
123
+ structlog.contextvars.merge_contextvars,
124
+ structlog.processors.add_log_level,
125
+ structlog.processors.TimeStamper(fmt="iso"),
126
+ structlog.processors.StackInfoRenderer(),
127
+ structlog.processors.format_exc_info,
128
+ ]
129
+
130
+ if env == "development":
131
+ renderer: structlog.types.Processor = structlog.dev.ConsoleRenderer()
132
+ else:
133
+ renderer = structlog.processors.JSONRenderer()
134
+
135
+ structlog.configure(
136
+ processors=[
137
+ *shared_processors,
138
+ structlog.processors.EventRenamer("msg"),
139
+ renderer,
140
+ ],
141
+ wrapper_class=structlog.make_filtering_bound_logger(logging.getLevelNamesMapping()[log_level.upper()]),
142
+ context_class=dict,
143
+ logger_factory=structlog.PrintLoggerFactory(file=sys.stdout),
144
+ cache_logger_on_first_use=True,
145
+ )
146
+
147
+ structlog.contextvars.clear_contextvars()
148
+ structlog.contextvars.bind_contextvars(service=service_name, env=env)
149
+ ```
150
+
151
+ </details>
152
+
153
+ <details>
154
+ <summary>TypeScript — <code>logger.ts</code></summary>
155
+
156
+ ```typescript
157
+ /**
158
+ * Structured logging using pino.
159
+ */
160
+
161
+ import pino from "pino";
162
+
163
+ export interface LoggerConfig {
164
+ serviceName: string;
165
+ env?: string;
166
+ level?: string;
167
+ }
168
+
169
+ /**
170
+ * Create a configured pino logger instance.
171
+ */
172
+ export function createLogger(config: LoggerConfig): pino.Logger {
173
+ const { serviceName, env = "development", level = "info" } = config;
174
+
175
+ const transport =
176
+ env === "development"
177
+ ? {
178
+ target: "pino/file",
179
+ options: { destination: 1 }, // stdout
180
+ }
181
+ : undefined;
182
+
183
+ return pino({
184
+ name: serviceName,
185
+ level,
186
+ transport,
187
+ base: {
188
+ service: serviceName,
189
+ env,
190
+ },
191
+ timestamp: pino.stdTimeFunctions.isoTime,
192
+ });
193
+ }
194
+ ```
195
+
196
+ </details>