@softize/opus 8.6.6
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/CHANGELOG.md +1616 -0
- package/LICENSE +21 -0
- package/README.md +113 -0
- package/bin/cli.mjs +528 -0
- package/bin/lib/check.mjs +307 -0
- package/bin/lib/components.mjs +151 -0
- package/bin/lib/create.mjs +208 -0
- package/bin/lib/db-check-runner.mjs +86 -0
- package/bin/lib/db-migrate-runner.mjs +89 -0
- package/bin/lib/db-scaffold-runner.mjs +84 -0
- package/bin/lib/db.mjs +261 -0
- package/bin/lib/docs-include.mjs +48 -0
- package/bin/lib/gen-dicts.mjs +134 -0
- package/bin/lib/gen-docs.mjs +288 -0
- package/bin/lib/gen-manifest.mjs +102 -0
- package/bin/lib/gen-openapi.mjs +195 -0
- package/bin/lib/gen-runner.mjs +472 -0
- package/bin/lib/gen-stubs.mjs +463 -0
- package/bin/lib/gen.mjs +311 -0
- package/bin/lib/init.mjs +514 -0
- package/bin/lib/introspect.mjs +107 -0
- package/bin/lib/mcp.mjs +85 -0
- package/bin/lib/postinstall.mjs +56 -0
- package/docs/chat-event-protocol.md +85 -0
- package/docs/code-style.md +16 -0
- package/docs/data-layer.md +246 -0
- package/docs/ownership-vs-shadcn-lock.md +102 -0
- package/docs/protocol.md +2053 -0
- package/docs/releasing.md +110 -0
- package/docs/shellnav.md +131 -0
- package/package.json +338 -0
- package/registry/hooks/hooks.json +26 -0
- package/registry/hooks/link-memory-on-start.mjs +46 -0
- package/registry/hooks/opus-check-on-stop.mjs +114 -0
- package/registry/skills/create-action/SKILL.md +49 -0
- package/registry/skills/create-action/scaffold.mjs +122 -0
- package/registry/templates/app/_gitignore +3 -0
- package/registry/templates/app/_npmrc +1 -0
- package/registry/templates/app/_opus/_gitignore +5 -0
- package/registry/templates/app/_prettierrc.json +6 -0
- package/registry/templates/app/index.html +13 -0
- package/registry/templates/app/opus.config.ts +16 -0
- package/registry/templates/app/package.json +43 -0
- package/registry/templates/app/pnpm-workspace.yaml +11 -0
- package/registry/templates/app/public/favicon.svg +4 -0
- package/registry/templates/app/src/App.tsx +37 -0
- package/registry/templates/app/src/domains/tasks/actions/list.test.ts +34 -0
- package/registry/templates/app/src/domains/tasks/actions/list.ts +33 -0
- package/registry/templates/app/src/domains/tasks/index.ts +13 -0
- package/registry/templates/app/src/index.css +18 -0
- package/registry/templates/app/src/main.tsx +25 -0
- package/registry/templates/app/tsconfig.json +20 -0
- package/registry/templates/app/vite.config.ts +46 -0
- package/registry/templates/monorepo/_gitignore +3 -0
- package/registry/templates/monorepo/_npmrc +1 -0
- package/registry/templates/monorepo/package.json +9 -0
- package/registry/templates/monorepo/pnpm-workspace.yaml +14 -0
- package/src/ai/ask.ts +64 -0
- package/src/ai/drivers/anthropic.ts +309 -0
- package/src/ai/index.ts +17 -0
- package/src/audit/drivers/console.ts +117 -0
- package/src/audit/drivers/pg.ts +172 -0
- package/src/audit/index.ts +51 -0
- package/src/auth/drivers/better-auth.ts +103 -0
- package/src/auth/drivers/jwt.ts +188 -0
- package/src/auth/index.ts +9 -0
- package/src/client/drivers/fetch.ts +202 -0
- package/src/client/index.ts +22 -0
- package/src/core/actions.ts +110 -0
- package/src/core/audit.ts +239 -0
- package/src/core/contracts.ts +137 -0
- package/src/core/domain.ts +310 -0
- package/src/core/errors.ts +181 -0
- package/src/core/index.ts +174 -0
- package/src/core/logical-type.ts +31 -0
- package/src/core/reactions.ts +81 -0
- package/src/core/runtime.ts +1167 -0
- package/src/core/schedules.ts +41 -0
- package/src/core/types.ts +1356 -0
- package/src/data/drivers/kysely.ts +389 -0
- package/src/data/index.ts +10 -0
- package/src/data/readonly-pool.ts +160 -0
- package/src/dsl/eval.ts +136 -0
- package/src/dsl/index.ts +29 -0
- package/src/dsl/kysely.ts +230 -0
- package/src/dsl/loads.ts +123 -0
- package/src/dsl/parser.ts +423 -0
- package/src/dsl/types.ts +113 -0
- package/src/events/drivers/mitt.ts +70 -0
- package/src/events/index.ts +9 -0
- package/src/log/drivers/pino.ts +57 -0
- package/src/log/index.ts +9 -0
- package/src/mcp/index.ts +62 -0
- package/src/queue/drivers/bullmq.ts +190 -0
- package/src/queue/index.ts +9 -0
- package/src/scheduler/drivers/node-cron.ts +93 -0
- package/src/scheduler/every.ts +45 -0
- package/src/scheduler/index.ts +9 -0
- package/src/schema/drivers/zod.ts +765 -0
- package/src/schema/entity.ts +439 -0
- package/src/schema/format/locale.ts +144 -0
- package/src/schema/index.ts +65 -0
- package/src/schema/openapi.ts +302 -0
- package/src/schema/scaffold.ts +160 -0
- package/src/server/drivers/fastify.ts +224 -0
- package/src/server/drivers/node.ts +386 -0
- package/src/server/index.ts +142 -0
- package/src/storage/drivers/fs.ts +90 -0
- package/src/storage/drivers/s3.ts +117 -0
- package/src/storage/index.ts +27 -0
- package/src/testing/fake.ts +298 -0
- package/src/testing/index.ts +324 -0
- package/src/ui/components/patterns/action-form-card.tsx +48 -0
- package/src/ui/components/patterns/action-list-dialog.tsx +93 -0
- package/src/ui/components/patterns/app-shell.tsx +227 -0
- package/src/ui/components/patterns/confirm.tsx +226 -0
- package/src/ui/components/patterns/data-state.tsx +75 -0
- package/src/ui/components/patterns/form-dialog.tsx +64 -0
- package/src/ui/components/patterns/form.tsx +584 -0
- package/src/ui/components/patterns/list.tsx +1488 -0
- package/src/ui/components/patterns/page.tsx +46 -0
- package/src/ui/components/patterns/section-shell.tsx +246 -0
- package/src/ui/components/patterns/shell-nav.tsx +150 -0
- package/src/ui/components/patterns/sidebar.tsx +89 -0
- package/src/ui/components/patterns/split.tsx +93 -0
- package/src/ui/components/patterns/trigger.tsx +196 -0
- package/src/ui/components/patterns/view.tsx +84 -0
- package/src/ui/components/primitives/accordion.tsx +64 -0
- package/src/ui/components/primitives/alert-dialog.tsx +190 -0
- package/src/ui/components/primitives/alert.tsx +116 -0
- package/src/ui/components/primitives/aspect-ratio.tsx +9 -0
- package/src/ui/components/primitives/avatar.tsx +107 -0
- package/src/ui/components/primitives/badge.tsx +37 -0
- package/src/ui/components/primitives/breadcrumb.tsx +109 -0
- package/src/ui/components/primitives/button-group.tsx +83 -0
- package/src/ui/components/primitives/button.tsx +102 -0
- package/src/ui/components/primitives/calendar.tsx +218 -0
- package/src/ui/components/primitives/card.tsx +56 -0
- package/src/ui/components/primitives/carousel.tsx +239 -0
- package/src/ui/components/primitives/chat.tsx +407 -0
- package/src/ui/components/primitives/checkbox.tsx +30 -0
- package/src/ui/components/primitives/collapsible.tsx +31 -0
- package/src/ui/components/primitives/command.tsx +182 -0
- package/src/ui/components/primitives/composer.tsx +121 -0
- package/src/ui/components/primitives/copyable.tsx +50 -0
- package/src/ui/components/primitives/dialog.tsx +147 -0
- package/src/ui/components/primitives/drawer.tsx +141 -0
- package/src/ui/components/primitives/empty.tsx +104 -0
- package/src/ui/components/primitives/field.tsx +246 -0
- package/src/ui/components/primitives/icon-picker.tsx +180 -0
- package/src/ui/components/primitives/input-group.tsx +168 -0
- package/src/ui/components/primitives/input-otp.tsx +75 -0
- package/src/ui/components/primitives/input.tsx +72 -0
- package/src/ui/components/primitives/item.tsx +193 -0
- package/src/ui/components/primitives/kbd.tsx +28 -0
- package/src/ui/components/primitives/label.tsx +22 -0
- package/src/ui/components/primitives/markdown.tsx +35 -0
- package/src/ui/components/primitives/menu.tsx +255 -0
- package/src/ui/components/primitives/pagination.tsx +127 -0
- package/src/ui/components/primitives/popover.tsx +87 -0
- package/src/ui/components/primitives/progress.tsx +29 -0
- package/src/ui/components/primitives/radio-group.tsx +43 -0
- package/src/ui/components/primitives/resizable.tsx +51 -0
- package/src/ui/components/primitives/scroll-area.tsx +56 -0
- package/src/ui/components/primitives/select.tsx +479 -0
- package/src/ui/components/primitives/separator.tsx +26 -0
- package/src/ui/components/primitives/skeleton.tsx +13 -0
- package/src/ui/components/primitives/slider.tsx +61 -0
- package/src/ui/components/primitives/sonner.tsx +46 -0
- package/src/ui/components/primitives/spinner.tsx +29 -0
- package/src/ui/components/primitives/switch.tsx +33 -0
- package/src/ui/components/primitives/table.tsx +114 -0
- package/src/ui/components/primitives/tabs.tsx +104 -0
- package/src/ui/components/primitives/textarea.tsx +18 -0
- package/src/ui/components/primitives/toggle-group.tsx +81 -0
- package/src/ui/components/primitives/toggle.tsx +45 -0
- package/src/ui/components/primitives/tooltip.tsx +55 -0
- package/src/ui/components/primitives/truncate.tsx +49 -0
- package/src/ui/docs/DocBrowser.tsx +90 -0
- package/src/ui/docs/changelog.tsx +80 -0
- package/src/ui/docs/content/accordion.md +86 -0
- package/src/ui/docs/content/action-form-card.md +24 -0
- package/src/ui/docs/content/action-form-dialog.md +30 -0
- package/src/ui/docs/content/action-form.md +125 -0
- package/src/ui/docs/content/action-list-dialog.md +68 -0
- package/src/ui/docs/content/action-list.md +194 -0
- package/src/ui/docs/content/action-trigger.md +72 -0
- package/src/ui/docs/content/action-view.md +47 -0
- package/src/ui/docs/content/actions.md +138 -0
- package/src/ui/docs/content/ai.md +112 -0
- package/src/ui/docs/content/alert-dialog.md +73 -0
- package/src/ui/docs/content/alert.md +69 -0
- package/src/ui/docs/content/app-shell.md +155 -0
- package/src/ui/docs/content/aspect-ratio.md +66 -0
- package/src/ui/docs/content/audit.md +84 -0
- package/src/ui/docs/content/auth.md +70 -0
- package/src/ui/docs/content/avatar.md +94 -0
- package/src/ui/docs/content/badge.md +48 -0
- package/src/ui/docs/content/breadcrumb.md +87 -0
- package/src/ui/docs/content/button-group.md +71 -0
- package/src/ui/docs/content/button.md +60 -0
- package/src/ui/docs/content/calendar.md +62 -0
- package/src/ui/docs/content/card.md +49 -0
- package/src/ui/docs/content/carousel.md +85 -0
- package/src/ui/docs/content/chat.md +69 -0
- package/src/ui/docs/content/checkbox.md +75 -0
- package/src/ui/docs/content/cli.md +58 -0
- package/src/ui/docs/content/collapsible.md +64 -0
- package/src/ui/docs/content/command.md +56 -0
- package/src/ui/docs/content/composer.md +50 -0
- package/src/ui/docs/content/confirm.md +120 -0
- package/src/ui/docs/content/copyable.md +30 -0
- package/src/ui/docs/content/customization.md +110 -0
- package/src/ui/docs/content/cycle.md +34 -0
- package/src/ui/docs/content/data-state.md +47 -0
- package/src/ui/docs/content/data.md +99 -0
- package/src/ui/docs/content/dialog.md +60 -0
- package/src/ui/docs/content/drawer.md +55 -0
- package/src/ui/docs/content/empty.md +66 -0
- package/src/ui/docs/content/events.md +61 -0
- package/src/ui/docs/content/field.md +58 -0
- package/src/ui/docs/content/getting-started.md +109 -0
- package/src/ui/docs/content/icon-picker.md +51 -0
- package/src/ui/docs/content/input-group.md +78 -0
- package/src/ui/docs/content/input-otp.md +72 -0
- package/src/ui/docs/content/input.md +78 -0
- package/src/ui/docs/content/item.md +84 -0
- package/src/ui/docs/content/kbd.md +62 -0
- package/src/ui/docs/content/label.md +32 -0
- package/src/ui/docs/content/log.md +55 -0
- package/src/ui/docs/content/markdown.md +41 -0
- package/src/ui/docs/content/mcp.md +44 -0
- package/src/ui/docs/content/menu.md +114 -0
- package/src/ui/docs/content/microcopy.md +83 -0
- package/src/ui/docs/content/page.md +34 -0
- package/src/ui/docs/content/pagination.md +99 -0
- package/src/ui/docs/content/popover.md +49 -0
- package/src/ui/docs/content/progress.md +69 -0
- package/src/ui/docs/content/queue.md +62 -0
- package/src/ui/docs/content/radio-group.md +77 -0
- package/src/ui/docs/content/resizable.md +86 -0
- package/src/ui/docs/content/router.md +56 -0
- package/src/ui/docs/content/runtime.md +77 -0
- package/src/ui/docs/content/scheduler.md +66 -0
- package/src/ui/docs/content/scroll-area.md +89 -0
- package/src/ui/docs/content/section-shell.md +121 -0
- package/src/ui/docs/content/select.md +342 -0
- package/src/ui/docs/content/separator.md +33 -0
- package/src/ui/docs/content/sidebar.md +38 -0
- package/src/ui/docs/content/skeleton.md +34 -0
- package/src/ui/docs/content/slider.md +64 -0
- package/src/ui/docs/content/spinner.md +37 -0
- package/src/ui/docs/content/split.md +33 -0
- package/src/ui/docs/content/storage.md +69 -0
- package/src/ui/docs/content/switch.md +69 -0
- package/src/ui/docs/content/table.md +102 -0
- package/src/ui/docs/content/tabs.md +94 -0
- package/src/ui/docs/content/testing.md +89 -0
- package/src/ui/docs/content/textarea.md +30 -0
- package/src/ui/docs/content/toast.md +67 -0
- package/src/ui/docs/content/toggle-group.md +81 -0
- package/src/ui/docs/content/toggle.md +72 -0
- package/src/ui/docs/content/tokens.md +171 -0
- package/src/ui/docs/content/tooltip.md +50 -0
- package/src/ui/docs/content/truncate.md +37 -0
- package/src/ui/docs/content/ui.md +40 -0
- package/src/ui/docs/content/upgrading.md +48 -0
- package/src/ui/docs/doc-client.tsx +214 -0
- package/src/ui/docs/doc.tsx +301 -0
- package/src/ui/docs/folder.tsx +149 -0
- package/src/ui/docs/index.ts +21 -0
- package/src/ui/docs/markdown.tsx +130 -0
- package/src/ui/docs/md-raw.d.ts +4 -0
- package/src/ui/docs/plugin.ts +104 -0
- package/src/ui/docs/registry.tsx +424 -0
- package/src/ui/docs/standalone.tsx +107 -0
- package/src/ui/drivers/react.tsx +627 -0
- package/src/ui/index.ts +92 -0
- package/src/ui/lib/cn.ts +10 -0
- package/src/ui/lib/zod-pt-br.ts +38 -0
- package/src/ui/meta.ts +412 -0
- package/src/ui/react.tsx +235 -0
- package/src/ui/router.ts +96 -0
- package/src/ui/theme.css +234 -0
- package/src/vite/design.ts +652 -0
- package/src/vite/index.ts +8 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Softize
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
# Opus
|
|
2
|
+
|
|
3
|
+
> **Status:** v0 — pacote único `@softize/opus` com subpath exports (core + adapters). Uma versão, sem semver por módulo.
|
|
4
|
+
|
|
5
|
+
End-to-end action protocol for TypeScript. Declare once — input, authorization, execution, audit, feedback — and let adapters materialize it across UI, client, server, and log.
|
|
6
|
+
|
|
7
|
+
**O Opus não é framework.** Não tem ciclo de vida próprio. É o contrato comum que todas as camadas falam.
|
|
8
|
+
|
|
9
|
+
## Filosofia
|
|
10
|
+
|
|
11
|
+
- **Pipeline declarado, não escrita declarada.** Action é qualquer unidade que flui pelo pipeline (validate → load → authorize → execute → audit → return), independente de mudar estado. Cobre write (form, simple) e read estruturado (list, view).
|
|
12
|
+
- **Três primitivas declarativas.** Action (humano/AI dispara) + Reaction (evento dispara) + Schedule (tempo dispara). Fecha o ciclo de ação proativa.
|
|
13
|
+
- **Provenance rastreável.** Toda execução carrega origem estruturada (http, schedule, reaction, ai-agent, background, ...) com cadeia preservada via `originalProvenance`. Audit registra a árvore de causalidade do user até a action final.
|
|
14
|
+
- **Kind como discriminator.** v0 suporta `simple`, `form`, `list`, `view`.
|
|
15
|
+
- **Contrato, não feature.** Tudo que entra no core padroniza forma; tudo que faz trabalho usa lib externa.
|
|
16
|
+
- **Fail-closed por default.** Action sem `authorize` é negada. Audit default-on.
|
|
17
|
+
- **Adapters plugáveis.** Cada categoria é uma superfície (subpath) com interface fechada e drivers trocáveis.
|
|
18
|
+
- **Doc é fonte da verdade.** `docs/protocol.md` — 16 seções fechadas (15 + glossário).
|
|
19
|
+
|
|
20
|
+
## Superfícies
|
|
21
|
+
|
|
22
|
+
Um pacote (`@softize/opus`), uma versão. Cada categoria abaixo é um **subpath ESM**, não um pacote separado.
|
|
23
|
+
|
|
24
|
+
| Superfície | Drivers | Propósito |
|
|
25
|
+
|---|---|---|
|
|
26
|
+
| `@softize/opus` (core) | — | Protocolo, `defineAction`, `defineEntity`, runtime |
|
|
27
|
+
| `@softize/opus/schema` | `/zod`, `/openapi` | Logical types (`t.*`) + OpenAPI generator |
|
|
28
|
+
| `@softize/opus/server` | `/fastify` | HTTP transport + endpoint mount |
|
|
29
|
+
| `@softize/opus/client` | `/fetch` | Client adapter pra invocar actions |
|
|
30
|
+
| `@softize/opus/ui` | `/react` | Componentes + Provider + hooks (`useAction`, `useListAction`) |
|
|
31
|
+
| `@softize/opus/data` | `/kysely` | Data adapter (`ctx.db`, `ctx.repo`) |
|
|
32
|
+
| `@softize/opus/auth` | `/jwt`, `/better-auth` | Auth adapter (`ctx.user`, `ctx.can`) |
|
|
33
|
+
| `@softize/opus/audit` | `/console`, `/pg` | Audit sinks |
|
|
34
|
+
| `@softize/opus/log` | `/pino` | Logger adapter (console default no core) |
|
|
35
|
+
| `@softize/opus/queue` | `/bullmq` | Background job execution |
|
|
36
|
+
| `@softize/opus/events` | `/mitt` | EventBus (in-process) |
|
|
37
|
+
| `@softize/opus/scheduler` | `/node-cron` | Schedule adapter (ação iniciada por tempo) |
|
|
38
|
+
|
|
39
|
+
Drivers via subpath ESM (estilo Drizzle): `@softize/opus/server/fastify`, `@softize/opus/data/kysely`.
|
|
40
|
+
|
|
41
|
+
## Quick start
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
pnpm add @softize/opus zod fastify
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
```ts
|
|
48
|
+
import Fastify from 'fastify'
|
|
49
|
+
import { z } from 'zod'
|
|
50
|
+
import { createRuntime, defineAction } from '@softize/opus'
|
|
51
|
+
import { t } from '@softize/opus/schema/zod'
|
|
52
|
+
import { fastifyServer } from '@softize/opus/server/fastify'
|
|
53
|
+
|
|
54
|
+
const archiveDeal = defineAction({
|
|
55
|
+
name: 'deal.archive',
|
|
56
|
+
kind: 'simple',
|
|
57
|
+
input: z.object({ dealId: z.string() }),
|
|
58
|
+
output: z.object({ archivedAt: t.datetime() }),
|
|
59
|
+
authorize: (ctx) => ctx.can('deal:archive'),
|
|
60
|
+
handler: async (_ctx, input) => ({ archivedAt: new Date().toISOString() }),
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
const app = Fastify()
|
|
64
|
+
const server = fastifyServer({ app })
|
|
65
|
+
const runtime = createRuntime({ server })
|
|
66
|
+
|
|
67
|
+
runtime.register([archiveDeal])
|
|
68
|
+
await runtime.start()
|
|
69
|
+
server.mountEndpoints({ openapi: true })
|
|
70
|
+
|
|
71
|
+
await app.listen({ port: 3000 })
|
|
72
|
+
// POST http://localhost:3000/api/deal/archive
|
|
73
|
+
// GET http://localhost:3000/openapi.json
|
|
74
|
+
// GET http://localhost:3000/health
|
|
75
|
+
// GET http://localhost:3000/ready
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
## Estrutura
|
|
79
|
+
|
|
80
|
+
```
|
|
81
|
+
src/
|
|
82
|
+
core/ schema/ server/ client/ ui/
|
|
83
|
+
data/ auth/ audit/ log/ queue/ events/ scheduler/ dsl/
|
|
84
|
+
registry/ # scaffolds + skills (create-action) — geração, não runtime
|
|
85
|
+
bin/ # CLI (opus setup/gen/check/introspect/mcp) + libs
|
|
86
|
+
docs/
|
|
87
|
+
protocol.md # contrato completo (16 seções)
|
|
88
|
+
data-layer.md · releasing.md · code-style.md
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
## Status
|
|
92
|
+
|
|
93
|
+
- **v0**: 12 superfícies, 430 tests, 100% coverage.
|
|
94
|
+
- **v1+**: drivers adicionais (Hono, Drizzle, ArkType, Vue, Inngest, Redis events), camada de entidades (`defineEntity`), Conversya migration plan.
|
|
95
|
+
|
|
96
|
+
## Desenvolvimento
|
|
97
|
+
|
|
98
|
+
```bash
|
|
99
|
+
pnpm install # instala deps
|
|
100
|
+
pnpm typecheck # tsc --noEmit
|
|
101
|
+
pnpm test # vitest run
|
|
102
|
+
pnpm test:cov # com coverage report
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
## Publicar
|
|
106
|
+
|
|
107
|
+
Publicado no registry compartilhado da Softize (`registry.softize.com.br`), não no
|
|
108
|
+
npm público. `pnpm release [patch|minor|major|x.y.z]` bumpa + publica. Passo a passo
|
|
109
|
+
(verificar, testar local, auth) em [docs/releasing.md](docs/releasing.md).
|
|
110
|
+
|
|
111
|
+
## Licença
|
|
112
|
+
|
|
113
|
+
A decidir. Ver `docs/protocol.md` — Decisões em aberto.
|
package/bin/cli.mjs
ADDED
|
@@ -0,0 +1,528 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* @softize/opus CLI — copia templates do registry pro consumer.
|
|
4
|
+
*
|
|
5
|
+
* Uso:
|
|
6
|
+
* npx @softize/opus add <name> # copia registry/<name>.tsx
|
|
7
|
+
* npx @softize/opus add <name> --force # sobrescreve se existe
|
|
8
|
+
* npx @softize/opus list # lista templates disponíveis
|
|
9
|
+
* npx @softize/opus gen # gera manifest/openapi/docs/stubs
|
|
10
|
+
*
|
|
11
|
+
* Lê components.json do consumer pra resolver path destino (mesma config
|
|
12
|
+
* do shadcn). Default: <cwd>/src/components/action/<name>.tsx.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import { promises as fs } from 'node:fs'
|
|
16
|
+
import path from 'node:path'
|
|
17
|
+
import { fileURLToPath } from 'node:url'
|
|
18
|
+
|
|
19
|
+
import { cmdGen, helpGen } from './lib/gen.mjs'
|
|
20
|
+
import { cmdDb } from './lib/db.mjs'
|
|
21
|
+
import { scanDir, emptyScanVerdict } from './lib/check.mjs'
|
|
22
|
+
import { createMonorepo, createProject } from './lib/create.mjs'
|
|
23
|
+
import { initProject, setupUiFoundation } from './lib/init.mjs'
|
|
24
|
+
import { introspect } from './lib/introspect.mjs'
|
|
25
|
+
|
|
26
|
+
const __filename = fileURLToPath(import.meta.url)
|
|
27
|
+
const __dirname = path.dirname(__filename)
|
|
28
|
+
const PACKAGE_ROOT = path.resolve(__dirname, '..')
|
|
29
|
+
const REGISTRY_DIR = path.join(PACKAGE_ROOT, 'registry')
|
|
30
|
+
|
|
31
|
+
// =============================================================================
|
|
32
|
+
// Helpers
|
|
33
|
+
// =============================================================================
|
|
34
|
+
|
|
35
|
+
function log(level, msg) {
|
|
36
|
+
const colors = {
|
|
37
|
+
info: '\x1b[36m',
|
|
38
|
+
success: '\x1b[32m',
|
|
39
|
+
error: '\x1b[31m',
|
|
40
|
+
warn: '\x1b[33m',
|
|
41
|
+
}
|
|
42
|
+
const reset = '\x1b[0m'
|
|
43
|
+
console.log(`${colors[level]}${msg}${reset}`)
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
async function fileExists(p) {
|
|
47
|
+
try {
|
|
48
|
+
await fs.access(p)
|
|
49
|
+
return true
|
|
50
|
+
} catch {
|
|
51
|
+
return false
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
async function loadComponentsJson(cwd) {
|
|
56
|
+
const candidate = path.join(cwd, 'components.json')
|
|
57
|
+
if (!(await fileExists(candidate))) {
|
|
58
|
+
return null
|
|
59
|
+
}
|
|
60
|
+
try {
|
|
61
|
+
const raw = await fs.readFile(candidate, 'utf-8')
|
|
62
|
+
return JSON.parse(raw)
|
|
63
|
+
} catch (err) {
|
|
64
|
+
log('warn', `components.json existe mas não é JSON válido: ${err.message}`)
|
|
65
|
+
return null
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function resolveAlias(alias, fallback) {
|
|
70
|
+
if (typeof alias !== 'string') return fallback
|
|
71
|
+
// Pega só a parte após "@/" pq path é relativo a src/.
|
|
72
|
+
// Ex: "@/components" → "components".
|
|
73
|
+
if (alias.startsWith('@/')) {
|
|
74
|
+
return alias.slice(2)
|
|
75
|
+
}
|
|
76
|
+
return alias
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
async function resolveDestination(cwd, name) {
|
|
80
|
+
const components = await loadComponentsJson(cwd)
|
|
81
|
+
if (components === null) {
|
|
82
|
+
log(
|
|
83
|
+
'warn',
|
|
84
|
+
'Sem components.json. Usando default ./src/components/action/. Roda `npx shadcn init` se quiser configurar.',
|
|
85
|
+
)
|
|
86
|
+
return path.join(cwd, 'src/components/action', `${name}.tsx`)
|
|
87
|
+
}
|
|
88
|
+
const aliasComponents =
|
|
89
|
+
components.aliases?.components ?? '@/components'
|
|
90
|
+
const componentsRel = resolveAlias(aliasComponents, 'components')
|
|
91
|
+
return path.join(cwd, 'src', componentsRel, 'action', `${name}.tsx`)
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
async function listTemplates() {
|
|
95
|
+
if (!(await fileExists(REGISTRY_DIR))) {
|
|
96
|
+
log('error', `Registry não encontrado em ${REGISTRY_DIR}.`)
|
|
97
|
+
process.exit(1)
|
|
98
|
+
}
|
|
99
|
+
const files = await fs.readdir(REGISTRY_DIR)
|
|
100
|
+
return files
|
|
101
|
+
.filter((f) => f.endsWith('.tsx') || f.endsWith('.ts'))
|
|
102
|
+
.map((f) => f.replace(/\.(tsx|ts)$/, ''))
|
|
103
|
+
.sort()
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// =============================================================================
|
|
107
|
+
// Commands
|
|
108
|
+
// =============================================================================
|
|
109
|
+
|
|
110
|
+
async function cmdList() {
|
|
111
|
+
const templates = await listTemplates()
|
|
112
|
+
if (templates.length === 0) {
|
|
113
|
+
log(
|
|
114
|
+
'info',
|
|
115
|
+
'\nSem templates de cópia (os componentes de UI agora são LIB, não se copiam):\n' +
|
|
116
|
+
' • importe de `@softize/opus/ui/react` (Button, ActionForm, DropdownMenu…)\n' +
|
|
117
|
+
' • descubra o catálogo via `opus mcp` → tool `opus_list_components`\n',
|
|
118
|
+
)
|
|
119
|
+
return
|
|
120
|
+
}
|
|
121
|
+
log('info', `\n@softize/opus templates disponíveis:\n`)
|
|
122
|
+
for (const t of templates) {
|
|
123
|
+
console.log(` ${t}`)
|
|
124
|
+
}
|
|
125
|
+
console.log('')
|
|
126
|
+
log('info', `Adicionar: npx @softize/opus add <nome>\n`)
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
async function cmdAdd(name, options) {
|
|
130
|
+
if (typeof name !== 'string' || name.length === 0) {
|
|
131
|
+
log('error', 'Uso: npx @softize/opus add <name>')
|
|
132
|
+
process.exit(1)
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const templates = await listTemplates()
|
|
136
|
+
if (!templates.includes(name)) {
|
|
137
|
+
log('error', `Template "${name}" não existe.`)
|
|
138
|
+
console.log(`\nDisponíveis: ${templates.join(', ')}\n`)
|
|
139
|
+
process.exit(1)
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
const cwd = process.cwd()
|
|
143
|
+
const src = path.join(REGISTRY_DIR, `${name}.tsx`)
|
|
144
|
+
const dest = await resolveDestination(cwd, name)
|
|
145
|
+
|
|
146
|
+
if (await fileExists(dest)) {
|
|
147
|
+
if (options.force !== true) {
|
|
148
|
+
log(
|
|
149
|
+
'warn',
|
|
150
|
+
`Já existe em ${path.relative(cwd, dest)} — passa --force pra sobrescrever.`,
|
|
151
|
+
)
|
|
152
|
+
process.exit(1)
|
|
153
|
+
}
|
|
154
|
+
log('warn', `Sobrescrevendo ${path.relative(cwd, dest)}`)
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
await fs.mkdir(path.dirname(dest), { recursive: true })
|
|
158
|
+
const content = await fs.readFile(src, 'utf-8')
|
|
159
|
+
await fs.writeFile(dest, content, 'utf-8')
|
|
160
|
+
|
|
161
|
+
log('success', `✓ Criado ${path.relative(cwd, dest)}`)
|
|
162
|
+
console.log(
|
|
163
|
+
'\n Edita à vontade — esse arquivo é seu agora. Próximas runs com --force\n sobrescrevem suas mudanças.\n',
|
|
164
|
+
)
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
async function cmdCheck(dir) {
|
|
168
|
+
const root = path.resolve(process.cwd(), dir ?? '.')
|
|
169
|
+
const rel = path.relative(process.cwd(), root) || '.'
|
|
170
|
+
const { files, actions, contracts, findings, hasMarker } = await scanDir(root)
|
|
171
|
+
|
|
172
|
+
if (actions === 0) {
|
|
173
|
+
const verdict = emptyScanVerdict({ hasMarker, contracts })
|
|
174
|
+
// Projeto MARCADO (opus.json) sem actions passa vacuamente — server/lib/app sem
|
|
175
|
+
// domínio ainda. Sem marcador, "0 actions" segue sintoma (diretório errado ou
|
|
176
|
+
// padrão que o check não enxerga), não aprovação.
|
|
177
|
+
if (verdict.ok) {
|
|
178
|
+
log('success', `✓ opus check: ${verdict.message}`)
|
|
179
|
+
return
|
|
180
|
+
}
|
|
181
|
+
log('error', `✗ opus check: nenhuma action encontrada em ${rel} (${files} arquivos .ts); ${verdict.message}`)
|
|
182
|
+
process.exit(1)
|
|
183
|
+
}
|
|
184
|
+
const viaContrato = contracts > 0 ? ` (${contracts} contrato(s))` : ''
|
|
185
|
+
if (findings.length === 0) {
|
|
186
|
+
log('success', `✓ opus check: ${actions} action(s)${viaContrato}, 0 violação — padrão ok.`)
|
|
187
|
+
return
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
const byFile = new Map()
|
|
191
|
+
for (const f of findings) {
|
|
192
|
+
if (!byFile.has(f.file)) byFile.set(f.file, [])
|
|
193
|
+
byFile.get(f.file).push(f)
|
|
194
|
+
}
|
|
195
|
+
for (const [file, list] of byFile) {
|
|
196
|
+
log('warn', `\n${file}`)
|
|
197
|
+
for (const f of list.sort((a, b) => a.line - b.line)) {
|
|
198
|
+
console.log(` ${String(f.line).padStart(4)}: [${f.rule}] ${f.action} — ${f.message}`)
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
log('error', `\n✗ opus check: ${findings.length} violação(ões) em ${byFile.size}/${actions} action(s).`)
|
|
202
|
+
process.exit(1)
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
async function cmdCreate(dir, flags) {
|
|
206
|
+
if (!dir) {
|
|
207
|
+
log('error', 'Uso: opus create <dir> — o nome do diretório vira o nome do app (--monorepo: a raiz do workspace).')
|
|
208
|
+
process.exit(1)
|
|
209
|
+
}
|
|
210
|
+
const target = path.resolve(process.cwd(), dir)
|
|
211
|
+
const name = path.basename(target)
|
|
212
|
+
|
|
213
|
+
if (flags.monorepo) {
|
|
214
|
+
const r = await createMonorepo(REGISTRY_DIR, target, name)
|
|
215
|
+
log('success', `✓ opus create: raiz do monorepo "${name}" criada (base v${r.version}).`)
|
|
216
|
+
console.log(`\n ${r.files.length} arquivos (workspace apps/* + packages/*, registry, allowBuilds).`)
|
|
217
|
+
console.log(`
|
|
218
|
+
Próximos passos:
|
|
219
|
+
cd ${dir}
|
|
220
|
+
git init && git add -A && git commit -m "chore: raiz do monorepo"
|
|
221
|
+
opus create apps/<seu-app> # o modo app detecta o workspace
|
|
222
|
+
`)
|
|
223
|
+
return
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
const r = await createProject(REGISTRY_DIR, target, name)
|
|
227
|
+
const modeLabel = r.mode === 'app' ? 'app no monorepo (workspace detectado)' : 'repo standalone'
|
|
228
|
+
log('success', `✓ opus create: app "${name}" criado (base v${r.version}, ${modeLabel}).`)
|
|
229
|
+
console.log(`\n ${r.files.length} arquivos do esqueleto + setup (${r.setup.created.join(', ')}).`)
|
|
230
|
+
const warnings = [...r.warnings, ...r.setup.warnings]
|
|
231
|
+
if (warnings.length) {
|
|
232
|
+
console.log('\n falta plugar (à mão, pra não sobrescrever o seu):')
|
|
233
|
+
for (const w of warnings) console.log(` ! ${w}`)
|
|
234
|
+
}
|
|
235
|
+
if (r.mode === 'app') {
|
|
236
|
+
console.log(`
|
|
237
|
+
Próximos passos (no monorepo):
|
|
238
|
+
pnpm install
|
|
239
|
+
pnpm --filter ${name} test && pnpm --filter ${name} manifest
|
|
240
|
+
|
|
241
|
+
Depois: cadastro do app no admin (root/run) → sessão no Maestro.
|
|
242
|
+
`)
|
|
243
|
+
} else {
|
|
244
|
+
console.log(`
|
|
245
|
+
Próximos passos:
|
|
246
|
+
cd ${dir}
|
|
247
|
+
git init && git add -A && git commit -m "chore: esqueleto opus"
|
|
248
|
+
pnpm install
|
|
249
|
+
pnpm test && pnpm exec opus check src && pnpm manifest
|
|
250
|
+
|
|
251
|
+
Depois: repo no GitHub + cadastro no admin (workspace/repo/app/agentes) → sessão no Maestro.
|
|
252
|
+
`)
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
async function cmdInit() {
|
|
257
|
+
const cwd = process.cwd()
|
|
258
|
+
const r = await initProject(REGISTRY_DIR, cwd)
|
|
259
|
+
log('success', `✓ opus setup: app v${r.version} ${r.wasInitialized ? 'atualizado' : 'inicializado'}.`)
|
|
260
|
+
if (r.created.length) {
|
|
261
|
+
console.log('\n criados (per-app):')
|
|
262
|
+
for (const c of r.created) console.log(` + ${c}`)
|
|
263
|
+
}
|
|
264
|
+
if (r.synced.length) {
|
|
265
|
+
console.log('\n atualizados (bloco gerenciado):')
|
|
266
|
+
for (const c of r.synced) console.log(` ~ ${c}`)
|
|
267
|
+
}
|
|
268
|
+
if (r.warnings.length) {
|
|
269
|
+
console.log('\n falta plugar (à mão, pra não sobrescrever o seu):')
|
|
270
|
+
for (const w of r.warnings) console.log(` ! ${w}`)
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
// Fundação de UI (só apps web): preset + tema + flags. Cria o que falta, avisa o resto.
|
|
274
|
+
const ui = await setupUiFoundation(cwd)
|
|
275
|
+
if (ui.applicable) {
|
|
276
|
+
if (ui.created.length) {
|
|
277
|
+
console.log('\n fundação de UI criada:')
|
|
278
|
+
for (const c of ui.created) console.log(` + ${c}`)
|
|
279
|
+
}
|
|
280
|
+
if (ui.warnings.length) {
|
|
281
|
+
console.log('\n fundação de UI — falta plugar (à mão, pra não sobrescrever o seu):')
|
|
282
|
+
for (const w of ui.warnings) console.log(` ! ${w}`)
|
|
283
|
+
}
|
|
284
|
+
if (!ui.created.length && !ui.warnings.length) {
|
|
285
|
+
console.log('\n fundação de UI: ok (preset + tema + flags já plugados).')
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
console.log(
|
|
290
|
+
'\n Per-app: opus.json (pin) + CLAUDE.md. A spec vive nas declarações (description → manifest).\n' +
|
|
291
|
+
' A camada-base (.claude agents+skills) é REPO-LEVEL — o Maestro a materializa.\n' +
|
|
292
|
+
' Gate de build: opus check.\n',
|
|
293
|
+
)
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
async function cmdIntrospect(dir, flags) {
|
|
297
|
+
const root = path.resolve(process.cwd(), dir ?? '.')
|
|
298
|
+
const model = await introspect(root)
|
|
299
|
+
if (flags.json) {
|
|
300
|
+
console.log(JSON.stringify(model, null, 2))
|
|
301
|
+
return
|
|
302
|
+
}
|
|
303
|
+
const rel = path.relative(process.cwd(), root) || '.'
|
|
304
|
+
log('info', `\nopus introspect — ${rel}`)
|
|
305
|
+
console.log(` actions: ${model.actions.length}`)
|
|
306
|
+
console.log(` reactions: ${model.reactions.length}`)
|
|
307
|
+
console.log(` schedules: ${model.schedules.length}`)
|
|
308
|
+
if (model.wiring.length > 0) {
|
|
309
|
+
log('info', '\nwiring:')
|
|
310
|
+
for (const e of model.wiring) {
|
|
311
|
+
if (e.kind === 'schedule') console.log(` ⏱ ${e.from} → ${e.to} (${e.via})`)
|
|
312
|
+
else console.log(` ⚡ ${e.from} → ${e.to} (evento: ${e.via})`)
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
console.log('')
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
// =============================================================================
|
|
319
|
+
// Entry
|
|
320
|
+
// =============================================================================
|
|
321
|
+
|
|
322
|
+
function parseArgs(argv) {
|
|
323
|
+
const args = argv.slice(2)
|
|
324
|
+
const positional = []
|
|
325
|
+
const flags = { force: false, help: false }
|
|
326
|
+
// Flags com valor — pega o próximo arg.
|
|
327
|
+
const VALUE_FLAGS = new Set(['--config', '--output'])
|
|
328
|
+
for (let i = 0; i < args.length; i++) {
|
|
329
|
+
const arg = args[i]
|
|
330
|
+
if (arg === '--force' || arg === '-f') {
|
|
331
|
+
flags.force = true
|
|
332
|
+
continue
|
|
333
|
+
}
|
|
334
|
+
if (arg === '--help' || arg === '-h') {
|
|
335
|
+
flags.help = true
|
|
336
|
+
continue
|
|
337
|
+
}
|
|
338
|
+
if (arg === '--check') {
|
|
339
|
+
flags.check = true
|
|
340
|
+
continue
|
|
341
|
+
}
|
|
342
|
+
if (arg === '--json') {
|
|
343
|
+
flags.json = true
|
|
344
|
+
continue
|
|
345
|
+
}
|
|
346
|
+
if (arg === '--monorepo') {
|
|
347
|
+
flags.monorepo = true
|
|
348
|
+
continue
|
|
349
|
+
}
|
|
350
|
+
if (VALUE_FLAGS.has(arg)) {
|
|
351
|
+
const next = args[i + 1]
|
|
352
|
+
if (typeof next !== 'string' || next.startsWith('-')) {
|
|
353
|
+
log('error', `Flag ${arg} precisa de valor`)
|
|
354
|
+
process.exit(1)
|
|
355
|
+
}
|
|
356
|
+
// Strip leading "--" pra usar como key direto.
|
|
357
|
+
flags[arg.slice(2)] = next
|
|
358
|
+
i++
|
|
359
|
+
continue
|
|
360
|
+
}
|
|
361
|
+
if (arg.startsWith('--')) {
|
|
362
|
+
log('warn', `Flag desconhecida: ${arg}`)
|
|
363
|
+
continue
|
|
364
|
+
}
|
|
365
|
+
positional.push(arg)
|
|
366
|
+
}
|
|
367
|
+
return { positional, flags }
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
async function main() {
|
|
371
|
+
const { positional, flags } = parseArgs(process.argv)
|
|
372
|
+
const [command, ...rest] = positional
|
|
373
|
+
|
|
374
|
+
if (command === undefined || command === 'help') {
|
|
375
|
+
console.log(`
|
|
376
|
+
@softize/opus CLI
|
|
377
|
+
|
|
378
|
+
Comandos:
|
|
379
|
+
create <dir> Scaffolda um app opus-based canônico (vite+react+ui+domínio-exemplo);
|
|
380
|
+
detecta workspace (app em monorepo); --monorepo cria a RAIZ do workspace
|
|
381
|
+
setup Bootstrap PER-APP: grava opus.json + CLAUDE.md (se faltam)
|
|
382
|
+
add <name> Copia template do registry pro consumer
|
|
383
|
+
list Lista templates disponíveis
|
|
384
|
+
gen Gera manifest/openapi/docs/stubs a partir do opus.config.ts
|
|
385
|
+
check [dir] Valida as convenções das actions (régua de padrão; exit ≠ 0 se violar)
|
|
386
|
+
db <verbo> Comandos de banco. v1: db check (drift-check entidade ↔ banco)
|
|
387
|
+
introspect [dir] Modelo da estrutura (actions/reactions/schedules + wiring); --json
|
|
388
|
+
mcp Server MCP (introspect/check/create-action) — agentes via mcp_config
|
|
389
|
+
help Mostra esta mensagem
|
|
390
|
+
|
|
391
|
+
Flags:
|
|
392
|
+
--force, -f Sobrescreve arquivo existente
|
|
393
|
+
--config <path> (gen) Caminho do opus.config.ts
|
|
394
|
+
--output <path> (gen) Pasta de saída
|
|
395
|
+
|
|
396
|
+
Exemplos:
|
|
397
|
+
npx @softize/opus add action-form
|
|
398
|
+
npx @softize/opus add action-list --force
|
|
399
|
+
npx @softize/opus list
|
|
400
|
+
npx @softize/opus gen --config ./apps/api/opus.config.ts
|
|
401
|
+
`)
|
|
402
|
+
return
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
if (command === 'create') {
|
|
406
|
+
if (flags.help) {
|
|
407
|
+
console.log(`
|
|
408
|
+
@softize/opus create <dir>
|
|
409
|
+
|
|
410
|
+
Scaffolda um app opus-based CANÔNICO no diretório <dir> (recusa dir não-vazio).
|
|
411
|
+
O esqueleto versiona com a base — nasce com os pré-requisitos plugados:
|
|
412
|
+
protocolo opus.config.ts + domínio-exemplo + manifest/scripts (+ teste verde)
|
|
413
|
+
UI vite + react + tailwind v4 CSS-first (tema + @source do Opus)
|
|
414
|
+
Maestro dev server na porta injetada (PORT) + allowedHosts do preview
|
|
415
|
+
dia zero opus.json, CLAUDE.md (bloco), .claude/memory/, CI de fábrica
|
|
416
|
+
|
|
417
|
+
Depois: git init && pnpm install && pnpm test — e cadastre repo/app no admin.
|
|
418
|
+
`)
|
|
419
|
+
return
|
|
420
|
+
}
|
|
421
|
+
await cmdCreate(rest[0], flags)
|
|
422
|
+
return
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
if (command === 'setup') {
|
|
426
|
+
if (flags.help) {
|
|
427
|
+
console.log(`
|
|
428
|
+
@softize/opus setup
|
|
429
|
+
|
|
430
|
+
Bootstrap PER-APP de um projeto pra usar a base (idempotente):
|
|
431
|
+
opus.json marcador com a versão da base pinada (fonte da verdade)
|
|
432
|
+
CLAUDE.md pointer do protocolo — criado só se faltar (não sobrescreve)
|
|
433
|
+
|
|
434
|
+
Fundação de UI (só apps web — preset Tailwind + tema + flags do Opus):
|
|
435
|
+
tailwind.config criado se faltar; senão avisa pra estender o preset
|
|
436
|
+
tema/tsconfig/dep — avisa o que falta plugar (não edita seus arquivos: sem clobber)
|
|
437
|
+
|
|
438
|
+
A camada-base (agents/skills) é REPO-LEVEL — materializada pelo MAESTRO (o regente),
|
|
439
|
+
NÃO pelo setup. Roda manual ou via postinstall.
|
|
440
|
+
`)
|
|
441
|
+
return
|
|
442
|
+
}
|
|
443
|
+
await cmdInit()
|
|
444
|
+
return
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
if (command === 'list') {
|
|
448
|
+
await cmdList()
|
|
449
|
+
return
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
if (command === 'add') {
|
|
453
|
+
if (flags.help) {
|
|
454
|
+
console.log(`
|
|
455
|
+
@softize/opus add <name>
|
|
456
|
+
|
|
457
|
+
Copia o template registry/<name>.tsx pra src/components/action/<name>.tsx
|
|
458
|
+
(ou ao alias resolvido via components.json).
|
|
459
|
+
|
|
460
|
+
Flags:
|
|
461
|
+
--force, -f Sobrescreve existente
|
|
462
|
+
`)
|
|
463
|
+
return
|
|
464
|
+
}
|
|
465
|
+
const [name] = rest
|
|
466
|
+
await cmdAdd(name, flags)
|
|
467
|
+
return
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
if (command === 'gen') {
|
|
471
|
+
if (flags.help) {
|
|
472
|
+
helpGen()
|
|
473
|
+
return
|
|
474
|
+
}
|
|
475
|
+
await cmdGen(flags)
|
|
476
|
+
return
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
if (command === 'check') {
|
|
480
|
+
if (flags.help) {
|
|
481
|
+
console.log(`
|
|
482
|
+
@softize/opus check [dir]
|
|
483
|
+
|
|
484
|
+
Valida as convenções das actions (estático, TS compiler API). Enxerga
|
|
485
|
+
\`defineAction\` e o split \`defineContract\`+\`bindAction\`:
|
|
486
|
+
action-name — <resource>.<verb> (minúsculo, ponto)
|
|
487
|
+
kind — simple|form|list|view
|
|
488
|
+
field-order — identidade→docs→input/output→authorize→handler→comportamento
|
|
489
|
+
export — defineAction/defineContract/bindAction como \`export const\`
|
|
490
|
+
requires-sem-authorize — \`requires\` é declarativo (o runtime não o executa);
|
|
491
|
+
action com requires precisa de authorize (no contrato
|
|
492
|
+
ou no binding — o join contrato↔bind é cross-file)
|
|
493
|
+
|
|
494
|
+
Exit ≠ 0 se houver violação OU se não achar action nenhuma (gate vazio não
|
|
495
|
+
passa verde). Default dir: cwd.
|
|
496
|
+
`)
|
|
497
|
+
return
|
|
498
|
+
}
|
|
499
|
+
await cmdCheck(rest[0])
|
|
500
|
+
return
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
if (command === 'db') {
|
|
504
|
+
await cmdDb(rest, flags)
|
|
505
|
+
return
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
if (command === 'introspect') {
|
|
509
|
+
await cmdIntrospect(rest[0], flags)
|
|
510
|
+
return
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
if (command === 'mcp') {
|
|
514
|
+
// stdout é o canal do protocolo MCP — NÃO logar aqui.
|
|
515
|
+
const { StdioServerTransport } = await import('@modelcontextprotocol/sdk/server/stdio.js')
|
|
516
|
+
const { buildServer } = await import('./lib/mcp.mjs')
|
|
517
|
+
await buildServer().connect(new StdioServerTransport())
|
|
518
|
+
return // processo segue vivo pelo listener do stdin
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
log('error', `Comando desconhecido: ${command}`)
|
|
522
|
+
process.exit(1)
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
main().catch((err) => {
|
|
526
|
+
log('error', `Erro: ${err.message}`)
|
|
527
|
+
process.exit(1)
|
|
528
|
+
})
|