spaps
Version:
Sweet Potato Authentication & Payment Service CLI - Docker Compose orchestrator for local Python/FastAPI SPAPS server with built-in admin middleware
209 lines (197 loc) • 11.4 kB
JavaScript
/**
* Domain registry for SPAPS CLI.
*
* Each entry declares the routes a domain exposes and how the CLI should
* surface them: tool-spec entries, doctor mount probe, and metadata flags.
*
* Shape:
* {
* key: 'dayrate',
* label: 'Dayrate',
* probe: { method: 'GET', path: '/api/dayrate/availability' },
* tools: [
* { name, description, method, path,
* parameters?: JSONSchema,
* admin_required?: boolean, // D1 + D5
* agent_callable?: boolean } // D5, default true
* ]
* }
*
* Path prefixes reflect the live backend routers as of 2026-04-16.
* Public = API key only. Admin = API key + JWT + admin role (admin_required:true).
* The CLI does not invent schemas; when OpenAPI is available ai-tool-spec.js
* enriches parameters/responses automatically.
*/
const DAYRATE = {
key: 'dayrate',
label: 'Dayrate',
probe: { method: 'GET', path: '/api/dayrate/availability' },
tools: [
{ name: 'dayrate_availability', method: 'GET', path: '/api/dayrate/availability',
description: 'List bookable slots within the configured horizon with dynamic pricing.' },
{ name: 'dayrate_book', method: 'POST', path: '/api/dayrate/book',
description: 'Book a single slot. Returns FreeBookResponse when an entitled user has remaining allotment, otherwise a Stripe Checkout URL.' },
{ name: 'dayrate_book_multi', method: 'POST', path: '/api/dayrate/book-multi',
description: 'Book multiple slots in one Stripe session with running fill-rate pricing.' },
{ name: 'dayrate_checkout_status', method: 'GET', path: '/api/dayrate/checkout-status',
description: 'Poll a Stripe session to learn the resulting booking status.' },
{ name: 'dayrate_cancel', method: 'POST', path: '/api/dayrate/cancel',
description: 'Cancel an existing booking. Owner verified by user_id then email.' },
{ name: 'dayrate_allotment', method: 'GET', path: '/api/dayrate/allotment',
description: 'Inspect free allotment status for a policy in the rolling window.' },
{ name: 'dayrate_admin_get_config', method: 'GET', path: '/api/dayrate/admin/config',
description: 'Fetch dayrate pricing/availability config.', admin_required: true },
{ name: 'dayrate_admin_update_config', method: 'PUT', path: '/api/dayrate/admin/config',
description: 'Upsert dayrate config (rates, tiers, horizon, timezone, slot definitions).', admin_required: true },
{ name: 'dayrate_admin_list_bookings', method: 'GET', path: '/api/dayrate/admin/bookings',
description: 'Admin view of bookings with status/date/email/enrollment/user/is_free filters.', admin_required: true },
{ name: 'dayrate_admin_list_policies', method: 'GET', path: '/api/dayrate/admin/policies',
description: 'List active booking policies.', admin_required: true },
{ name: 'dayrate_admin_create_policy', method: 'POST', path: '/api/dayrate/admin/policies',
description: 'Create a booking policy (entitlement key, free allotment, overage rate, reschedule rules).', admin_required: true },
{ name: 'dayrate_admin_update_policy', method: 'PUT', path: '/api/dayrate/admin/policies/{policy_id}',
description: 'Partial update of a booking policy.', admin_required: true },
{ name: 'dayrate_admin_delete_policy', method: 'DELETE', path: '/api/dayrate/admin/policies/{policy_id}',
description: 'Delete a booking policy.', admin_required: true },
],
};
const EMAIL = {
key: 'email',
label: 'Email',
probe: { method: 'GET', path: '/api/email/templates/__doctor__/preview', ok_on_404: true },
tools: [
{ name: 'email_send', method: 'POST', path: '/api/email/send',
description: 'Send a transactional email by template key. In local mode Mailgun calls are short-circuited.' },
{ name: 'email_get_template', method: 'GET', path: '/api/email/templates/{template_key}',
description: 'Fetch a template definition by key.' },
{ name: 'email_preview_template', method: 'GET', path: '/api/email/templates/{template_key}/preview',
description: 'Render a template with its sample context.' },
{ name: 'email_preview_template_with_context', method: 'POST', path: '/api/email/templates/{template_key}/preview',
description: 'Render a template with caller-provided context.' },
{ name: 'email_list_logs', method: 'GET', path: '/api/email/logs',
description: 'List email sends. Non-admin users are scoped to their own logs; admins can filter by user_id or owner_id.' },
{ name: 'email_admin_list_templates', method: 'GET', path: '/api/email/templates',
description: 'List all templates for the application.', admin_required: true },
{ name: 'email_admin_create_template', method: 'POST', path: '/api/email/templates',
description: 'Create a template (subject, html, text, variables, sample context).', admin_required: true },
{ name: 'email_admin_update_template', method: 'PUT', path: '/api/email/templates/{template_key}',
description: 'Update an existing template.', admin_required: true },
{ name: 'email_admin_get_override', method: 'GET', path: '/api/email/templates/{template_key}/override',
description: 'Get subject/body override for a template.' },
{ name: 'email_admin_set_override', method: 'PUT', path: '/api/email/templates/{template_key}/override',
description: 'Set subject/body override for a template.', admin_required: true },
{ name: 'email_admin_clear_override', method: 'DELETE', path: '/api/email/templates/{template_key}/override',
description: 'Clear subject/body override for a template.', admin_required: true },
],
};
const WEBHOOKS = {
key: 'webhooks',
label: 'Webhooks',
probe: { method: 'GET', path: '/api/webhooks/events' },
tools: [
{ name: 'webhook_list', method: 'GET', path: '/api/webhooks',
description: 'List registered outbound webhooks.' },
{ name: 'webhook_register', method: 'POST', path: '/api/webhooks',
description: 'Register an outbound webhook. The create response returns a one-time signing_secret.' },
{ name: 'webhook_update', method: 'PUT', path: '/api/webhooks/{webhook_id}',
description: 'Update url, events, is_active, or headers on an existing webhook.' },
{ name: 'webhook_delete', method: 'DELETE', path: '/api/webhooks/{webhook_id}',
description: 'Remove a registered webhook.' },
{ name: 'webhook_deliveries', method: 'GET', path: '/api/webhooks/{webhook_id}/deliveries',
description: 'List delivery attempts for a webhook.' },
{ name: 'webhook_events', method: 'GET', path: '/api/webhooks/events',
description: 'List supported outbound event types.' },
{ name: 'webhook_mailgun_inbound', method: 'POST', path: '/api/webhooks/mailgun/events',
description: 'Mailgun inbound event receiver. Signature-verified; not callable from agents.',
agent_callable: false },
],
};
const POLICIES = {
key: 'policies',
label: 'Policies',
// /policies/evaluations is admin-gated; 401/403 still proves the router is mounted.
probe: { method: 'GET', path: '/api/policies/evaluations', ok_on_auth_error: true },
tools: [
{ name: 'policy_authorize', method: 'POST', path: '/api/policies/authorize',
description: 'Evaluate a named policy against a user/context. Returns allow/deny with reasons.' },
{ name: 'policy_admin_list', method: 'GET', path: '/api/policies',
description: 'List policies (is_active filter).', admin_required: true },
{ name: 'policy_admin_create', method: 'POST', path: '/api/policies',
description: 'Create a policy (name, effect, conditions, priority, metadata).', admin_required: true },
{ name: 'policy_admin_get', method: 'GET', path: '/api/policies/{policy_id}',
description: 'Fetch a single policy.', admin_required: true },
{ name: 'policy_admin_update', method: 'PUT', path: '/api/policies/{policy_id}',
description: 'Partial update of a policy.', admin_required: true },
{ name: 'policy_admin_delete', method: 'DELETE', path: '/api/policies/{policy_id}',
description: 'Delete a policy.', admin_required: true },
{ name: 'policy_admin_evaluations', method: 'GET', path: '/api/policies/evaluations',
description: 'List policy evaluation audit logs.', admin_required: true },
],
};
const BILLING = {
key: 'billing',
label: 'Billing Accounts',
// Admin-gated; 401/403 still proves the operator route is mounted.
probe: { method: 'GET', path: '/api/admin/billing/status', ok_on_auth_error: true },
tools: [
{ name: 'billing_status', method: 'GET', path: '/api/admin/billing/status',
description: 'Show current application billing-account resolution and redacted Stripe diagnostics.', admin_required: true },
{ name: 'billing_verify', method: 'GET', path: '/api/admin/billing/verify',
description: 'Verify that the current application has usable Stripe secret and webhook configuration.', admin_required: true },
{ name: 'billing_attach', method: 'POST', path: '/api/admin/billing/attach',
description: 'Bind the current application to an existing active Stripe billing account.', admin_required: true },
],
};
const ISSUE_REPORTING = {
key: 'issue_reporting',
label: 'Issue Reporting',
// /v1/issue-reports/status requires auth; any 401/403 still proves mounted.
probe: { method: 'GET', path: '/api/v1/issue-reports/status', ok_on_auth_error: true },
tools: [
{ name: 'issue_report_create', method: 'POST', path: '/api/v1/issue-reports',
description: 'Create an issue report. Requires the issue_reporting capability.' },
{ name: 'issue_report_list_mine', method: 'GET', path: '/api/v1/issue-reports',
description: 'List the caller\u2019s issue reports.' },
{ name: 'issue_report_status', method: 'GET', path: '/api/v1/issue-reports/status',
description: 'Aggregate status for the caller\u2019s issues.' },
{ name: 'issue_report_get', method: 'GET', path: '/api/v1/issue-reports/{issue_report_id}',
description: 'Fetch a single issue report.' },
{ name: 'issue_report_update_note', method: 'PATCH', path: '/api/v1/issue-reports/{issue_report_id}',
description: 'Update the note on an existing report.' },
{ name: 'issue_report_reply', method: 'POST', path: '/api/v1/issue-reports/{issue_report_id}/replies',
description: 'Reply to an issue; creates a child report and reopens the linked support case.' },
],
};
const DOMAINS = [DAYRATE, EMAIL, WEBHOOKS, POLICIES, BILLING, ISSUE_REPORTING];
function listDomains() {
return DOMAINS;
}
function findDomain(key) {
return DOMAINS.find((d) => d.key === key) || null;
}
function buildDomainTools({ includeNonAgent = false } = {}) {
const out = [];
for (const domain of DOMAINS) {
for (const t of domain.tools) {
const agentCallable = t.agent_callable !== false;
if (!agentCallable && !includeNonAgent) continue;
out.push({
name: t.name,
description: t.description,
method: t.method,
path: t.path,
parameters: t.parameters || { type: 'object', properties: {} },
domain: domain.key,
admin_required: Boolean(t.admin_required),
agent_callable: agentCallable,
});
}
}
return out;
}
module.exports = {
DOMAINS,
listDomains,
findDomain,
buildDomainTools,
};