Loading the notebook…
Loading the notebook…
Notebooks · Specialist agents · 48
End-to-end workflow:
Its imports and arguments are checked against shipit-agent 3.0. The outputs were saved when it was written, for an earlier release.
from pathlib import Path
def _find_notebooks_dir() -> Path:
cwd = Path.cwd().resolve()
if cwd.name == 'notebooks':
return cwd
candidate = cwd / 'notebooks'
return candidate if candidate.is_dir() else cwd
WORKSPACE = _find_notebooks_dir() / '_designer_workspace'
WORKSPACE.mkdir(parents=True, exist_ok=True)
print('workspace:', WORKSPACE)
Defaults to SimpleEchoLLM. Swap in a vision-capable LLM (Claude, GPT-4o, Bedrock Claude) to get real image analysis in section 4.
# from shipit_agent.llms import build_llm_from_settings
# Bedrock Claude (vision-capable):
# llm = build_llm_from_settings({
# 'provider': 'bedrock',
# 'model': 'bedrock/anthropic.claude-sonnet-4-5-v2:0',
# }, provider='bedrock')
# LiteLLM direct (GPT-4o is vision-capable):
# llm = build_llm_from_settings({
# 'provider': 'litellm', 'model': 'openai/gpt-4o',
# }, provider='litellm')
# LiteLLM proxy:
# from shipit_agent.llms import LiteLLMProxyChatLLM
# llm = LiteLLMProxyChatLLM(model='gpt-4o', api_base='https://litellm.internal', api_key='sk-...')
from shipit_agent.llms import SimpleEchoLLM
llm = SimpleEchoLLM()
print('llm:', type(llm).__name__)
FigmaTool is an HTTPConnectorToolBase — we replace _request_json with canned responses that look like real Figma API payloads.
from shipit_agent.integrations import CredentialRecord, InMemoryCredentialStore
from shipit_agent.tools.figma import FigmaTool
store = InMemoryCredentialStore()
store.set(CredentialRecord(
key='figma', provider='figma',
secrets={'token': 'figma-demo-token'},
metadata={'base_url': 'https://api.figma.com'},
))
figma = FigmaTool(credential_store=store)
FILE_KEY = 'ABCDEF123456'
def _fake_figma(*, record, method, path, query=None, body=None):
if path == f'/v1/files/{FILE_KEY}':
return {
'name': 'Billing v3 — Empty States',
'lastModified': '2026-04-23T09:12:00Z',
'document': {'children': [
{'id': '1:2', 'name': 'Cover', 'type': 'CANVAS'},
{'id': '1:5', 'name': 'Screens', 'type': 'CANVAS'},
{'id': '1:8', 'name': 'States', 'type': 'CANVAS'},
]},
}
if path == f'/v1/files/{FILE_KEY}/comments':
return {'comments': [
{'id': 'c1', 'user': {'handle': 'mira'},
'message': 'Error state contrast feels too low — bump to 4.5:1.',
'resolved_at': None},
{'id': 'c2', 'user': {'handle': 'rahul'},
'message': 'Primary CTA should sit above the fold on mobile.',
'resolved_at': None},
{'id': 'c3', 'user': {'handle': 'shinji'},
'message': 'Copy for zero-invoices state is too apologetic.',
'resolved_at': None},
]}
if path == f'/v1/images/{FILE_KEY}':
return {'images': {
'1:20': 'https://example.com/figma-fake/frame-empty-invoices.png',
'1:21': 'https://example.com/figma-fake/frame-error-card-declined.png',
}}
return {}
figma._request_json = _fake_figma
print('figma stubs installed')
from shipit_agent.tools.base import ToolContext
ctx = ToolContext(prompt='design review', state={'credential_store': store})
file_out = figma.run(ctx, action='get_file', file_key=FILE_KEY)
print(file_out.text)
comments_out = figma.run(ctx, action='get_comments', file_key=FILE_KEY)
print()
print(comments_out.text)
img_out = figma.run(ctx, action='get_image', file_key=FILE_KEY,
ids=['1:20', '1:21'], format='png', scale=2)
print()
print(img_out.text)
images = img_out.metadata.get('images', {})
VisionTool takes any URL, file path, data URL, or raw base64 and forwards it to a vision-capable LLM. With SimpleEchoLLM you get the shape of the request; plug in a real Claude/GPT-4o to get actual analysis of each frame.
from shipit_agent.tools.vision import VisionTool
vision = VisionTool(llm=llm)
vision_notes: list[dict] = []
for node_id, url in images.items():
v = vision.run(ctx, image=url,
prompt=('You are a design reviewer. What are the '
'top 3 visual issues on this frame? Focus '
'on contrast, hierarchy, and empty-state copy.'))
vision_notes.append({'node_id': node_id, 'url': url, 'notes': v.text})
print(f'{node_id}: {v.text[:160]}')
Bundle the file header, comments, vision notes, and a verdict into a single HTML document the designer can share in a PR.
from shipit_agent.tools.dashboard_render import DashboardRenderTool
dash = DashboardRenderTool(workspace_root=WORKSPACE)
comment_items = comments_out.metadata.get('items') or []
timeline = [
{'period': c.get('id', '?'),
'head': c.get('user', {}).get('handle', '?'),
'desc': c.get('message', ''),
'dot_color': '#ba7517',
'tags': [{'text': 'open', 'color': 'amber'}]}
for c in comment_items
]
cards = [
{'title': f'Frame {n["node_id"]}', 'rows': [
{'strong': 'URL:', 'text': n['url'], 'dot_color': '#185fa5'},
{'strong': 'Notes:', 'text': (n['notes'] or '(echo — plug in a vision LLM)')[:240],
'dot_color': '#1d9e75'},
]}
for n in vision_notes
]
result = dash.run(
ToolContext(prompt='design review', state={'artifact_workspace_root': str(WORKSPACE)}),
title=f'Design Review — {file_out.metadata["file"]["name"]}',
subtitle='Generated from Figma comments + vision analysis',
lang='en',
sections=[
{'type': 'metrics', 'title': 'Review snapshot', 'columns': 3, 'items': [
{'label': 'Pages', 'value': str(len(file_out.metadata['file']['document']['children']))},
{'label': 'Open comments', 'value': str(len(comment_items))},
{'label': 'Frames analysed', 'value': str(len(vision_notes))},
]},
{'type': 'timeline', 'title': 'Open comments', 'items': timeline},
{'type': 'cards', 'title': 'Vision analysis', 'columns': 2, 'cards': cards},
{'type': 'verdict', 'title': 'Reviewer verdict',
'text': ('Three blockers: **contrast on error state**, **CTA above fold on mobile**, '
'**softer empty-state copy**. Ship fixes before QA hand-off.')},
],
export=True,
)
print(result.text)
print('artifact path:', result.metadata.get('path'))
SimpleEchoLLM for a real vision-capable LLM (Claude Sonnet, GPT-4o) in section 1 — you'll get genuine design critique instead of echoed prompts.docs-app/content/source/tools/figma.md for the full Figma action surface (projects, components, rendering, posting replies).figma.run(..., action='post_comment') to close the loop.