spine-framework 0.4.4 → 0.5.0-rc.5
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.
- package/.env.example +13 -3
- package/.framework/cli/commands/bootstrap.ts +392 -0
- package/.framework/cli/commands/dev.ts +91 -218
- package/.framework/cli/commands/doctor.ts +132 -26
- package/.framework/cli/commands/generate.ts +38 -298
- package/.framework/cli/commands/init.ts +20 -1
- package/.framework/cli/commands/install-app.ts +183 -13
- package/.framework/cli/commands/migrate.ts +36 -30
- package/.framework/cli/commands/migrations.ts +16 -56
- package/.framework/cli/commands/upgrade.ts +35 -0
- package/.framework/cli/commands/validate-app.ts +127 -1
- package/.framework/cli/commands/validate-schema.ts +147 -0
- package/.framework/cli/context.ts +223 -110
- package/.framework/cli/index.ts +6 -0
- package/.framework/cli/lib/project-files.ts +241 -1
- package/.framework/functions/_shared/app-manifest.ts +2 -0
- package/.framework/functions/_shared/hash-api-key.ts +183 -79
- package/.framework/functions/_shared/logging.ts +562 -224
- package/.framework/functions/_shared/middleware.ts +47 -44
- package/.framework/functions/_shared/permissions.ts +4 -0
- package/.framework/functions/admin-data.ts +51 -28
- package/.framework/functions/ai-agents.ts +10 -6
- package/.framework/functions/api-keys.ts +301 -122
- package/.framework/functions/apps.ts +63 -29
- package/.framework/functions/cil-ingest.ts +48 -15
- package/.framework/functions/debug-auth.ts +14 -8
- package/.framework/functions/deliveries.ts +39 -12
- package/.framework/functions/embeddings.ts +149 -78
- package/.framework/functions/integration-outbound.ts +67 -27
- package/.framework/functions/integrations.ts +254 -127
- package/.framework/functions/logs.ts +273 -133
- package/.framework/functions/observability.ts +278 -148
- package/.framework/functions/pipeline-executions.ts +1 -1
- package/.framework/functions/pipelines.ts +14 -9
- package/.framework/functions/prompt-configs.ts +7 -4
- package/.framework/functions/roles.ts +8 -5
- package/.framework/functions/security.ts +30 -17
- package/.framework/functions/system-cron.ts +240 -127
- package/.framework/functions/tests.ts +7 -6
- package/.framework/functions/timers.ts +310 -107
- package/.framework/functions/triggers.ts +28 -13
- package/.framework/functions/types.ts +13 -10
- package/.framework/migrations/069_log_stats_window.sql +54 -0
- package/.framework/migrations/070_app_type_schema_rpcs.sql +166 -0
- package/.framework/migrations/071_timer_interval_keys.sql +96 -0
- package/.framework/migrations/072_slug_autofill.sql +77 -0
- package/.framework/migrations/073_seed_member_roles.sql +26 -0
- package/.framework/migrations/074_app_route_prefix_domains.sql +168 -0
- package/.framework/migrations/075_security_definer_grants.sql +157 -0
- package/.framework/src/apps/admin/index.tsx +1141 -775
- package/.framework/src/components/ErrorBoundary.tsx +136 -0
- package/.framework/src/components/admin/AdminListPage.tsx +81 -73
- package/.framework/src/components/admin/AdminSidebar.tsx +88 -79
- package/.framework/src/components/admin/EntitySelect.tsx +181 -0
- package/.framework/src/components/admin/PageInstructions.tsx +86 -127
- package/.framework/src/components/app-shell/GenericDetailPage.tsx +37 -13
- package/.framework/src/components/app-shell/GenericListPage.tsx +28 -32
- package/.framework/src/components/layout/Sidebar.tsx +300 -177
- package/.framework/src/components/runtime/DataDetailPage.tsx +67 -35
- package/.framework/src/components/runtime/DataHeader.tsx +96 -48
- package/.framework/src/components/runtime/DataListPage.tsx +3 -26
- package/.framework/src/components/runtime/DataStats.tsx +10 -10
- package/.framework/src/components/runtime/DataTable.tsx +12 -10
- package/.framework/src/components/ui/sheet.tsx +24 -26
- package/.framework/src/components/ui/textarea.tsx +74 -48
- package/.framework/src/contexts/AppContext.tsx +5 -12
- package/.framework/src/contexts/AuthContext.tsx +17 -9
- package/.framework/src/hooks/useApps.ts +18 -22
- package/.framework/src/hooks/useEntityList.ts +12 -9
- package/.framework/src/hooks/useListSchema.ts +108 -58
- package/.framework/src/instructions/README.md +74 -0
- package/.framework/src/instructions/accounts.md +97 -0
- package/.framework/src/instructions/ai-agents.md +69 -0
- package/.framework/src/instructions/alerts.md +80 -0
- package/.framework/src/instructions/api-keys.md +87 -0
- package/.framework/src/instructions/apps.md +77 -0
- package/.framework/src/instructions/concepts/ai-and-prompts.md +150 -0
- package/.framework/src/instructions/concepts/apps-and-renderers.md +228 -0
- package/.framework/src/instructions/concepts/auth-and-api-keys.md +133 -0
- package/.framework/src/instructions/concepts/automation.md +186 -0
- package/.framework/src/instructions/concepts/concurrency-and-idempotency.md +125 -0
- package/.framework/src/instructions/concepts/core-vs-custom.md +124 -0
- package/.framework/src/instructions/concepts/design-schema.md +152 -0
- package/.framework/src/instructions/concepts/integrations-and-webhooks.md +127 -0
- package/.framework/src/instructions/concepts/observability.md +116 -0
- package/.framework/src/instructions/concepts/permissions.md +150 -0
- package/.framework/src/instructions/concepts/runtime-records.md +111 -0
- package/.framework/src/instructions/concepts/scope-and-accounts.md +112 -0
- package/.framework/src/instructions/concepts/spine-way.md +172 -0
- package/.framework/src/instructions/deliveries.md +59 -0
- package/.framework/src/instructions/guide/00-orientation.md +106 -0
- package/.framework/src/instructions/guide/01-install-and-first-run.md +253 -0
- package/.framework/src/instructions/guide/02-project-map.md +147 -0
- package/.framework/src/instructions/guide/03-domain-model.md +223 -0
- package/.framework/src/instructions/guide/04-first-app.md +227 -0
- package/.framework/src/instructions/guide/05-glossary.md +183 -0
- package/.framework/src/instructions/integrations.md +96 -0
- package/.framework/src/instructions/internal/cil-markers.md +83 -0
- package/.framework/src/instructions/internal/docs-contract.md +158 -0
- package/.framework/src/instructions/invites.md +60 -0
- package/.framework/src/instructions/kb.md +80 -0
- package/.framework/src/instructions/logs.md +77 -0
- package/.framework/src/instructions/notifications.md +77 -0
- package/.framework/src/instructions/observability.md +61 -0
- package/.framework/src/instructions/operations/deployment.md +115 -0
- package/.framework/src/instructions/operations/migrations.md +125 -0
- package/.framework/src/instructions/operations/security-checklist.md +170 -0
- package/.framework/src/instructions/operations/troubleshooting.md +170 -0
- package/.framework/src/instructions/operations/upgrade.md +116 -0
- package/.framework/src/instructions/people.md +91 -0
- package/.framework/src/instructions/pipeline-executions.md +62 -0
- package/.framework/src/instructions/pipelines.md +78 -0
- package/.framework/src/instructions/prompts.md +95 -0
- package/.framework/src/instructions/reference/api/admin-data.md +162 -0
- package/.framework/src/instructions/reference/api/auth.md +113 -0
- package/.framework/src/instructions/reference/api/errors-and-envelopes.md +113 -0
- package/.framework/src/instructions/reference/app-manifest.md +184 -0
- package/.framework/src/instructions/reference/cli.md +124 -0
- package/.framework/src/instructions/reference/database.md +132 -0
- package/.framework/src/instructions/reference/design-schema.md +280 -0
- package/.framework/src/instructions/reference/env-vars.md +133 -0
- package/.framework/src/instructions/reference/frontend-sdk.md +207 -0
- package/.framework/src/instructions/reference/functions/account-nodes.md +75 -0
- package/.framework/src/instructions/reference/functions/admin-data.md +197 -0
- package/.framework/src/instructions/reference/functions/admin-funnel.md +90 -0
- package/.framework/src/instructions/reference/functions/admin-install-funnel.md +107 -0
- package/.framework/src/instructions/reference/functions/admin-signals.md +102 -0
- package/.framework/src/instructions/reference/functions/ai-agents.md +148 -0
- package/.framework/src/instructions/reference/functions/ai-interactions.md +156 -0
- package/.framework/src/instructions/reference/functions/api-keys.md +130 -0
- package/.framework/src/instructions/reference/functions/apps.md +140 -0
- package/.framework/src/instructions/reference/functions/auth.md +99 -0
- package/.framework/src/instructions/reference/functions/debug-auth.md +91 -0
- package/.framework/src/instructions/reference/functions/deliveries.md +110 -0
- package/.framework/src/instructions/reference/functions/embeddings.md +166 -0
- package/.framework/src/instructions/reference/functions/eval-harness.md +149 -0
- package/.framework/src/instructions/reference/functions/index.md +103 -0
- package/.framework/src/instructions/reference/functions/integration-outbound.md +200 -0
- package/.framework/src/instructions/reference/functions/integration-routes.md +151 -0
- package/.framework/src/instructions/reference/functions/integrations.md +130 -0
- package/.framework/src/instructions/reference/functions/invites.md +182 -0
- package/.framework/src/instructions/reference/functions/item-progress.md +107 -0
- package/.framework/src/instructions/reference/functions/kb.md +166 -0
- package/.framework/src/instructions/reference/functions/logs.md +141 -0
- package/.framework/src/instructions/reference/functions/observability.md +105 -0
- package/.framework/src/instructions/reference/functions/pipeline-executions.md +132 -0
- package/.framework/src/instructions/reference/functions/pipeline-runner-background.md +121 -0
- package/.framework/src/instructions/reference/functions/pipelines.md +136 -0
- package/.framework/src/instructions/reference/functions/prompt-configs.md +121 -0
- package/.framework/src/instructions/reference/functions/roles.md +121 -0
- package/.framework/src/instructions/reference/functions/security.md +140 -0
- package/.framework/src/instructions/reference/functions/system-cron.md +136 -0
- package/.framework/src/instructions/reference/functions/system.md +106 -0
- package/.framework/src/instructions/reference/functions/tests.md +97 -0
- package/.framework/src/instructions/reference/functions/timers.md +117 -0
- package/.framework/src/instructions/reference/functions/trigger-scheduler.md +102 -0
- package/.framework/src/instructions/reference/functions/triggers.md +140 -0
- package/.framework/src/instructions/reference/functions/types.md +146 -0
- package/.framework/src/instructions/reference/seeds.md +126 -0
- package/.framework/src/instructions/reference/shared-modules.md +127 -0
- package/.framework/src/instructions/reference/spine-config.md +114 -0
- package/.framework/src/instructions/roles.md +80 -0
- package/.framework/src/instructions/runtime-collaboration.md +106 -0
- package/.framework/src/instructions/runtime-detail.md +70 -0
- package/.framework/src/instructions/runtime-list.md +77 -0
- package/.framework/src/instructions/security.md +67 -0
- package/.framework/src/instructions/tasks/add-a-pipeline.md +110 -0
- package/.framework/src/instructions/tasks/add-a-role.md +96 -0
- package/.framework/src/instructions/tasks/add-a-timer.md +114 -0
- package/.framework/src/instructions/tasks/add-a-trigger.md +106 -0
- package/.framework/src/instructions/tasks/add-a-type.md +151 -0
- package/.framework/src/instructions/tasks/add-a-webhook.md +108 -0
- package/.framework/src/instructions/tasks/add-an-ai-agent.md +108 -0
- package/.framework/src/instructions/tasks/add-an-integration.md +124 -0
- package/.framework/src/instructions/tasks/build-a-custom-page.md +118 -0
- package/.framework/src/instructions/tasks/document-your-app.md +117 -0
- package/.framework/src/instructions/tasks/package-and-install-an-app.md +136 -0
- package/.framework/src/instructions/tasks/register-an-app.md +137 -0
- package/.framework/src/instructions/tasks/test-your-code.md +108 -0
- package/.framework/src/instructions/tasks/write-a-function.md +132 -0
- package/.framework/src/instructions/tasks/write-a-migration.md +110 -0
- package/.framework/src/instructions/testing.md +59 -0
- package/.framework/src/instructions/timers.md +84 -0
- package/.framework/src/instructions/triggers.md +69 -0
- package/.framework/src/instructions/types.md +93 -0
- package/.framework/src/lib/app-mount.ts +88 -0
- package/.framework/src/pages/NotFoundPage.tsx +62 -31
- package/.framework/src/pages/admin/AIAgentDetailPage.tsx +117 -106
- package/.framework/src/pages/admin/AIAgentsPage.tsx +86 -48
- package/.framework/src/pages/admin/APIKeyDetailPage.tsx +153 -152
- package/.framework/src/pages/admin/APIKeysPage.tsx +91 -90
- package/.framework/src/pages/admin/AlertsConfigPage.tsx +29 -18
- package/.framework/src/pages/admin/AppDetailPage.tsx +6 -60
- package/.framework/src/pages/admin/AppsPage.tsx +104 -66
- package/.framework/src/pages/admin/DeliveriesPage.tsx +5 -5
- package/.framework/src/pages/admin/EmbeddingDetailPage.tsx +5 -23
- package/.framework/src/pages/admin/EmbeddingsPage.tsx +5 -26
- package/.framework/src/pages/admin/IntegrationDetailPage.tsx +7 -7
- package/.framework/src/pages/admin/IntegrationsPage.tsx +85 -43
- package/.framework/src/pages/admin/LogsPage.tsx +144 -76
- package/.framework/src/pages/admin/ObservabilityDashboard.tsx +223 -140
- package/.framework/src/pages/admin/PipelineDetailPage.tsx +134 -113
- package/.framework/src/pages/admin/PipelineExecutionsPage.tsx +108 -45
- package/.framework/src/pages/admin/PipelinesPage.tsx +160 -115
- package/.framework/src/pages/admin/PromptConfigDetailPage.tsx +96 -98
- package/.framework/src/pages/admin/PromptConfigsPage.tsx +101 -59
- package/.framework/src/pages/admin/RoleDetailPage.tsx +90 -79
- package/.framework/src/pages/admin/RolesPage.tsx +86 -43
- package/.framework/src/pages/admin/SecurityPage.tsx +1 -1
- package/.framework/src/pages/admin/TestingDashboard.tsx +16 -28
- package/.framework/src/pages/admin/TimerDetailPage.tsx +117 -100
- package/.framework/src/pages/admin/TimersPage.tsx +90 -62
- package/.framework/src/pages/admin/TriggerDetailPage.tsx +90 -83
- package/.framework/src/pages/admin/TriggersPage.tsx +124 -99
- package/.framework/src/pages/admin/TypeDetailPage.tsx +152 -138
- package/.framework/src/pages/admin/TypesPage.tsx +90 -48
- package/.framework/src/pages/admin/cil/CILAgentPage.tsx +17 -25
- package/.framework/src/pages/admin/cil/CILChunkDetailPage.tsx +17 -27
- package/.framework/src/pages/admin/cil/CILDashboardPage.tsx +72 -40
- package/.framework/src/pages/admin/cil/CILFileListPage.tsx +321 -129
- package/.framework/src/pages/admin/cil/CILFileSelectPage.tsx +18 -29
- package/.framework/src/pages/admin/cil/CILGraphPage.tsx +26 -38
- package/.framework/src/pages/admin/cil/CILIngestionPage.tsx +34 -46
- package/.framework/src/pages/admin/cil/CILProjectsPage.tsx +189 -103
- package/.framework/src/pages/admin/cil/CILSearchPage.tsx +246 -136
- package/.framework/src/pages/admin/cil/CILSuggestionsPage.tsx +11 -22
- package/.framework/src/pages/admin/cil/CILVersionsPage.tsx +9 -20
- package/.framework/src/pages/admin/cil/FlexiblePipelinePage.tsx +26 -38
- package/.framework/src/pages/admin/kb/KBAdminPage.tsx +7 -26
- package/.framework/src/pages/auth/LoginPage.tsx +3 -2
- package/.framework/src/pages/auth/RegisterPage.tsx +16 -8
- package/.framework/templates/agent/AGENTS.md +167 -0
- package/.framework/templates/agent/skills/spine-add-ai-agent/SKILL.md +58 -0
- package/.framework/templates/agent/skills/spine-add-automation/SKILL.md +62 -0
- package/.framework/templates/agent/skills/spine-add-integration/SKILL.md +63 -0
- package/.framework/templates/agent/skills/spine-architect/SKILL.md +86 -0
- package/.framework/templates/agent/skills/spine-install-app/SKILL.md +79 -0
- package/.framework/templates/agent/skills/spine-migration/SKILL.md +75 -0
- package/.framework/templates/agent/skills/spine-new-app/SKILL.md +79 -0
- package/.framework/templates/agent/skills/spine-new-function/SKILL.md +71 -0
- package/.framework/templates/agent/skills/spine-new-type/SKILL.md +72 -0
- package/.framework/templates/agent/skills/spine-schema/SKILL.md +77 -0
- package/.framework/templates/agent/skills/spine-test/SKILL.md +67 -0
- package/.framework/templates/agent/skills/spine-troubleshoot/SKILL.md +74 -0
- package/.framework/templates/agent/skills/spine-upgrade/SKILL.md +69 -0
- package/.framework/templates/project.json +4 -1
- package/.framework/tests/unit/account-scoping-gates.test.ts +394 -0
- package/.framework/tests/unit/agent-files.test.ts +355 -0
- package/.framework/tests/unit/agent-runner.test.ts +479 -44
- package/.framework/tests/unit/api-keys-create-scope-gate.test.ts +160 -0
- package/.framework/tests/unit/api-keys-omit-secret-fields.test.ts +38 -0
- package/.framework/tests/unit/app-mount.test.ts +80 -0
- package/.framework/tests/unit/bootstrap.test.ts +242 -0
- package/.framework/tests/unit/cli-context-api-key.test.ts +114 -0
- package/.framework/tests/unit/debug-auth.test.ts +38 -0
- package/.framework/tests/unit/deliveries-account-scoping.test.ts +185 -0
- package/.framework/tests/unit/integration-outbound-credentials.test.ts +167 -0
- package/.framework/tests/unit/integrations-audit-redaction.test.ts +130 -0
- package/.framework/tests/unit/logging-app-id.test.ts +75 -0
- package/.framework/tests/unit/security-definer-grants.test.ts +154 -0
- package/.framework/tests/unit/system-context-denial.test.ts +145 -0
- package/.framework/tests/unit/system-cron.test.ts +240 -0
- package/.framework/tests/unit/timers.test.ts +159 -0
- package/README.md +125 -166
- package/config/tsconfig.cli.json +2 -2
- package/package.json +15 -4
- package/templates/project/package-lock.json +2 -2
- package/.framework/docs/admin-routes.md +0 -76
- package/.framework/docs/api-docs-progress.md +0 -38
- package/.framework/docs/api-governance.md +0 -146
- package/.framework/docs/api-testing-results.md +0 -212
- package/.framework/docs/assembly-launch-guide.md +0 -138
- package/.framework/docs/audit-results.md +0 -590
- package/.framework/docs/db-api-inventory.md +0 -95
- package/.framework/docs/gap-remediation-backlog.md +0 -103
- package/.framework/docs/ui-adoption-verification.md +0 -111
- package/.framework/docs/ui-api-coverage.md +0 -84
- package/.framework/docs/v2-compatibility-audit.md +0 -228
- package/.framework/mcp/index.ts +0 -133
- package/.framework/mcp/load-env.ts +0 -60
- package/.framework/mcp/shared/app-manifest.ts +0 -275
- package/.framework/mcp/shared/db.ts +0 -477
- package/.framework/mcp/shared/kb.ts +0 -118
- package/.framework/mcp/tools/architect.ts +0 -431
- package/.framework/mcp/tools/cil/context.ts +0 -345
- package/.framework/mcp/tools/cil/graph.ts +0 -242
- package/.framework/mcp/tools/cil/ingest.ts +0 -1217
- package/.framework/mcp/tools/cil/pipeline-orchestrator.ts +0 -693
- package/.framework/mcp/tools/cil/pipeline.ts +0 -767
- package/.framework/mcp/tools/cil/project.ts +0 -791
- package/.framework/mcp/tools/cil/retrieval.ts +0 -336
- package/.framework/mcp/tools/cil/search-core.ts +0 -454
- package/.framework/mcp/tools/cil/search.ts +0 -97
- package/.framework/mcp/tools/cil/suggest.ts +0 -186
- package/.framework/mcp/tools/cil/validate.ts +0 -1369
- package/.framework/mcp/tools/kb/delete.ts +0 -139
- package/.framework/mcp/tools/kb/index.ts +0 -52
- package/.framework/mcp/tools/kb/ingest.ts +0 -84
- package/.framework/mcp/tools/kb/list-types.ts +0 -102
- package/.framework/mcp/tools/kb/publish.ts +0 -100
- package/.framework/mcp/tools/kb/reindex.ts +0 -86
- package/.framework/mcp/tools/kb/revoke.ts +0 -85
- package/.framework/mcp/tools/kb/search.ts +0 -101
- package/.framework/mcp/tools/kb/upsert.ts +0 -125
- package/.framework/mcp/tools/observability.ts +0 -510
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Add an integration
|
|
3
|
+
audience: developers, agents
|
|
4
|
+
kind: task
|
|
5
|
+
verified_against: e13127d
|
|
6
|
+
verified_on: 2026-10-30
|
|
7
|
+
source_files:
|
|
8
|
+
- .framework/functions/integrations.ts
|
|
9
|
+
- .framework/functions/integration-outbound.ts
|
|
10
|
+
- .framework/functions/_shared/resolve-credentials.ts
|
|
11
|
+
- .framework/functions/_shared/permissions.ts
|
|
12
|
+
- .framework/migrations/004_core_rpc_and_broker.sql
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
# Add an integration
|
|
16
|
+
|
|
17
|
+
## Goal
|
|
18
|
+
|
|
19
|
+
Create a `public.integrations` row — the config + credentials handle for
|
|
20
|
+
outbound HTTP calls (`integration-outbound`) and inbound webhook routing
|
|
21
|
+
(`integration-routes`). See
|
|
22
|
+
[Integrations and webhooks](../concepts/integrations-and-webhooks.md) for
|
|
23
|
+
the full model.
|
|
24
|
+
|
|
25
|
+
## Prerequisites
|
|
26
|
+
|
|
27
|
+
- A `system_admin` login — the integrations function enforces it
|
|
28
|
+
(`.framework/functions/integrations.ts`, `isSystemAdmin` signal).
|
|
29
|
+
|
|
30
|
+
## Steps
|
|
31
|
+
|
|
32
|
+
1. **Choose the integration shape.**
|
|
33
|
+
|
|
34
|
+
- `integration_type`: semantic category — `webhook`, `oauth`, `api`,
|
|
35
|
+
`scheduled_sync` (column comment,
|
|
36
|
+
`.framework/migrations/004_core_rpc_and_broker.sql`).
|
|
37
|
+
- `provider`: the vendor name — `stripe`, `openai`, `generic`, …
|
|
38
|
+
(used as the fallback inbound adapter name).
|
|
39
|
+
- `slug`: unique per `(app_id, account_id)` —
|
|
40
|
+
`integrations_app_account_slug_key`
|
|
41
|
+
(`.framework/migrations/004_core_rpc_and_broker.sql`).
|
|
42
|
+
|
|
43
|
+
2. **Configure `config` and `credentials`.**
|
|
44
|
+
|
|
45
|
+
- `config.outbound_adapter` selects the adapter module for outbound
|
|
46
|
+
calls — built-ins: `adapters/openai-outbound`,
|
|
47
|
+
`adapters/generic-webhook-outbound`, `adapters/resend-outbound`
|
|
48
|
+
(`.framework/functions/integration-outbound.ts`). `action_config.adapter`
|
|
49
|
+
can override it per call.
|
|
50
|
+
- `credentials` is a map `{ field: { source, value } }`:
|
|
51
|
+
`source: "value"` stores a literal secret; `source: "env_var"` resolves
|
|
52
|
+
`value` as a `process.env` name server-side at call time
|
|
53
|
+
(`.framework/functions/_shared/resolve-credentials.ts`).
|
|
54
|
+
|
|
55
|
+
```json
|
|
56
|
+
{
|
|
57
|
+
"slug": "acme-api", "name": "Acme API",
|
|
58
|
+
"integration_type": "api", "provider": "acme",
|
|
59
|
+
"config": {
|
|
60
|
+
"base_url": "https://api.acme.test",
|
|
61
|
+
"outbound_adapter": "adapters/generic-webhook-outbound"
|
|
62
|
+
},
|
|
63
|
+
"credentials": {
|
|
64
|
+
"api_token": { "source": "env_var", "value": "ACME_API_TOKEN" }
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
`is_configured` is recomputed on every save — `true` only when every
|
|
70
|
+
credential entry is present and non-empty
|
|
71
|
+
(`.framework/functions/integrations.ts`).
|
|
72
|
+
|
|
73
|
+
3. **Create the integration.** Admin UI → **Configs → Integrations → New**,
|
|
74
|
+
or `POST /api/integrations` — `name`, `integration_type`, and `provider`
|
|
75
|
+
required (`.framework/functions/integrations.ts`).
|
|
76
|
+
|
|
77
|
+
4. **Call it outbound.** `POST /api/integration-outbound` (or a pipeline
|
|
78
|
+
`http_request`/webhook stage) with `{ integration_slug, action_config }`.
|
|
79
|
+
`action_config` carries `endpoint`/`url`, `body`, `credential_field`
|
|
80
|
+
(default `api_token`), `action_slug`, `max_attempts`, and optional
|
|
81
|
+
`adapter` (`.framework/functions/integration-outbound.ts`).
|
|
82
|
+
Deliveries land in `webhook_deliveries` with retry bookkeeping — see
|
|
83
|
+
[integration-outbound reference](../reference/functions/integration-outbound.md).
|
|
84
|
+
|
|
85
|
+
5. **For inbound**, point senders at `integration-routes` and pick an
|
|
86
|
+
adapter — see [Add a webhook](./add-a-webhook.md).
|
|
87
|
+
|
|
88
|
+
## Verify
|
|
89
|
+
|
|
90
|
+
- `GET /api/integrations?action=list` — `is_configured: true` once every
|
|
91
|
+
credential resolves.
|
|
92
|
+
- A `webhook_deliveries` row is written per outbound call with
|
|
93
|
+
`response_status` and retry fields.
|
|
94
|
+
|
|
95
|
+
## Common mistakes
|
|
96
|
+
|
|
97
|
+
- **An empty or absent credential means "no auth", not an error.**
|
|
98
|
+
`execute` skips resolution for an absent entry or an empty
|
|
99
|
+
`source: "value"` literal and the call goes out without a credential —
|
|
100
|
+
the stock `generic-http` seed (`{"api_token":{"source":"value","value":""}}`,
|
|
101
|
+
`.framework/migrations/004_core_rpc_and_broker.sql`) therefore works
|
|
102
|
+
unauthenticated out of the box. Whether a credential is required is the
|
|
103
|
+
*adapter's* call: `generic-webhook-outbound` only attaches
|
|
104
|
+
`Authorization: Bearer` when a credential resolved, while
|
|
105
|
+
`openai-outbound`/`resend-outbound` throw in `buildRequest`. What still
|
|
106
|
+
fails hard: an `env_var` entry naming an unset variable
|
|
107
|
+
(`credential_env_missing`).
|
|
108
|
+
- **Expecting credentials in reads.** Integrations are a protected
|
|
109
|
+
second-surface table — non-system-admin/machine reads are sanitized to
|
|
110
|
+
minimal fields; secrets are only resolved server-side
|
|
111
|
+
(`.framework/functions/_shared/permissions.ts`).
|
|
112
|
+
- **`env_var` pointing at a missing env** — `resolveCredential` throws
|
|
113
|
+
before the request is sent and emits `integration.credential_*` audit
|
|
114
|
+
rows.
|
|
115
|
+
- **Slug not unique within `(app_id, account_id)`** — the DB unique
|
|
116
|
+
constraint rejects it; account-scoped and global rows share the key.
|
|
117
|
+
|
|
118
|
+
## Reference
|
|
119
|
+
|
|
120
|
+
- [integrations entity page](../integrations.md) ·
|
|
121
|
+
[integrations function reference](../reference/functions/integrations.md) ·
|
|
122
|
+
[integration-outbound reference](../reference/functions/integration-outbound.md) ·
|
|
123
|
+
[integration-routes reference](../reference/functions/integration-routes.md)
|
|
124
|
+
- [Integrations and webhooks](../concepts/integrations-and-webhooks.md)
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Build a custom page
|
|
3
|
+
audience: developers, agents
|
|
4
|
+
kind: task
|
|
5
|
+
verified_against: e13127d
|
|
6
|
+
verified_on: 2026-10-30
|
|
7
|
+
source_files:
|
|
8
|
+
- .framework/src/components/CustomAppLoader.tsx
|
|
9
|
+
- .framework/src/components/AppWrapper.tsx
|
|
10
|
+
- .framework/src/components/app-shell/GenericAppShell.tsx
|
|
11
|
+
- .framework/src/lib/api.ts
|
|
12
|
+
- .framework/cli/commands/create-app.ts
|
|
13
|
+
- scripts/assemble.sh
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
# Build a custom page
|
|
17
|
+
|
|
18
|
+
## Goal
|
|
19
|
+
|
|
20
|
+
Ship a React page inside a custom app (`renderer: 'custom'`) instead of the
|
|
21
|
+
generic list/detail renderer. See
|
|
22
|
+
[Apps and renderers](../concepts/apps-and-renderers.md) for the model.
|
|
23
|
+
|
|
24
|
+
## When to go custom
|
|
25
|
+
|
|
26
|
+
Stay on `renderer: 'generic'` when the app is CRUD over typed records — the
|
|
27
|
+
generic renderer gives you list/detail/create/sort/permission-aware editing
|
|
28
|
+
for free from `views.default_list` / `views.default_detail`. Go custom for
|
|
29
|
+
dashboards, wizards, bespoke layouts, or anything not expressible as
|
|
30
|
+
schema-driven list/detail — `GenericAppShell` only generates routes for
|
|
31
|
+
nav items carrying `type_slug`
|
|
32
|
+
(`.framework/src/components/app-shell/GenericAppShell.tsx`).
|
|
33
|
+
|
|
34
|
+
## Steps
|
|
35
|
+
|
|
36
|
+
1. **Scaffold the app** (or reuse one):
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
npx spine-framework create-app <slug> --role <role-slug>
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
Writes `custom/apps/<slug>/` with `manifest.json`, `index.tsx`, and
|
|
43
|
+
`components/`, and inserts the `apps` row with `renderer: 'custom'`
|
|
44
|
+
(`.framework/cli/commands/create-app.ts`). Pass `--role` a slug
|
|
45
|
+
that exists in `roles` — the default `member` is seeded by
|
|
46
|
+
`073_seed_member_roles.sql`.
|
|
47
|
+
|
|
48
|
+
2. **Add pages under `custom/apps/<slug>/pages/`.** `CustomAppLoader`
|
|
49
|
+
lazy-imports only `custom/apps/<slug>/index.tsx`
|
|
50
|
+
(`import.meta.glob('../../../custom/apps/*/index.tsx')`,
|
|
51
|
+
`.framework/src/components/CustomAppLoader.tsx`) — `index.tsx`
|
|
52
|
+
default-exports the component (usually its own `<Routes>`) and imports
|
|
53
|
+
the page components itself.
|
|
54
|
+
|
|
55
|
+
```tsx
|
|
56
|
+
// custom/apps/<slug>/pages/DashboardPage.tsx
|
|
57
|
+
export default function DashboardPage() { /* ... */ }
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
3. **Set the app row.** `renderer: 'custom'`, `route_prefix: '/<slug>'`,
|
|
61
|
+
`min_role` / `required_roles`, `nav_items` for the sidebar. `AppWrapper`
|
|
62
|
+
enforces auth + role before mounting your app
|
|
63
|
+
(`.framework/src/components/AppWrapper.tsx`).
|
|
64
|
+
|
|
65
|
+
4. **Fetch data through `/api/admin-data`** — never write a bespoke CRUD
|
|
66
|
+
function:
|
|
67
|
+
|
|
68
|
+
```ts
|
|
69
|
+
import { apiFetch } from '@core/lib/api'
|
|
70
|
+
|
|
71
|
+
const res = await apiFetch(
|
|
72
|
+
'/api/admin-data?action=list&entity=items&type_slug=ticket&limit=50')
|
|
73
|
+
const { data } = await res.json()
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
`apiFetch` attaches the Supabase Bearer token and the `X-Account-Id`
|
|
77
|
+
header (`.framework/src/lib/api.ts`). Find-or-create related records
|
|
78
|
+
(e.g. a thread for a work item) through the same endpoint — see
|
|
79
|
+
[Build your first app — Step 4](../guide/04-first-app.md) for the full
|
|
80
|
+
call shapes.
|
|
81
|
+
|
|
82
|
+
5. **Rebuild and run.** `npm run assemble` (or `npm run dev` / `dev:netlify`,
|
|
83
|
+
which run assembly automatically). Custom functions go in
|
|
84
|
+
`custom/functions/` prefixed `custom_` — assembly hard-fails on a
|
|
85
|
+
basename collision with a framework function
|
|
86
|
+
(`scripts/assemble-functions.sh`).
|
|
87
|
+
|
|
88
|
+
## Verify
|
|
89
|
+
|
|
90
|
+
- Navigate to `/<slug>` — `CustomAppLoader` resolves your `index.tsx`
|
|
91
|
+
(a missing entry shows the `App "<slug>" not found` error naming the
|
|
92
|
+
expected path).
|
|
93
|
+
- `GET /api/apps?action=get&slug=<slug>` shows `renderer: 'custom'` and the
|
|
94
|
+
right `route_prefix`.
|
|
95
|
+
- `npm run typecheck` compiles the page.
|
|
96
|
+
|
|
97
|
+
## Common mistakes
|
|
98
|
+
|
|
99
|
+
- **A bespoke CRUD endpoint.** Custom Netlify functions that
|
|
100
|
+
create/update/list runtime records bypass `admin-data`'s permission,
|
|
101
|
+
type-resolution, audit, and trigger logic — call `/api/admin-data` from
|
|
102
|
+
the client instead (see [Write a function](./write-a-function.md) for the
|
|
103
|
+
cases where a custom function *is* allowed).
|
|
104
|
+
- **Forgetting `index.tsx` is the only entry.** New pages in `pages/` are
|
|
105
|
+
invisible until `index.tsx` routes to them.
|
|
106
|
+
- **`type_slug` nav items on a custom app** — they only do something under
|
|
107
|
+
the generic renderer; use plain `path` nav items and route them yourself.
|
|
108
|
+
- **Editing before re-assembling** — `CustomAppLoader`'s glob is evaluated
|
|
109
|
+
at build time; new files need an assemble/dev restart.
|
|
110
|
+
|
|
111
|
+
## Reference
|
|
112
|
+
|
|
113
|
+
- [Apps and renderers](../concepts/apps-and-renderers.md) ·
|
|
114
|
+
[Runtime records](../concepts/runtime-records.md)
|
|
115
|
+
- [App manifest reference](../reference/app-manifest.md) ·
|
|
116
|
+
[admin-data function reference](../reference/functions/admin-data.md)
|
|
117
|
+
- [Build your first app](../guide/04-first-app.md) ·
|
|
118
|
+
[Anatomy of an installed project](../guide/02-project-map.md)
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Document your app
|
|
3
|
+
audience: developers, agents
|
|
4
|
+
kind: task
|
|
5
|
+
verified_against: b3b3ae0
|
|
6
|
+
verified_on: 2026-11-01
|
|
7
|
+
source_files:
|
|
8
|
+
- .framework/cli/commands/install-app.ts
|
|
9
|
+
- .framework/cli/commands/validate-app.ts
|
|
10
|
+
- .framework/src/instructions/internal/docs-contract.md
|
|
11
|
+
- .framework/templates/agent/AGENTS.md
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
# Document your app
|
|
15
|
+
|
|
16
|
+
## Goal
|
|
17
|
+
|
|
18
|
+
Give your app the same agent-legible documentation the framework ships —
|
|
19
|
+
so a cold agent (or a future teammate) can install it, understand it, and
|
|
20
|
+
work in it without reading every file. The contract: a small instruction
|
|
21
|
+
book inside the app plus namespaced Devin skills, kept in sync by the same
|
|
22
|
+
capture rule as the core book.
|
|
23
|
+
|
|
24
|
+
## Steps
|
|
25
|
+
|
|
26
|
+
1. **Create the app's instruction book** at
|
|
27
|
+
`custom/apps/<slug>/src/instructions/` — "book-lite": the same layer
|
|
28
|
+
structure, shrunk to app scale:
|
|
29
|
+
|
|
30
|
+
```
|
|
31
|
+
src/instructions/
|
|
32
|
+
├── README.md # what the app is + the doc map (kind: index)
|
|
33
|
+
├── entities/ # one page per seeded type (or per type family)
|
|
34
|
+
│ └── ticketing.md
|
|
35
|
+
├── tasks/ # the jobs someone does in/with this app
|
|
36
|
+
│ └── install.md
|
|
37
|
+
└── reference/
|
|
38
|
+
└── functions.md # the app's custom_* functions
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
Every page carries the same frontmatter as the core book — `title`,
|
|
42
|
+
`audience`, `kind`, `verified_against`, `verified_on`, and
|
|
43
|
+
`source_files` pointing at **the app's own** functions/seeds/pages. That
|
|
44
|
+
frontmatter is what makes your docs drift-checkable later.
|
|
45
|
+
|
|
46
|
+
2. **Author skills where they live.** App skills go in
|
|
47
|
+
`custom/.devin/skills/<prefix>_<kebab-name>/SKILL.md` — the discovered,
|
|
48
|
+
invocable location. Directory name = SKILL.md `name:` field = the skill's
|
|
49
|
+
identity. The `<prefix>_` namespace keeps your skills separate from
|
|
50
|
+
framework `spine-*` skills and makes uninstall deterministic.
|
|
51
|
+
|
|
52
|
+
3. **Declare the prefix in `manifest.json`:**
|
|
53
|
+
|
|
54
|
+
```json
|
|
55
|
+
"agent": { "skill_prefix": "ao" }
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
`^[a-z][a-z0-9]{0,7}$`, unique across installed apps (the installer
|
|
59
|
+
checks). Convention: initials of the slug — `answer-once` → `ao`,
|
|
60
|
+
`demo` → `d`.
|
|
61
|
+
|
|
62
|
+
4. **Stage assets for packaging** under `agent/` in the app directory —
|
|
63
|
+
the installer maps them to their destinations:
|
|
64
|
+
|
|
65
|
+
| Package path | Installed to |
|
|
66
|
+
|---|---|
|
|
67
|
+
| `agent/skills/<prefix>_*/` | `custom/.devin/skills/<prefix>_*/` |
|
|
68
|
+
| `agent/instructions/**` | `custom/apps/<slug>/src/instructions/` |
|
|
69
|
+
|
|
70
|
+
The package's `files` array in `package.json` must list `agent/`
|
|
71
|
+
(npm only packs declared entries) — plus everything else the installer
|
|
72
|
+
copies (`seed/`, `standalone.config.json`, `docs/`, …).
|
|
73
|
+
|
|
74
|
+
For in-project development you edit the live copies directly
|
|
75
|
+
(`custom/.devin/skills/`, `custom/apps/<slug>/src/instructions/`); copy
|
|
76
|
+
them into `agent/` when packaging. `validate-app` warns if the staged
|
|
77
|
+
and live copies drift.
|
|
78
|
+
|
|
79
|
+
5. **Validate** with `npx spine-framework validate-app <dir>` — checks the
|
|
80
|
+
`agent` block, prefix rules, and SKILL.md name/dir equality.
|
|
81
|
+
|
|
82
|
+
## Verify
|
|
83
|
+
|
|
84
|
+
- `custom/.devin/skills/<prefix>_*/SKILL.md` files are invocable in a fresh
|
|
85
|
+
agent session.
|
|
86
|
+
- `custom/apps/<slug>/src/instructions/README.md` exists and links the
|
|
87
|
+
rest.
|
|
88
|
+
- A fresh agent session asked "work in this app" finds the book via the
|
|
89
|
+
AGENTS.md pointer line without being told the path.
|
|
90
|
+
|
|
91
|
+
## Common mistakes
|
|
92
|
+
|
|
93
|
+
- **Skills inside the app dir** — `custom/apps/<slug>/.devin/` is *not*
|
|
94
|
+
discovered by agent tooling; `custom/.devin/skills/` is. Authored in the
|
|
95
|
+
app dir they become docs, not skills.
|
|
96
|
+
- **Missing `agent.skill_prefix`** — the installer fails closed when
|
|
97
|
+
`agent/skills/` exists without it; declare it even with one skill.
|
|
98
|
+
- **Prefix collisions** — two installed apps sharing a prefix fails at
|
|
99
|
+
install. Keep prefixes short and slug-derived.
|
|
100
|
+
- **`agent/` missing from `files`** — the tarball silently drops your
|
|
101
|
+
staged assets and the installer has nothing to sync. List `agent/` (and
|
|
102
|
+
`seed/`, `standalone.config.json`, …) in `package.json`'s `files`.
|
|
103
|
+
- **`src/` for code** — `src/` inside an app is *instructions-only*; the
|
|
104
|
+
loader only globs `custom/apps/*/index.tsx`. App code stays in
|
|
105
|
+
`pages/`, `components/`, `hooks/`, `utils/`, `lib/`.
|
|
106
|
+
- **Docs written once and abandoned** — the capture contract applies to
|
|
107
|
+
your app too: every change to the app updates its `src/instructions/`
|
|
108
|
+
in the same commit.
|
|
109
|
+
|
|
110
|
+
## Reference
|
|
111
|
+
|
|
112
|
+
- [Docs contract](../internal/docs-contract.md) — the doctrine and the
|
|
113
|
+
capture rule this recipe implements
|
|
114
|
+
- [App manifest reference](../reference/app-manifest.md) — the `agent`
|
|
115
|
+
block
|
|
116
|
+
- [Package and install an app](package-and-install-an-app.md) — the
|
|
117
|
+
install/update/uninstall mechanics
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Package and install an app
|
|
3
|
+
audience: developers, agents
|
|
4
|
+
kind: task
|
|
5
|
+
verified_against: e13127d
|
|
6
|
+
verified_on: 2026-10-30
|
|
7
|
+
source_files:
|
|
8
|
+
- .framework/cli/commands/install-app.ts
|
|
9
|
+
- .framework/cli/commands/validate-app.ts
|
|
10
|
+
- .framework/functions/_shared/app-manifest.ts
|
|
11
|
+
- .framework/cli/commands/migrate.ts
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
# Package and install an app
|
|
15
|
+
|
|
16
|
+
## Goal
|
|
17
|
+
|
|
18
|
+
Package a `custom/apps/<slug>/` directory as an npm package and install it
|
|
19
|
+
into a Spine project — files copied, `apps` row upserted, migrations run,
|
|
20
|
+
seeds applied. Field-level manifest semantics are in
|
|
21
|
+
[App manifest reference](../reference/app-manifest.md) and
|
|
22
|
+
[Seeds reference](../reference/seeds.md).
|
|
23
|
+
|
|
24
|
+
## Steps
|
|
25
|
+
|
|
26
|
+
1. **Author `manifest.json`.** Required fields the installer validates:
|
|
27
|
+
`slug` (must equal the directory name and match
|
|
28
|
+
`^[a-z][a-z0-9-]*$`), plus `name`, `route_prefix`, `renderer`,
|
|
29
|
+
`min_role` / `required_roles`, `nav_items`, `config`, `registration`.
|
|
30
|
+
See [App manifest reference](../reference/app-manifest.md) for every
|
|
31
|
+
field.
|
|
32
|
+
|
|
33
|
+
2. **Author seed files.** The `seed` block is a list of
|
|
34
|
+
`{ file, table, conflict, inject_app_id?, ignore_duplicates?, permissions_patch? }`
|
|
35
|
+
entries, each pointing at a JSON file of rows
|
|
36
|
+
(`.framework/cli/commands/install-app.ts`):
|
|
37
|
+
|
|
38
|
+
- `conflict` — the `onConflict` column list for the upsert. Types use
|
|
39
|
+
`conflict: "kind,slug"` with `inject_app_id: false` — types are
|
|
40
|
+
**global**, not duplicated per install.
|
|
41
|
+
- `inject_app_id` — stamps `app_id` on each seeded row (default `true`);
|
|
42
|
+
set `false` for global tables like `types`.
|
|
43
|
+
- `ignore_duplicates` — `ignoreDuplicates` on the upsert; `types` rows
|
|
44
|
+
always get it (`.framework/cli/commands/install-app.ts`).
|
|
45
|
+
- `permissions_patch` — a types-table entry that merges
|
|
46
|
+
`record_permissions` into existing design schemas instead of
|
|
47
|
+
overwriting; ranks just after `types` in apply order.
|
|
48
|
+
- If no `seed` array is declared, the conventional files
|
|
49
|
+
`seed/roles.json`, `seed/types.json`, `seed/link-types.json` are used
|
|
50
|
+
(`.framework/cli/commands/install-app.ts`).
|
|
51
|
+
|
|
52
|
+
Seeds apply in dependency order — `roles`, `types`, `link_types`,
|
|
53
|
+
`actions`, `ai_agents`, `prompt_configs`, `pipelines`, `triggers` —
|
|
54
|
+
regardless of declaration order
|
|
55
|
+
(`.framework/cli/commands/install-app.ts`). Pipeline seeds are
|
|
56
|
+
validated against `public.actions` stage slugs, and trigger seeds
|
|
57
|
+
resolve `pipeline_slug` → `pipeline_id`.
|
|
58
|
+
|
|
59
|
+
3. **Package it.** Ship the app directory as an npm package — declare
|
|
60
|
+
every shippable entry in `package.json`'s `files` array (npm packs
|
|
61
|
+
nothing else). Include `index.tsx`, `manifest.json`, and optionally `pages/`,
|
|
62
|
+
`components/`, `hooks/`, `utils/`, `functions/`, `seed/`, `public/` —
|
|
63
|
+
the installer copies a fixed allowlist (`APP_COPY_ENTRIES` in
|
|
64
|
+
`.framework/cli/commands/install-app.ts`: those dirs plus `lib/`,
|
|
65
|
+
`views/`, `docs/`, `api/`, `installer/`, `mcp/`, `migrations*`,
|
|
66
|
+
`tests/`, `standalone.config.json`, `agent/`). `agent/` is staged —
|
|
67
|
+
`agent/skills/` lands at `custom/.devin/skills/` (requires
|
|
68
|
+
`manifest.agent.skill_prefix`) and `agent/instructions/` lands at
|
|
69
|
+
`custom/apps/<slug>/src/instructions/`; see
|
|
70
|
+
[App manifest reference](../reference/app-manifest.md). Validate locally
|
|
71
|
+
with `npx spine-framework validate-app`.
|
|
72
|
+
|
|
73
|
+
4. **Install.**
|
|
74
|
+
|
|
75
|
+
```bash
|
|
76
|
+
npx spine-framework install-app <package>
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
Flow: `npm install <package>` → resolve slug → copy files to
|
|
80
|
+
`custom/apps/<slug>/` (a reinstall preserves the local `manifest.json`)
|
|
81
|
+
→ `runMigrations` → `seedApp` → sync the `registration` block into
|
|
82
|
+
`apps.config` and `spine.config.json`
|
|
83
|
+
(`.framework/cli/commands/install-app.ts`). Requires
|
|
84
|
+
`SUPABASE_URL` + `SUPABASE_DB_PASSWORD` + `SUPABASE_SERVICE_ROLE_KEY`;
|
|
85
|
+
`--no-seed` skips all DB work.
|
|
86
|
+
|
|
87
|
+
5. **Update paths.**
|
|
88
|
+
|
|
89
|
+
- `update-app <package>` — `npm install @latest` + copy files + sync
|
|
90
|
+
registration. **It does not re-run migrations or seeds.**
|
|
91
|
+
- `update-db-app <slug>` — upserts the `apps` row from the local
|
|
92
|
+
`manifest.json` and syncs registration; no npm, no file changes.
|
|
93
|
+
- `seed-app <slug>` — re-runs just the seed block for a local app;
|
|
94
|
+
`--reconcile-owned-types` interactively updates `manifest.owned_types`
|
|
95
|
+
slugs whose DB `design_schema` drifted from the seed.
|
|
96
|
+
|
|
97
|
+
6. **Uninstall.** `uninstall-app <package>` removes
|
|
98
|
+
`custom/apps/<slug>/`, removes the app's skills
|
|
99
|
+
(`custom/.devin/skills/<prefix>_*` when `manifest.agent.skill_prefix`
|
|
100
|
+
is declared), and runs `npm uninstall` — and nothing else. The
|
|
101
|
+
`--keep-data` flag is parsed but never read: seeded rows are **not**
|
|
102
|
+
deleted either way (`.framework/cli/commands/install-app.ts`).
|
|
103
|
+
|
|
104
|
+
## Verify
|
|
105
|
+
|
|
106
|
+
- `npx spine-framework status` lists the installed app and version.
|
|
107
|
+
- `GET /api/apps?action=get&slug=<slug>` shows the upserted row;
|
|
108
|
+
`migrations status` shows migration state.
|
|
109
|
+
- Seeded rows exist: `GET /api/types?action=list` (or the seeded table's
|
|
110
|
+
endpoint) includes the app's types/roles.
|
|
111
|
+
|
|
112
|
+
## Common mistakes
|
|
113
|
+
|
|
114
|
+
- **Types not global.** Forgetting `inject_app_id: false` on a types seed
|
|
115
|
+
injects `app_id` into global type rows — and `conflict: "kind,slug"`
|
|
116
|
+
means a second app with the same slug silently skips or errors on
|
|
117
|
+
schema drift. Prefix slugs.
|
|
118
|
+
- **Expecting `uninstall --keep-data` to matter** — it is a no-op; seeded
|
|
119
|
+
rows always survive uninstall.
|
|
120
|
+
- **`update-app` doesn't re-seed** — new seed rows in the package never
|
|
121
|
+
reach the DB; run `seed-app <slug>` or `update-db-app` after.
|
|
122
|
+
- **Seed rows referencing unseeded slugs** — `owned_types`,
|
|
123
|
+
`pipeline_slug`, and `send_notification` recipients are validated
|
|
124
|
+
fail-closed before any DB writes; fix the manifest, don't retry.
|
|
125
|
+
- **`SUPABASE_URL` must be a hosted `https://<ref>.supabase.co` URL** —
|
|
126
|
+
the migrator rejects anything else before connecting
|
|
127
|
+
(`.framework/cli/commands/migrate.ts`).
|
|
128
|
+
|
|
129
|
+
## Reference
|
|
130
|
+
|
|
131
|
+
- [App manifest reference](../reference/app-manifest.md) ·
|
|
132
|
+
[Seeds reference](../reference/seeds.md) ·
|
|
133
|
+
[CLI reference](../reference/cli.md) ·
|
|
134
|
+
[spine.config.json reference](../reference/spine-config.md)
|
|
135
|
+
- [Apps and renderers](../concepts/apps-and-renderers.md) ·
|
|
136
|
+
[Write a migration](./write-a-migration.md)
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Register an app
|
|
3
|
+
audience: developers, agents
|
|
4
|
+
kind: task
|
|
5
|
+
verified_against: b3b3ae0
|
|
6
|
+
verified_on: 2026-11-01
|
|
7
|
+
source_files:
|
|
8
|
+
- .framework/cli/commands/create-app.ts
|
|
9
|
+
- .framework/cli/commands/install-app.ts
|
|
10
|
+
- .framework/functions/apps.ts
|
|
11
|
+
- .framework/functions/_shared/app-manifest.ts
|
|
12
|
+
- .framework/src/App.tsx
|
|
13
|
+
- .framework/src/components/app-shell/GenericAppShell.tsx
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
# Register an app
|
|
17
|
+
|
|
18
|
+
## Goal
|
|
19
|
+
|
|
20
|
+
Put an app in the `public.apps` registry so it appears in navigation, mounts
|
|
21
|
+
at a route, and renders — either by the generic renderer (schema-driven) or
|
|
22
|
+
by your own code (`custom/apps/<slug>/`).
|
|
23
|
+
|
|
24
|
+
Two decisions up front:
|
|
25
|
+
|
|
26
|
+
1. **Renderer** — `generic` (platform draws list/detail pages from each
|
|
27
|
+
type's `design_schema.views`) or `custom` (your `index.tsx` draws
|
|
28
|
+
everything). `none` registers data/types with no UI.
|
|
29
|
+
2. **Mount point (`route_prefix`)** — where the app lives in the URL space:
|
|
30
|
+
`/my-app` (default), `/` (site root), or a nested prefix. See
|
|
31
|
+
[Mounting](../concepts/apps-and-renderers.md#mounting-directory-or-root)
|
|
32
|
+
for the full rules — the short version:
|
|
33
|
+
|
|
34
|
+
| Mount | Effect |
|
|
35
|
+
|---|---|
|
|
36
|
+
| `/my-app` (default) | App owns `/my-app/*`. Safe default. |
|
|
37
|
+
| `/` | App owns `/*`; renders at `/` for non-admins. **One app only** — must render its own not-found for unknown paths. |
|
|
38
|
+
| `/crm/sales` | Nested; longest-prefix-first. Works, rarely needed. |
|
|
39
|
+
|
|
40
|
+
## Steps
|
|
41
|
+
|
|
42
|
+
### Option A — scaffold a custom app
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
npx spine-framework create-app <slug>
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Creates `custom/apps/<slug>/` (`manifest.json` + `index.tsx` +
|
|
49
|
+
`components/`) and inserts the `apps` row with `renderer: 'custom'` and
|
|
50
|
+
`route_prefix: /<slug>` (`.framework/cli/commands/create-app.ts`).
|
|
51
|
+
|
|
52
|
+
Then edit `custom/apps/<slug>/manifest.json` — set `route_prefix`,
|
|
53
|
+
`required_roles`, `nav_items`, and `routes`. Sync to the DB:
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
npx spine-framework update-db-app <slug>
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
### Option B — register a generic app
|
|
60
|
+
|
|
61
|
+
Generic apps are registry rows plus nav items — no app directory is needed
|
|
62
|
+
(the shell renders from type schemas):
|
|
63
|
+
|
|
64
|
+
1. Ensure every type the app will serve has complete `design_schema` —
|
|
65
|
+
`fields`, `views.default_list`, `views.default_detail` (see
|
|
66
|
+
[Design schema](../concepts/design-schema.md)).
|
|
67
|
+
2. Create the `apps` row — via the admin UI (Admin → Apps → New) or the API:
|
|
68
|
+
|
|
69
|
+
```ts
|
|
70
|
+
await apiFetch('/api/apps?action=create', {
|
|
71
|
+
method: 'POST',
|
|
72
|
+
body: JSON.stringify({
|
|
73
|
+
slug: 'taskboard', name: 'Taskboard',
|
|
74
|
+
renderer: 'generic',
|
|
75
|
+
route_prefix: '/taskboard',
|
|
76
|
+
min_role: 'member',
|
|
77
|
+
is_active: true,
|
|
78
|
+
nav_items: [
|
|
79
|
+
{ title: 'Tasks', path: '/taskboard/tasks', type_slug: 'task', order: 1 }
|
|
80
|
+
]
|
|
81
|
+
})
|
|
82
|
+
})
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
`nav_items` entries carrying `type_slug` (and optional `view`) get
|
|
86
|
+
generated list/detail/new routes. Omit `view` to use `default_list` +
|
|
87
|
+
`default_detail` — a nav item cannot pick list and detail views
|
|
88
|
+
independently ([manifest reference](../reference/app-manifest.md)).
|
|
89
|
+
3. Log out/in (or refresh) so the registry reloads.
|
|
90
|
+
|
|
91
|
+
### Option C — install a packaged app
|
|
92
|
+
|
|
93
|
+
```bash
|
|
94
|
+
npx spine-framework install-app <package>
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
The package's `manifest.json` declares `route_prefix` and everything else —
|
|
98
|
+
see [Package and install an app](package-and-install-an-app.md).
|
|
99
|
+
|
|
100
|
+
## Verify
|
|
101
|
+
|
|
102
|
+
- `GET /api/apps?action=get&slug=<slug>` returns the row with your
|
|
103
|
+
`route_prefix`, `renderer`, `nav_items`.
|
|
104
|
+
- Navigating to `<route_prefix>` renders the app (generic: list page;
|
|
105
|
+
custom: your `index.tsx`). For `/`, members land on it directly;
|
|
106
|
+
`system_admin` still lands on `/spine-framework/admin`.
|
|
107
|
+
- `useAppPath`-built links inside the app resolve correctly at the chosen
|
|
108
|
+
mount point.
|
|
109
|
+
|
|
110
|
+
## Common mistakes
|
|
111
|
+
|
|
112
|
+
- **Two apps mounted at `/`** — both compile to `/*`; ordering silently
|
|
113
|
+
decides which renders. The registry won't stop you; don't do it.
|
|
114
|
+
- **Root app with no internal not-found** — a `/`-mounted `/*` shadows the
|
|
115
|
+
global `*` route; unmatched paths inside the app show blank/error instead
|
|
116
|
+
of your 404. Add `<Route path="*" element={...}>` in the app's routes.
|
|
117
|
+
- **Hardcoded paths** — `navigate('/tasks')` or `to="/tasks"` escapes a
|
|
118
|
+
directory-mounted app. Use `useAppPath()` from `@core/hooks/useAppPath`
|
|
119
|
+
or relative routes.
|
|
120
|
+
- **`nav_items` absolute paths missing the prefix** — `/tasks` navigates
|
|
121
|
+
outside the app and 404s. Use either `/taskboard/tasks` (absolute
|
|
122
|
+
incl. prefix) or `tasks` (relative — the shell prefixes it).
|
|
123
|
+
- **Expecting public access** — `is_public`/`auth_required` exist on the
|
|
124
|
+
manifest but are not wired into the unauthenticated router; every app is
|
|
125
|
+
behind login.
|
|
126
|
+
- **Generic app, thin schema** — `renderer: 'generic'` with types lacking
|
|
127
|
+
`views.default_list`/`default_detail` renders "No schema available."
|
|
128
|
+
|
|
129
|
+
## Reference
|
|
130
|
+
|
|
131
|
+
- [Apps and renderers](../concepts/apps-and-renderers.md) — mounting model,
|
|
132
|
+
role gating, renderer semantics
|
|
133
|
+
- [Apps](../apps.md) — the registry entity page
|
|
134
|
+
- [App manifest reference](../reference/app-manifest.md) — every manifest
|
|
135
|
+
field incl. `route_prefix`, `nav_items`, `registration`
|
|
136
|
+
- [Build a custom page](build-a-custom-page.md) ·
|
|
137
|
+
[Frontend SDK](../reference/frontend-sdk.md) — `useAppPath`, `apiFetch`
|