gridzen-developer-kit 0.3.0__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.
- gridzen_developer/__init__.py +2 -0
- gridzen_developer/cli.py +19 -0
- gridzen_developer/client.py +19 -0
- gridzen_developer/core.py +58 -0
- gridzen_developer/data/catalog.json +1 -0
- gridzen_developer/mcp.py +54 -0
- gridzen_developer/remote.py +43 -0
- gridzen_developer/server.py +60 -0
- gridzen_developer/skills.py +9 -0
- gridzen_developer/usage.py +47 -0
- gridzen_developer/web/developer.css +2 -0
- gridzen_developer/web/developer.js +33 -0
- gridzen_developer/web/icon.png +0 -0
- gridzen_developer/web/icon.svg +9 -0
- gridzen_developer/web/index.html +3 -0
- gridzen_developer/web/language.js +1 -0
- gridzen_developer/web/locales.js +369 -0
- gridzen_developer/web/privacy.html +1 -0
- gridzen_developer/web/quickstart.html +14 -0
- gridzen_developer/web/support.html +1 -0
- gridzen_developer/web/terms.html +1 -0
- gridzen_developer_kit-0.3.0.data/data/share/gridzen-developer-kit/skills/gridzen-explain-verification/SKILL.md +15 -0
- gridzen_developer_kit-0.3.0.data/data/share/gridzen-developer-kit/skills/gridzen-integrate-sandbox/SKILL.md +12 -0
- gridzen_developer_kit-0.3.0.data/data/share/gridzen-developer-kit/skills/gridzen-integrate-sandbox/references/api.md +23 -0
- gridzen_developer_kit-0.3.0.data/data/share/gridzen-developer-kit/skills/gridzen-select-verification/SKILL.md +12 -0
- gridzen_developer_kit-0.3.0.data/data/share/gridzen-developer-kit/skills/mexico-pilot-scoper/LICENSE +9 -0
- gridzen_developer_kit-0.3.0.data/data/share/gridzen-developer-kit/skills/mexico-pilot-scoper/README.md +22 -0
- gridzen_developer_kit-0.3.0.data/data/share/gridzen-developer-kit/skills/mexico-pilot-scoper/SKILL.md +15 -0
- gridzen_developer_kit-0.3.0.data/data/share/gridzen-developer-kit/skills/mexico-pilot-scoper/agent-evals.json +39 -0
- gridzen_developer_kit-0.3.0.data/data/share/gridzen-developer-kit/skills/mexico-pilot-scoper/evaluate.py +62 -0
- gridzen_developer_kit-0.3.0.data/data/share/gridzen-developer-kit/skills/mexico-pilot-scoper/example-input.json +9 -0
- gridzen_developer_kit-0.3.0.data/data/share/gridzen-developer-kit/skills/mexico-pilot-scoper/examples/missing-evidence.json +24 -0
- gridzen_developer_kit-0.3.0.data/data/share/gridzen-developer-kit/skills/mexico-pilot-scoper/examples/normal.json +21 -0
- gridzen_developer_kit-0.3.0.data/data/share/gridzen-developer-kit/skills/mexico-pilot-scoper/examples/out-of-scope.json +21 -0
- gridzen_developer_kit-0.3.0.data/data/share/gridzen-developer-kit/skills/mexico-pilot-scoper/spec.json +13 -0
- gridzen_developer_kit-0.3.0.data/data/share/gridzen-developer-kit/skills/mexico-pilot-scoper/test_evaluate.py +35 -0
- gridzen_developer_kit-0.3.0.data/data/share/gridzen-developer-kit/skills/payout-policy-designer/LICENSE +9 -0
- gridzen_developer_kit-0.3.0.data/data/share/gridzen-developer-kit/skills/payout-policy-designer/README.md +22 -0
- gridzen_developer_kit-0.3.0.data/data/share/gridzen-developer-kit/skills/payout-policy-designer/SKILL.md +15 -0
- gridzen_developer_kit-0.3.0.data/data/share/gridzen-developer-kit/skills/payout-policy-designer/agent-evals.json +39 -0
- gridzen_developer_kit-0.3.0.data/data/share/gridzen-developer-kit/skills/payout-policy-designer/evaluate.py +62 -0
- gridzen_developer_kit-0.3.0.data/data/share/gridzen-developer-kit/skills/payout-policy-designer/example-input.json +9 -0
- gridzen_developer_kit-0.3.0.data/data/share/gridzen-developer-kit/skills/payout-policy-designer/examples/missing-evidence.json +30 -0
- gridzen_developer_kit-0.3.0.data/data/share/gridzen-developer-kit/skills/payout-policy-designer/examples/normal.json +34 -0
- gridzen_developer_kit-0.3.0.data/data/share/gridzen-developer-kit/skills/payout-policy-designer/examples/out-of-scope.json +21 -0
- gridzen_developer_kit-0.3.0.data/data/share/gridzen-developer-kit/skills/payout-policy-designer/spec.json +11 -0
- gridzen_developer_kit-0.3.0.data/data/share/gridzen-developer-kit/skills/payout-policy-designer/test_evaluate.py +35 -0
- gridzen_developer_kit-0.3.0.data/data/share/gridzen-developer-kit/skills/provider-rights-readiness/LICENSE +9 -0
- gridzen_developer_kit-0.3.0.data/data/share/gridzen-developer-kit/skills/provider-rights-readiness/README.md +22 -0
- gridzen_developer_kit-0.3.0.data/data/share/gridzen-developer-kit/skills/provider-rights-readiness/SKILL.md +15 -0
- gridzen_developer_kit-0.3.0.data/data/share/gridzen-developer-kit/skills/provider-rights-readiness/agent-evals.json +39 -0
- gridzen_developer_kit-0.3.0.data/data/share/gridzen-developer-kit/skills/provider-rights-readiness/evaluate.py +62 -0
- gridzen_developer_kit-0.3.0.data/data/share/gridzen-developer-kit/skills/provider-rights-readiness/example-input.json +11 -0
- gridzen_developer_kit-0.3.0.data/data/share/gridzen-developer-kit/skills/provider-rights-readiness/examples/missing-evidence.json +23 -0
- gridzen_developer_kit-0.3.0.data/data/share/gridzen-developer-kit/skills/provider-rights-readiness/examples/normal.json +23 -0
- gridzen_developer_kit-0.3.0.data/data/share/gridzen-developer-kit/skills/provider-rights-readiness/examples/out-of-scope.json +23 -0
- gridzen_developer_kit-0.3.0.data/data/share/gridzen-developer-kit/skills/provider-rights-readiness/spec.json +15 -0
- gridzen_developer_kit-0.3.0.data/data/share/gridzen-developer-kit/skills/provider-rights-readiness/test_evaluate.py +35 -0
- gridzen_developer_kit-0.3.0.dist-info/METADATA +155 -0
- gridzen_developer_kit-0.3.0.dist-info/RECORD +65 -0
- gridzen_developer_kit-0.3.0.dist-info/WHEEL +5 -0
- gridzen_developer_kit-0.3.0.dist-info/entry_points.txt +3 -0
- gridzen_developer_kit-0.3.0.dist-info/licenses/LICENSE +21 -0
- gridzen_developer_kit-0.3.0.dist-info/licenses/NOTICE.md +16 -0
- gridzen_developer_kit-0.3.0.dist-info/top_level.txt +1 -0
gridzen_developer/mcp.py
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
"""Local, offline MCP: no credentials, no external calls, no real verification."""
|
|
2
|
+
from typing import Any
|
|
3
|
+
from inspect import signature
|
|
4
|
+
from mcp.server import MCPServer
|
|
5
|
+
from mcp.shared.exceptions import MCPError
|
|
6
|
+
from mcp.types import ToolAnnotations
|
|
7
|
+
from . import core
|
|
8
|
+
|
|
9
|
+
async def strict_arguments(ctx, call_next):
|
|
10
|
+
"""Reject unknown/personal fields before SDK coercion; never echo their values."""
|
|
11
|
+
if ctx.method == 'tools/call' and isinstance(ctx.params, dict):
|
|
12
|
+
name=ctx.params.get('name'); arguments=ctx.params.get('arguments', {})
|
|
13
|
+
fn=TOOLS.get(name) if isinstance(name,str) else None
|
|
14
|
+
if fn:
|
|
15
|
+
allowed=signature(fn).parameters
|
|
16
|
+
if not isinstance(arguments,dict) or set(arguments)-set(allowed):
|
|
17
|
+
raise MCPError(code=-32602,message='Only documented sandbox/research arguments are accepted; do not send personal data.')
|
|
18
|
+
for key,value in arguments.items():
|
|
19
|
+
if value is None and allowed[key].default is None:continue
|
|
20
|
+
if not isinstance(value,str) or len(value)>160:
|
|
21
|
+
raise MCPError(code=-32602,message='Invalid argument type or length.')
|
|
22
|
+
return await call_next(ctx)
|
|
23
|
+
|
|
24
|
+
class GridzenMCP(MCPServer):
|
|
25
|
+
async def list_tools(self):
|
|
26
|
+
tools=await super().list_tools()
|
|
27
|
+
for tool in tools:tool.input_schema['additionalProperties']=False
|
|
28
|
+
return tools
|
|
29
|
+
|
|
30
|
+
server=GridzenMCP('Gridzen Developer Kit',version='0.3.0',website_url='https://gridzen.ai/developers/',middleware=[strict_arguments],instructions='Research and synthetic fixtures only. No live providers are enabled. Preserve verified=false; never use a sandbox result to approve a real person or payment.')
|
|
31
|
+
read=ToolAnnotations(readOnlyHint=True,destructiveHint=False,idempotentHint=True,openWorldHint=False)
|
|
32
|
+
@server.tool(annotations=read,structured_output=True)
|
|
33
|
+
def get_coverage(country: str | None=None, capability: str | None=None) -> dict[str, Any]:
|
|
34
|
+
"""List country research or inspect one ISO-2 country's capability/access evidence; not live coverage."""
|
|
35
|
+
return core.coverage(country,capability)
|
|
36
|
+
@server.tool(annotations=read,structured_output=True)
|
|
37
|
+
def plan_verification(country: str, event: str='payout') -> dict[str, Any]:
|
|
38
|
+
"""Plan onboarding, payout or account_change; returns research requirements and sandbox example."""
|
|
39
|
+
return core.plan(country,event)
|
|
40
|
+
@server.tool(annotations=read,structured_output=True)
|
|
41
|
+
def create_sandbox_verification(country: str, capability: str, scenario: str='match') -> dict[str, Any]:
|
|
42
|
+
"""Return a synthetic match/mismatch/not_found/timeout/unsupported fixture. Never provide personal data."""
|
|
43
|
+
return core.simulate(country,capability,scenario)
|
|
44
|
+
@server.tool(annotations=read,structured_output=True)
|
|
45
|
+
def get_sandbox_verification(identifier: str) -> dict[str, Any]:
|
|
46
|
+
"""Retrieve a deterministic sim_v1 fixture, not a real transaction."""
|
|
47
|
+
return core.get_simulation(identifier)
|
|
48
|
+
@server.tool(annotations=read,structured_output=True)
|
|
49
|
+
def explain_result(reason_code: str) -> dict[str, Any]:
|
|
50
|
+
"""Explain a sandbox reason code; missing data and timeout remain inconclusive."""
|
|
51
|
+
return core.explain(reason_code)
|
|
52
|
+
TOOLS={fn.__name__:fn for fn in (get_coverage,plan_verification,create_sandbox_verification,get_sandbox_verification,explain_result)}
|
|
53
|
+
def main():server.run(transport='stdio')
|
|
54
|
+
if __name__=='__main__':main()
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
"""Bounded, stateless public MCP transport. No accounts or providers; optional aggregate counts only."""
|
|
2
|
+
from collections import OrderedDict
|
|
3
|
+
from time import monotonic
|
|
4
|
+
from starlette.responses import JSONResponse
|
|
5
|
+
from mcp.server.transport_security import TransportSecuritySettings
|
|
6
|
+
from .mcp import server
|
|
7
|
+
|
|
8
|
+
def make_transport():
|
|
9
|
+
from .usage import aggregate_calls
|
|
10
|
+
if aggregate_calls not in server.middleware:
|
|
11
|
+
server.middleware.append(aggregate_calls)
|
|
12
|
+
return server.streamable_http_app(
|
|
13
|
+
streamable_http_path='/mcp',json_response=True,stateless_http=True,
|
|
14
|
+
max_request_body_size=4096,
|
|
15
|
+
transport_security=TransportSecuritySettings(
|
|
16
|
+
allowed_hosts=['gridzen.ai','127.0.0.1:*','localhost:*','testserver'],
|
|
17
|
+
allowed_origins=['https://gridzen.ai','http://127.0.0.1:*','http://localhost:*'],
|
|
18
|
+
),
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
class MCPBounds:
|
|
22
|
+
"""One process-wide and one per-client fixed window; no payload logging.
|
|
23
|
+
|
|
24
|
+
The reverse proxy overwrites X-Real-IP. The service binds to loopback only.
|
|
25
|
+
Buckets are in memory, bounded to 1024 clients, and reset after 60 seconds.
|
|
26
|
+
"""
|
|
27
|
+
def __init__(self,app,per_client=120,total=600,clock=monotonic):
|
|
28
|
+
self.app=app;self.per_client=per_client;self.total=total;self.clock=clock
|
|
29
|
+
self.started=clock();self.count=0;self.clients=OrderedDict()
|
|
30
|
+
|
|
31
|
+
async def __call__(self,scope,receive,send):
|
|
32
|
+
if scope['type']!='http' or scope['path']!='/mcp':
|
|
33
|
+
return await self.app(scope,receive,send)
|
|
34
|
+
now=self.clock()
|
|
35
|
+
if now-self.started>=60:
|
|
36
|
+
self.started=now;self.count=0;self.clients.clear()
|
|
37
|
+
headers=dict(scope.get('headers',[]))
|
|
38
|
+
client=headers.get(b'x-real-ip',str(scope.get('client',('unknown',))[0]).encode())
|
|
39
|
+
count=self.clients.get(client,0)
|
|
40
|
+
if self.count>=self.total or count>=self.per_client or (client not in self.clients and len(self.clients)>=1024):
|
|
41
|
+
return await JSONResponse({'error':'RATE_LIMITED'},status_code=429,headers={'Retry-After':'60','Cache-Control':'no-store'})(scope,receive,send)
|
|
42
|
+
self.count+=1;self.clients[client]=count+1
|
|
43
|
+
return await self.app(scope,receive,send)
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
from contextlib import asynccontextmanager
|
|
3
|
+
from typing import Literal
|
|
4
|
+
from fastapi import FastAPI, Query, Request
|
|
5
|
+
from fastapi.exceptions import RequestValidationError
|
|
6
|
+
from fastapi.responses import JSONResponse, RedirectResponse
|
|
7
|
+
from fastapi.staticfiles import StaticFiles
|
|
8
|
+
from pydantic import BaseModel, ConfigDict, Field
|
|
9
|
+
from . import core
|
|
10
|
+
from .remote import make_transport, MCPBounds
|
|
11
|
+
from .mcp import server as mcp_server
|
|
12
|
+
|
|
13
|
+
mcp_app=make_transport()
|
|
14
|
+
@asynccontextmanager
|
|
15
|
+
async def lifespan(_app):
|
|
16
|
+
async with mcp_app.router.lifespan_context(mcp_app):yield
|
|
17
|
+
|
|
18
|
+
app=FastAPI(title='Gridzen Developer Sandbox',version='0.3.0',lifespan=lifespan,docs_url=None,redoc_url=None,openapi_url='/api/openapi.json',servers=[{'url':'/developers'}])
|
|
19
|
+
app.add_middleware(MCPBounds)
|
|
20
|
+
class PlanRequest(BaseModel):
|
|
21
|
+
model_config=ConfigDict(extra='forbid',strict=True)
|
|
22
|
+
country: str=Field(min_length=2,max_length=2,pattern=r'^[A-Za-z]{2}$')
|
|
23
|
+
event: Literal['onboarding','payout','account_change']='payout'
|
|
24
|
+
class SandboxRequest(BaseModel):
|
|
25
|
+
model_config=ConfigDict(extra='forbid',strict=True)
|
|
26
|
+
country: str=Field(min_length=2,max_length=2,pattern=r'^[A-Za-z]{2}$')
|
|
27
|
+
capability: str=Field(max_length=40)
|
|
28
|
+
scenario: Literal['match','mismatch','not_found','timeout','unsupported']='match'
|
|
29
|
+
@app.exception_handler(ValueError)
|
|
30
|
+
async def invalid(_r,exc):return JSONResponse({'error':str(exc)},status_code=400)
|
|
31
|
+
@app.exception_handler(RequestValidationError)
|
|
32
|
+
async def invalid_schema(_r,_exc):return JSONResponse({'error':'INVALID_INPUT','message':'Only documented country, capability, event and synthetic scenario fields are accepted.'},status_code=422)
|
|
33
|
+
@app.middleware('http')
|
|
34
|
+
async def bounds(request:Request,call_next):
|
|
35
|
+
if request.method=='POST':
|
|
36
|
+
body=await request.body()
|
|
37
|
+
if len(body)>4096:return JSONResponse({'error':'REQUEST_TOO_LARGE'},status_code=413)
|
|
38
|
+
response=await call_next(request)
|
|
39
|
+
response.headers['X-Content-Type-Options']='nosniff'
|
|
40
|
+
response.headers['Referrer-Policy']='no-referrer'
|
|
41
|
+
response.headers['Content-Security-Policy']="default-src 'self'; script-src 'self'; style-src 'self'; img-src 'self' data:; connect-src 'self'; frame-ancestors 'none'; base-uri 'none'"
|
|
42
|
+
response.headers['Cache-Control']='no-store' if request.url.path.startswith(('/api','/mcp')) else 'public, max-age=60'
|
|
43
|
+
return response
|
|
44
|
+
@app.get('/health')
|
|
45
|
+
def health():return {'status':'ok','mode':'sandbox','live_routes':0,'version':'0.3.0','mcp_transport':'streamable-http'}
|
|
46
|
+
@app.get('/server-card.json')
|
|
47
|
+
async def server_card():
|
|
48
|
+
return {'serverInfo':{'name':'Gridzen Verification','version':'0.3.0'},'authentication':{'required':False},'tools':[tool.model_dump(by_alias=True,exclude_none=True) for tool in await mcp_server.list_tools()],'resources':[],'prompts':[]}
|
|
49
|
+
@app.get('/api/coverage')
|
|
50
|
+
def coverage(country:str|None=Query(None,max_length=2),capability:str|None=Query(None,max_length=40)):return core.coverage(country,capability)
|
|
51
|
+
@app.post('/api/plan')
|
|
52
|
+
def plan(request:PlanRequest):return core.plan(request.country,request.event)
|
|
53
|
+
@app.post('/api/sandbox/verifications')
|
|
54
|
+
def simulate(request:SandboxRequest):return core.simulate(request.country,request.capability,request.scenario)
|
|
55
|
+
@app.get('/api/sandbox/verifications/{identifier}')
|
|
56
|
+
def get_simulation(identifier:str):return core.get_simulation(identifier)
|
|
57
|
+
@app.get('/api/explain/{reason_code}')
|
|
58
|
+
def explain(reason_code:str):return core.explain(reason_code)
|
|
59
|
+
app.router.routes.extend(mcp_app.routes)
|
|
60
|
+
app.mount('/',StaticFiles(directory=Path(__file__).parent/'web',html=True),name='developer-web')
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
"""Discover installed Skill assets without auto-activating an assistant."""
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
import sysconfig
|
|
4
|
+
|
|
5
|
+
def list_skills():
|
|
6
|
+
installed = Path(sysconfig.get_path('data')) / 'share/gridzen-developer-kit/skills'
|
|
7
|
+
source = Path(__file__).resolve().parents[2] / 'skills'
|
|
8
|
+
root = installed if installed.is_dir() else source
|
|
9
|
+
return [{'name':p.parent.name,'path':str(p.parent),'instructions':str(p)} for p in sorted(root.glob('*/SKILL.md'))]
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
"""Remote-only, fail-open UTC aggregates. No request fields or identifiers."""
|
|
2
|
+
import asyncio
|
|
3
|
+
import os
|
|
4
|
+
import sqlite3
|
|
5
|
+
from datetime import date, datetime, timedelta, timezone
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
TOOLS = frozenset(('get_coverage', 'plan_verification', 'create_sandbox_verification', 'get_sandbox_verification', 'explain_result'))
|
|
9
|
+
RETENTION_DAYS = 90
|
|
10
|
+
|
|
11
|
+
def record(path, tool, day=None):
|
|
12
|
+
if tool not in TOOLS:
|
|
13
|
+
return
|
|
14
|
+
day = day or datetime.now(timezone.utc).date()
|
|
15
|
+
path = Path(path)
|
|
16
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
17
|
+
with sqlite3.connect(path, timeout=5) as db:
|
|
18
|
+
db.execute('CREATE TABLE IF NOT EXISTS calls (day TEXT NOT NULL, tool TEXT NOT NULL, count INTEGER NOT NULL, PRIMARY KEY(day, tool))')
|
|
19
|
+
db.execute('DELETE FROM calls WHERE day < ?', ((day - timedelta(days=RETENTION_DAYS - 1)).isoformat(),))
|
|
20
|
+
db.execute('INSERT INTO calls VALUES (?, ?, 1) ON CONFLICT(day, tool) DO UPDATE SET count=count+1', (day.isoformat(), tool))
|
|
21
|
+
|
|
22
|
+
def summarize(path, end=None):
|
|
23
|
+
end = end or datetime.now(timezone.utc).date()
|
|
24
|
+
start = end - timedelta(days=6)
|
|
25
|
+
result = {'status': 'unavailable', 'start': start.isoformat(), 'end': end.isoformat(), 'timezone': 'UTC', 'calls': None, 'by_tool': {}}
|
|
26
|
+
if not path or not Path(path).is_file():
|
|
27
|
+
return result
|
|
28
|
+
try:
|
|
29
|
+
with sqlite3.connect(Path(path).resolve().as_uri() + '?mode=ro', uri=True) as db:
|
|
30
|
+
rows = db.execute('SELECT tool, SUM(count) FROM calls WHERE day BETWEEN ? AND ? GROUP BY tool', (start.isoformat(), end.isoformat())).fetchall()
|
|
31
|
+
result.update(status='available', calls=sum(n for _, n in rows), by_tool=dict(rows))
|
|
32
|
+
except sqlite3.Error:
|
|
33
|
+
pass
|
|
34
|
+
return result
|
|
35
|
+
|
|
36
|
+
async def aggregate_calls(ctx, call_next):
|
|
37
|
+
result = await call_next(ctx)
|
|
38
|
+
if ctx.method == 'tools/call' and isinstance(ctx.params, dict):
|
|
39
|
+
tool = ctx.params.get('name')
|
|
40
|
+
path = os.environ.get('GRIDZEN_MCP_EVENTS_DB')
|
|
41
|
+
if path and isinstance(tool, str) and tool in TOOLS and not getattr(result, 'is_error', False):
|
|
42
|
+
try:
|
|
43
|
+
await asyncio.to_thread(record, path, tool)
|
|
44
|
+
except (OSError, sqlite3.Error):
|
|
45
|
+
# Aggregate availability must never change tool behavior.
|
|
46
|
+
pass
|
|
47
|
+
return result
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
.developer{max-width:1180px;margin:auto;padding:0 30px}.developer header{padding:90px 0 35px}.developer h1{font-size:clamp(40px,6vw,72px);line-height:1.05;letter-spacing:-.05em;font-weight:550}.developer .boundary{color:var(--muted);font-size:13px;margin-top:22px}.developer section{padding:48px 0}.workspace{display:grid;grid-template-columns:minmax(230px,1fr) minmax(0,2fr);gap:20px}.controls{display:flex;flex-direction:column;gap:12px}.developer label{font-size:13px;color:var(--muted)}.developer select{width:100%;padding:12px;background:var(--paper);border:1px solid var(--line);color:var(--ink);border-radius:6px}.developer .card{padding:24px;min-width:0}.output-head{display:flex;justify-content:space-between;gap:16px;flex-wrap:wrap}.tag{font-size:10px;color:var(--mint);letter-spacing:.05em}.developer pre{font:12px/1.6 ui-monospace,SFMono-Regular,monospace;background:#0b0c10;border:1px solid var(--line);padding:20px;border-radius:8px;overflow:auto;max-width:100%;white-space:pre-wrap;overflow-wrap:anywhere}.output pre{height:380px}.developer button{cursor:pointer}.developer button:disabled{opacity:.5;cursor:wait}.developer #sources{color:var(--muted);font-size:13px;overflow-wrap:anywhere;padding:14px 0}.developer .next{border-top:1px solid var(--line)}.developer code{overflow-wrap:anywhere}.developer .actions{flex-wrap:wrap}.developer .grid{min-width:0}.developer .card p{font-size:14px}.developer #copy-status{font-size:12px;color:var(--mint)}@media(max-width:760px){.developer{padding:0 20px}.developer header{padding-top:55px}.workspace{grid-template-columns:1fr}.developer .grid.three{grid-template-columns:1fr}.output pre{height:330px}.developer .actions a{white-space:normal}}
|
|
2
|
+
.developer #summary{margin:20px 0}.result-line{margin:14px 0}.result-line dt{font-size:12px;color:var(--muted);margin-bottom:4px}.result-line dd{margin:0;font-size:14px;line-height:1.65}.developer details>summary{cursor:pointer;color:var(--muted);font-size:13px}.developer #raw>p{font-size:12px;color:var(--muted)}.developer #raw pre{height:auto;max-height:380px}.developer #sources details{margin:10px 0}.developer #sources details>p{font-size:13px}.developer .output-head{align-items:flex-start}.developer .output-head #status{flex:1;min-width:150px}.developer .tag{white-space:normal}.developer .controls select{white-space:normal}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
(()=>{
|
|
2
|
+
'use strict';
|
|
3
|
+
const $=id=>document.getElementById(id),api='/developers/api',L=window.GridzenLanguage;
|
|
4
|
+
const caps={payout:'bank_account_match',onboarding:'commercial_identity',account_change:'phone_identity'};
|
|
5
|
+
let result=null,countries=[],kind=null,pending=false,failed=false,copyKey='',sequence=0;
|
|
6
|
+
const node=(tag,text)=>{const n=document.createElement(tag);n.textContent=text;return n};
|
|
7
|
+
function countriesView(){if(!countries.length)return;const selected=$('country').value||'ID';$('country').replaceChildren();const rows=[...countries].sort((a,b)=>L.region(a.code).localeCompare(L.region(b.code),L.lang()));for(const c of rows){const option=node('option',c.code+' · '+L.region(c.code));option.value=c.code;$('country').append(option)}$('country').value=selected;}
|
|
8
|
+
function line(label,value){const row=node('dl','');row.className='result-line';row.append(node('dt',label),node('dd',value));$('summary').append(row)}
|
|
9
|
+
function render(){
|
|
10
|
+
$('sources').replaceChildren();$('summary').replaceChildren();$('copy-status').textContent=copyKey?L.t(copyKey):'';
|
|
11
|
+
$('status').textContent=L.t(pending?'dev.loading':failed?'dev.error':!result?'dev.choose':kind==='plan'?'dev.plan-status':'dev.sim-status',result?{scenario:L.t('dev.'+result.fixture_outcome)}:{});
|
|
12
|
+
if(!result){$('result').textContent=L.t(failed?'dev.no-result':'dev.no-provider');return}
|
|
13
|
+
$('result').textContent=JSON.stringify(result,null,2);
|
|
14
|
+
line(L.t('dev.country'),L.region(result.country)+' · '+result.country);
|
|
15
|
+
line(L.t('dev.capability'),L.t('cap.'+result.capability));
|
|
16
|
+
if(kind==='plan'){
|
|
17
|
+
line(L.t('dev.date'),result.research_date);
|
|
18
|
+
line(L.t('dev.evidence'),L.t(result.evidence_status==='RESEARCH_EVIDENCE'?'dev.found':'dev.unconfirmed'));
|
|
19
|
+
line(L.t('dev.next'),L.t('dev.plan-next'));
|
|
20
|
+
for(const service of result.services||[]){const details=node('details',''),heading=node('summary',L.t('dev.source-note'));details.append(heading,node('p',service.name));for(const url of service.source_urls){let u;try{u=new URL(url)}catch{continue}if(u.protocol!=='https:'||u.username||u.password)continue;const link=node('a',L.t('dev.source')+' · '+u.hostname);link.href=u.href;link.target='_blank';link.rel='noopener noreferrer';const p=node('p','');p.append(link);details.append(p)}$('sources').append(details)}
|
|
21
|
+
}else{
|
|
22
|
+
line(L.t('dev.outcome'),L.t('meaning.'+result.fixture_outcome));
|
|
23
|
+
line(L.t('dev.next'),L.t('next.'+result.fixture_outcome));
|
|
24
|
+
$('summary').append(node('p',L.t('dev.sim-note')));
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
async function request(path,payload){const r=await fetch(api+path,{method:payload?'POST':'GET',headers:{'Content-Type':'application/json'},body:payload?JSON.stringify(payload):undefined,signal:AbortSignal.timeout(12000)});if(!r.ok)throw Error('request failed');return r.json()}
|
|
28
|
+
async function act(action){const current=++sequence;kind=action;pending=true;failed=false;result=null;copyKey='';$('plan').disabled=$('run').disabled=true;render();try{const data=action==='plan'?await request('/plan',{country:$('country').value,event:$('event').value}):await request('/sandbox/verifications',{country:$('country').value,capability:caps[$('event').value],scenario:$('scenario').value});if(current!==sequence)return;result=data}catch{if(current!==sequence)return;failed=true}finally{if(current===sequence){pending=false;$('plan').disabled=$('run').disabled=false;render()}}}
|
|
29
|
+
$('plan').onclick=()=>act('plan');$('run').onclick=()=>act('run');
|
|
30
|
+
$('copy').onclick=async()=>{if(!result)return;try{await navigator.clipboard.writeText(JSON.stringify(result,null,2));copyKey='dev.copied'}catch{copyKey='dev.copy-failed'}render()};
|
|
31
|
+
window.addEventListener('gridzen:language',()=>{countriesView();render()});
|
|
32
|
+
request('/coverage').then(data=>{countries=data.countries;countriesView();act('plan')}).catch(()=>{failed=true;render()});
|
|
33
|
+
})();
|
|
Binary file
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36">
|
|
2
|
+
<defs>
|
|
3
|
+
<linearGradient id="metal" x1="8" y1="9" x2="28" y2="29" gradientUnits="userSpaceOnUse"><stop stop-color="#faf8ff"/><stop offset=".45" stop-color="#d6cfee"/><stop offset="1" stop-color="#999ac5"/></linearGradient>
|
|
4
|
+
<pattern id="lines" width="4" height="4" patternUnits="userSpaceOnUse" patternTransform="rotate(45)"><rect width="1" height="4" fill="#17151f" fill-opacity=".35"/></pattern>
|
|
5
|
+
</defs>
|
|
6
|
+
<rect width="36" height="36" rx="9" fill="#121218"/>
|
|
7
|
+
<path d="M25 9H14L8 15V24L14 29H28V18H18V22H23V25H16L12 22V17L16 13H25Z" fill="url(#metal)"/>
|
|
8
|
+
<path d="M25 9H14L8 15V24L14 29H28V18H18V22H23V25H16L12 22V17L16 13H25Z" fill="url(#lines)"/>
|
|
9
|
+
</svg>
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
<!doctype html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>Developer tools · GridZen</title><meta name="description" content="Explore global identity and account verification research, plan an integration, and test synthetic outcomes. API, MCP and Skills."><link rel="icon" href="/favicon.svg"><link rel="stylesheet" href="/assets/site.css"><link rel="stylesheet" href="/developers/developer.css?v=0.3.0"></head><body data-page="developers"><div id="gz-nav"></div><main class="wrap developer"><header><span class="eyebrow" data-i18n="dev.badge">GridZen for developers · Preview</span><h1 data-i18n="dev.heading">Build verification into<br>your next product.</h1><p class="lead" data-i18n="dev.lead">Find country-specific requirements. Plan your integration.<br>Test the happy path and the failures before going live.</p><div class="actions"><a class="button primary" href="#playground" data-i18n="dev.try">Try the sandbox</a><a class="button" href="#install" data-i18n="dev.get">Get MCP & Skills</a></div><p class="boundary" data-i18n="dev.boundary">Research: 198 countries / territories · Sandbox: synthetic results · Live routes: 0</p></header><section id="playground"><div class="section-title"><h2 data-i18n="dev.start">Start with your use case.</h2><p data-i18n="dev.research">Research shows what a source documents. It does not mean GridZen has commercial access to that service.</p></div><div class="workspace"><div class="card controls"><label for="country" data-i18n="dev.country">Country / territory</label><select id="country"><option value="ID" data-i18n="dev.loading">Loading…</option></select><label for="event" data-i18n="dev.event">Use case</label><select id="event"><option value="payout" data-i18n="dev.payout">Payout / account ownership</option><option value="onboarding" data-i18n="dev.onboarding">Onboarding / identity</option><option value="account_change" data-i18n="dev.account_change">Account change / phone</option></select><button class="button" id="plan" data-i18n="dev.plan">Show integration plan</button><label for="scenario" data-i18n="dev.scenario">Synthetic test scenario</label><select id="scenario"><option value="match" data-i18n="dev.match">Match</option><option value="mismatch" data-i18n="dev.mismatch">Mismatch</option><option value="not_found" data-i18n="dev.not_found">No record</option><option value="timeout" data-i18n="dev.timeout">Provider timeout</option><option value="unsupported" data-i18n="dev.unsupported">Unsupported route</option></select><button class="button primary" id="run" data-i18n="dev.run">Run sandbox test</button><p data-i18n="dev.no-data">No personal details or credentials needed.</p></div><div class="card output"><div class="output-head"><span id="status" role="status" data-i18n="dev.choose">Choose a country to begin</span><span class="tag" data-i18n="dev.tag">SANDBOX / RESEARCH</span></div><div id="summary" aria-live="polite"></div><details id="raw"><summary data-i18n="dev.raw">Raw API response</summary><p data-i18n="dev.raw-note">API keys and codes remain unchanged. Research notes retain their source language.</p><pre id="result">No real provider is called.</pre><button class="button" id="copy" data-i18n="dev.copy">Copy JSON</button><p id="copy-status" role="status"></p></details></div></div><div id="sources"></div></section><section id="install"><div class="section-title"><h2 data-i18n="dev.use">Use it where you build.</h2><p data-i18n="dev.three">Six task-focused Skills and a local MCP server. The local tools work offline after installation.</p></div><div class="grid three"><article class="card"><h3 data-i18n="dev.select">Choose a route</h3><p data-i18n="dev.select-desc">Compare research evidence, required inputs, user participation and access limits.</p></article><article class="card"><h3 data-i18n="dev.integrate">Integrate & test</h3><p data-i18n="dev.integrate-desc">Add a sandbox flow to your app with an HTTP API, Python SDK or Next.js example.</p></article><article class="card"><h3 data-i18n="dev.explain">Explain a result</h3><p data-i18n="dev.explain-desc">Keep missing data and provider failures distinct from a mismatch.</p></article><article class="card"><h3 data-i18n="dev.scope">Mexico pilot scope</h3><p data-i18n="dev.scope-desc">Draft owners, permissions and non-production limits; no pilot activation.</p></article><article class="card"><h3 data-i18n="dev.policy">Payout policy draft</h3><p data-i18n="dev.policy-desc">Design missing-evidence and provider-failure handling; no payment authorization.</p></article><article class="card"><h3 data-i18n="dev.rights">Provider rights checklist</h3><p data-i18n="dev.rights-desc">Identify contractual evidence gaps; no legal or compliance conclusion.</p></article></div><div class="actions"><a class="button primary" href="/developers/downloads/gridzen-developer-kit.zip" data-i18n="dev.download">Download developer kit</a><a class="button" href="/developers/downloads/gridzen-skills.zip" data-i18n="dev.download-skills">Download 6 Skills</a><a class="button" href="/developers/quickstart.html" data-i18n="dev.guide">Installation & API guide →</a><a class="button" href="/developers/api/openapi.json">OpenAPI</a></div><pre>python -m pip install './gridzen-developer-kit[mcp]'
|
|
2
|
+
gridzen plan --country ID --event payout
|
|
3
|
+
gridzen simulate --country ID --capability bank_account_match --scenario timeout</pre><p class="boundary" data-i18n="dev.install-note">Unzip the developer kit first. Python 3.11+ required. MCP launch command: <code>gridzen-mcp</code>.</p></section><section class="next"><h2 data-i18n="dev.pilot">Have a real verification use case?</h2><p data-i18n="dev.pilot-desc">Tell us your country, verification type and expected volume. We’ll evaluate an authorized route for your pilot.</p><a class="button" href="mailto:open@gridzen.ai?subject=Gridzen%20developer%20pilot" data-i18n="dev.discuss">Discuss a pilot</a></section></main><div id="gz-foot"></div><script src="/assets/i18n.js"></script><script src="/developers/locales.js?v=0.3.0"></script><script src="/assets/layout.js"></script><script src="/developers/language.js?v=0.3.0"></script><script src="/developers/developer.js?v=0.3.0"></script></body></html>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
(()=>{'use strict';const supported=['en','zh','es'];function lang(){const v=window.gzGetLang?.();return supported.includes(v)?v:'en'}function t(key,values={}){let text=window.GZ_DEVELOPER_LOCALES[lang()][key]||window.GZ_DEVELOPER_LOCALES.en[key]||key;for(const [k,v] of Object.entries(values))text=text.replaceAll('{'+k+'}',v);return text}function region(code){try{return new Intl.DisplayNames([lang()==='zh'?'zh-CN':lang()],{type:'region'}).of(code)||code}catch{return code}}function update(){document.title=t(document.body.dataset.titleKey||(document.body.dataset.page==='developer-quickstart'?'guide.title':'dev.title'));const meta=document.querySelector('meta[name="description"]');if(meta)meta.content=t('dev.meta');document.documentElement.lang=lang()==='zh'?'zh-CN':lang();window.dispatchEvent(new Event('gridzen:language'))}window.GridzenLanguage={lang,t,region};window.gzOnLangChange(update);update()})();
|