create-kuckit-app 0.3.5 → 0.4.1
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/dist/bin.js +1 -1
- package/dist/{create-project-CP-h4Ygi.js → create-project-CAsuZMK5.js} +7 -1
- package/dist/index.js +1 -1
- package/package.json +1 -1
- package/templates/base/.claude/CLAUDE.md +83 -0
- package/templates/base/.claude/skills/kuckit/SKILL.md +22 -2
- package/templates/base/.claude/skills/kuckit/references/MODULE-DEVELOPMENT.md +39 -28
- package/templates/base/.claude/skills/kuckit/references/PACKAGES.md +94 -74
- package/templates/base/AGENTS.md +130 -18
- package/templates/base/apps/server/AGENTS.md +44 -62
- package/templates/base/apps/server/package.json +5 -17
- package/templates/base/apps/server/src/config.ts +12 -0
- package/templates/base/apps/server/src/modules.ts +66 -0
- package/templates/base/apps/server/src/server.ts +4 -44
- package/templates/base/apps/web/AGENTS.md +63 -85
- package/templates/base/apps/web/package.json +7 -11
- package/templates/base/apps/web/src/components/KuckitModuleRoute.tsx +29 -7
- package/templates/base/apps/web/src/components/dashboard/dashboard-overview.tsx +2 -2
- package/templates/base/apps/web/src/components/dashboard/nav-user.tsx +2 -2
- package/templates/base/apps/web/src/main.tsx +12 -22
- package/templates/base/apps/web/src/modules.client.ts +43 -9
- package/templates/base/apps/web/src/routes/__root.tsx +1 -1
- package/templates/base/apps/web/src/routes/dashboard.tsx +1 -1
- package/templates/base/apps/web/src/routes/index.tsx +2 -2
- package/templates/base/apps/web/src/routes/login.tsx +2 -2
- package/templates/base/{packages/db/src/migrations → drizzle}/0000_init.sql +31 -36
- package/templates/base/{packages/db/src/migrations → drizzle}/meta/_journal.json +1 -1
- package/templates/base/drizzle.config.ts +34 -0
- package/templates/base/kuckit.config.ts +30 -0
- package/templates/base/package.json +14 -9
- package/templates/base/packages/items-module/AGENTS.md +83 -0
- package/templates/base/packages/items-module/package.json +7 -7
- package/templates/base/packages/items-module/src/api/items.router.ts +1 -1
- package/templates/base/apps/server/src/app.ts +0 -20
- package/templates/base/apps/server/src/auth.ts +0 -10
- package/templates/base/apps/server/src/config/modules.ts +0 -21
- package/templates/base/apps/server/src/container.ts +0 -81
- package/templates/base/apps/server/src/health.ts +0 -27
- package/templates/base/apps/server/src/middleware/container.ts +0 -41
- package/templates/base/apps/server/src/rpc-router-registry.ts +0 -26
- package/templates/base/apps/server/src/rpc.ts +0 -31
- package/templates/base/apps/web/src/providers/KuckitProvider.tsx +0 -123
- package/templates/base/apps/web/src/providers/ServicesProvider.tsx +0 -47
- package/templates/base/apps/web/src/services/auth-client.ts +0 -12
- package/templates/base/apps/web/src/services/index.ts +0 -3
- package/templates/base/apps/web/src/services/rpc.ts +0 -29
- package/templates/base/apps/web/src/services/types.ts +0 -14
- package/templates/base/packages/api/AGENTS.md +0 -66
- package/templates/base/packages/api/package.json +0 -35
- package/templates/base/packages/api/src/context.ts +0 -48
- package/templates/base/packages/api/src/index.ts +0 -22
- package/templates/base/packages/api/tsconfig.json +0 -8
- package/templates/base/packages/auth/AGENTS.md +0 -61
- package/templates/base/packages/auth/package.json +0 -27
- package/templates/base/packages/auth/src/index.ts +0 -22
- package/templates/base/packages/auth/tsconfig.json +0 -8
- package/templates/base/packages/db/AGENTS.md +0 -99
- package/templates/base/packages/db/drizzle.config.ts +0 -23
- package/templates/base/packages/db/package.json +0 -36
- package/templates/base/packages/db/src/connection.ts +0 -40
- package/templates/base/packages/db/src/index.ts +0 -4
- package/templates/base/packages/db/src/schema/auth.ts +0 -51
- package/templates/base/packages/db/tsconfig.json +0 -8
|
@@ -1,17 +1,51 @@
|
|
|
1
1
|
import type { ClientModuleSpec } from '@kuckit/sdk-react'
|
|
2
2
|
import { kuckitClientModule as itemsClientModule } from '@__APP_NAME_KEBAB__/items-module/client'
|
|
3
|
+
import config from '../../../kuckit.config'
|
|
3
4
|
|
|
4
5
|
/**
|
|
5
|
-
*
|
|
6
|
+
* Known client modules mapping: package name → direct module import
|
|
6
7
|
*
|
|
7
|
-
*
|
|
8
|
+
* This enables config-driven module loading while keeping direct imports
|
|
9
|
+
* for workspace packages (required for Vite bundling).
|
|
10
|
+
*
|
|
11
|
+
* When adding a new module:
|
|
12
|
+
* 1. Add to kuckit.config.ts (source of truth)
|
|
13
|
+
* 2. Add import and mapping here
|
|
8
14
|
*/
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
+
const KNOWN_CLIENT_MODULES: Record<string, { module: unknown }> = {
|
|
16
|
+
// KUCKIT_KNOWN_CLIENT_MODULES_START
|
|
17
|
+
'@__APP_NAME_KEBAB__/items-module': {
|
|
18
|
+
module: itemsClientModule,
|
|
19
|
+
},
|
|
20
|
+
// KUCKIT_KNOWN_CLIENT_MODULES_END
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Convert unified config to ClientModuleSpec array.
|
|
25
|
+
* For known modules, uses direct imports. For npm packages, uses package-based loading.
|
|
26
|
+
*/
|
|
27
|
+
function configToClientModuleSpecs(): ClientModuleSpec[] {
|
|
28
|
+
return config.modules
|
|
29
|
+
.filter((m) => m.enabled !== false)
|
|
30
|
+
.map((m) => {
|
|
31
|
+
const known = KNOWN_CLIENT_MODULES[m.package]
|
|
32
|
+
if (known) {
|
|
33
|
+
return {
|
|
34
|
+
module: known.module,
|
|
35
|
+
config: m.config,
|
|
36
|
+
} as ClientModuleSpec
|
|
37
|
+
}
|
|
38
|
+
// For npm packages, use package-based loading (client subpath)
|
|
39
|
+
return {
|
|
40
|
+
package: `${m.package}/client`,
|
|
41
|
+
config: m.config,
|
|
42
|
+
} as ClientModuleSpec
|
|
43
|
+
})
|
|
44
|
+
}
|
|
15
45
|
|
|
16
|
-
|
|
46
|
+
/**
|
|
47
|
+
* Get client module specs from kuckit.config.ts (single source of truth).
|
|
48
|
+
*/
|
|
49
|
+
export const getClientModuleSpecs = (): ClientModuleSpec[] => {
|
|
50
|
+
return configToClientModuleSpecs()
|
|
17
51
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { QueryClient } from '@tanstack/react-query'
|
|
2
2
|
import { Outlet, createRootRouteWithContext } from '@tanstack/react-router'
|
|
3
|
-
import type { ORPCUtils } from '
|
|
3
|
+
import type { ORPCUtils } from '@kuckit/app-web'
|
|
4
4
|
|
|
5
5
|
export interface RouterAppContext {
|
|
6
6
|
orpc: ORPCUtils
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { createFileRoute, Outlet, redirect } from '@tanstack/react-router'
|
|
2
2
|
import { DashboardLayout } from '@/components/dashboard/dashboard-layout'
|
|
3
|
-
import { createAuthClientService } from '
|
|
3
|
+
import { createAuthClientService } from '@kuckit/app-web'
|
|
4
4
|
|
|
5
5
|
export const Route = createFileRoute('/dashboard')({
|
|
6
6
|
beforeLoad: async () => {
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { createFileRoute, Link } from '@tanstack/react-router'
|
|
2
|
-
import {
|
|
2
|
+
import { useAuth } from '@kuckit/app-web'
|
|
3
3
|
|
|
4
4
|
export const Route = createFileRoute('/')({
|
|
5
5
|
component: HomePage,
|
|
6
6
|
})
|
|
7
7
|
|
|
8
8
|
function HomePage() {
|
|
9
|
-
const
|
|
9
|
+
const authClient = useAuth()
|
|
10
10
|
const { data: session, isPending } = authClient.useSession()
|
|
11
11
|
|
|
12
12
|
if (isPending) {
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import { createFileRoute, useNavigate } from '@tanstack/react-router'
|
|
2
2
|
import { useState } from 'react'
|
|
3
|
-
import {
|
|
3
|
+
import { useAuth } from '@kuckit/app-web'
|
|
4
4
|
|
|
5
5
|
export const Route = createFileRoute('/login')({
|
|
6
6
|
component: LoginPage,
|
|
7
7
|
})
|
|
8
8
|
|
|
9
9
|
function LoginPage() {
|
|
10
|
-
const
|
|
10
|
+
const authClient = useAuth()
|
|
11
11
|
const navigate = useNavigate()
|
|
12
12
|
const [isSignUp, setIsSignUp] = useState(false)
|
|
13
13
|
const [email, setEmail] = useState('')
|
|
@@ -1,19 +1,20 @@
|
|
|
1
|
-
|
|
2
|
-
-- This migration is generated by drizzle-kit
|
|
3
|
-
-- Run: bun run db:migrate
|
|
4
|
-
|
|
5
|
-
CREATE TABLE IF NOT EXISTS "user" (
|
|
1
|
+
CREATE TABLE "account" (
|
|
6
2
|
"id" text PRIMARY KEY NOT NULL,
|
|
7
|
-
"
|
|
8
|
-
"
|
|
9
|
-
"
|
|
10
|
-
"
|
|
3
|
+
"account_id" text NOT NULL,
|
|
4
|
+
"provider_id" text NOT NULL,
|
|
5
|
+
"user_id" text NOT NULL,
|
|
6
|
+
"access_token" text,
|
|
7
|
+
"refresh_token" text,
|
|
8
|
+
"id_token" text,
|
|
9
|
+
"access_token_expires_at" timestamp,
|
|
10
|
+
"refresh_token_expires_at" timestamp,
|
|
11
|
+
"scope" text,
|
|
12
|
+
"password" text,
|
|
11
13
|
"created_at" timestamp NOT NULL,
|
|
12
|
-
"updated_at" timestamp NOT NULL
|
|
13
|
-
CONSTRAINT "user_email_unique" UNIQUE("email")
|
|
14
|
+
"updated_at" timestamp NOT NULL
|
|
14
15
|
);
|
|
15
|
-
|
|
16
|
-
CREATE TABLE
|
|
16
|
+
--> statement-breakpoint
|
|
17
|
+
CREATE TABLE "session" (
|
|
17
18
|
"id" text PRIMARY KEY NOT NULL,
|
|
18
19
|
"expires_at" timestamp NOT NULL,
|
|
19
20
|
"token" text NOT NULL,
|
|
@@ -24,24 +25,19 @@ CREATE TABLE IF NOT EXISTS "session" (
|
|
|
24
25
|
"user_id" text NOT NULL,
|
|
25
26
|
CONSTRAINT "session_token_unique" UNIQUE("token")
|
|
26
27
|
);
|
|
27
|
-
|
|
28
|
-
CREATE TABLE
|
|
28
|
+
--> statement-breakpoint
|
|
29
|
+
CREATE TABLE "user" (
|
|
29
30
|
"id" text PRIMARY KEY NOT NULL,
|
|
30
|
-
"
|
|
31
|
-
"
|
|
32
|
-
"
|
|
33
|
-
"
|
|
34
|
-
"refresh_token" text,
|
|
35
|
-
"id_token" text,
|
|
36
|
-
"access_token_expires_at" timestamp,
|
|
37
|
-
"refresh_token_expires_at" timestamp,
|
|
38
|
-
"scope" text,
|
|
39
|
-
"password" text,
|
|
31
|
+
"name" text NOT NULL,
|
|
32
|
+
"email" text NOT NULL,
|
|
33
|
+
"email_verified" boolean NOT NULL,
|
|
34
|
+
"image" text,
|
|
40
35
|
"created_at" timestamp NOT NULL,
|
|
41
|
-
"updated_at" timestamp NOT NULL
|
|
36
|
+
"updated_at" timestamp NOT NULL,
|
|
37
|
+
CONSTRAINT "user_email_unique" UNIQUE("email")
|
|
42
38
|
);
|
|
43
|
-
|
|
44
|
-
CREATE TABLE
|
|
39
|
+
--> statement-breakpoint
|
|
40
|
+
CREATE TABLE "verification" (
|
|
45
41
|
"id" text PRIMARY KEY NOT NULL,
|
|
46
42
|
"identifier" text NOT NULL,
|
|
47
43
|
"value" text NOT NULL,
|
|
@@ -49,16 +45,15 @@ CREATE TABLE IF NOT EXISTS "verification" (
|
|
|
49
45
|
"created_at" timestamp,
|
|
50
46
|
"updated_at" timestamp
|
|
51
47
|
);
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
ALTER TABLE "account" ADD CONSTRAINT "account_user_id_user_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."user"("id") ON DELETE cascade ON UPDATE no action;
|
|
55
|
-
|
|
56
|
-
-- Items module table
|
|
57
|
-
CREATE TABLE IF NOT EXISTS "items" (
|
|
48
|
+
--> statement-breakpoint
|
|
49
|
+
CREATE TABLE "items" (
|
|
58
50
|
"id" text PRIMARY KEY NOT NULL,
|
|
59
51
|
"name" text NOT NULL,
|
|
60
52
|
"description" text,
|
|
61
|
-
"created_at" timestamp
|
|
62
|
-
"updated_at" timestamp
|
|
53
|
+
"created_at" timestamp DEFAULT now() NOT NULL,
|
|
54
|
+
"updated_at" timestamp DEFAULT now() NOT NULL,
|
|
63
55
|
"user_id" text NOT NULL
|
|
64
56
|
);
|
|
57
|
+
--> statement-breakpoint
|
|
58
|
+
ALTER TABLE "account" ADD CONSTRAINT "account_user_id_user_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."user"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
|
59
|
+
ALTER TABLE "session" ADD CONSTRAINT "session_user_id_user_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."user"("id") ON DELETE cascade ON UPDATE no action;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { defineConfig } from 'drizzle-kit'
|
|
2
|
+
import dotenv from 'dotenv'
|
|
3
|
+
import { dirname, resolve } from 'path'
|
|
4
|
+
import { fileURLToPath } from 'url'
|
|
5
|
+
import { getModuleSchemaPaths } from '@kuckit/db/schema-discovery'
|
|
6
|
+
|
|
7
|
+
const currentFilePath = fileURLToPath(import.meta.url)
|
|
8
|
+
const currentDirPath = dirname(currentFilePath)
|
|
9
|
+
|
|
10
|
+
dotenv.config({
|
|
11
|
+
path: resolve(currentDirPath, './apps/server/.env'),
|
|
12
|
+
})
|
|
13
|
+
|
|
14
|
+
function buildDatabaseUrl(): string {
|
|
15
|
+
if (process.env.DATABASE_URL) {
|
|
16
|
+
return process.env.DATABASE_URL
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const { DB_HOST, DB_USER, DB_PASSWORD, DB_NAME } = process.env
|
|
20
|
+
if (DB_HOST && DB_USER && DB_PASSWORD && DB_NAME) {
|
|
21
|
+
return `postgresql://${DB_USER}:${encodeURIComponent(DB_PASSWORD)}@/${DB_NAME}?host=${DB_HOST}`
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
throw new Error(
|
|
25
|
+
'Missing database configuration: provide DATABASE_URL or DB_HOST, DB_USER, DB_PASSWORD, DB_NAME'
|
|
26
|
+
)
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export default defineConfig({
|
|
30
|
+
schema: getModuleSchemaPaths(),
|
|
31
|
+
out: './drizzle',
|
|
32
|
+
dialect: 'postgresql',
|
|
33
|
+
dbCredentials: { url: buildDatabaseUrl() },
|
|
34
|
+
})
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { defineConfig } from '@kuckit/sdk/config'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Kuckit Configuration
|
|
5
|
+
*
|
|
6
|
+
* This is the unified configuration file for both server and client modules.
|
|
7
|
+
* Both apps/server and apps/web will read from this single source of truth.
|
|
8
|
+
*
|
|
9
|
+
* @see https://github.com/kuckit/kuckit#configuration
|
|
10
|
+
*/
|
|
11
|
+
export default defineConfig({
|
|
12
|
+
modules: [
|
|
13
|
+
// KUCKIT_MODULES_START
|
|
14
|
+
{ package: '@__APP_NAME_KEBAB__/items-module', schemaDir: 'src/adapters' },
|
|
15
|
+
// KUCKIT_MODULES_END
|
|
16
|
+
],
|
|
17
|
+
|
|
18
|
+
server: {
|
|
19
|
+
apiPrefix: '/api',
|
|
20
|
+
},
|
|
21
|
+
|
|
22
|
+
client: {
|
|
23
|
+
routeInjection: true,
|
|
24
|
+
componentRegistry: 'context',
|
|
25
|
+
},
|
|
26
|
+
|
|
27
|
+
discovery: {
|
|
28
|
+
enabled: false,
|
|
29
|
+
},
|
|
30
|
+
})
|
|
@@ -14,9 +14,10 @@
|
|
|
14
14
|
"lint:fix": "eslint apps packages --fix",
|
|
15
15
|
"format": "prettier --write \"**/*.{ts,tsx,js,jsx,json,md,yml,yaml}\"",
|
|
16
16
|
"format:check": "prettier --check \"**/*.{ts,tsx,js,jsx,json,md,yml,yaml}\"",
|
|
17
|
-
"db:
|
|
18
|
-
"db:
|
|
19
|
-
"db:
|
|
17
|
+
"db:push": "drizzle-kit push",
|
|
18
|
+
"db:generate": "drizzle-kit generate",
|
|
19
|
+
"db:migrate": "drizzle-kit migrate",
|
|
20
|
+
"db:studio": "drizzle-kit studio",
|
|
20
21
|
"prepare": "husky"
|
|
21
22
|
},
|
|
22
23
|
"lint-staged": {
|
|
@@ -29,21 +30,25 @@
|
|
|
29
30
|
]
|
|
30
31
|
},
|
|
31
32
|
"dependencies": {
|
|
32
|
-
"@kuckit/sdk": "^
|
|
33
|
+
"@kuckit/sdk": "^2.0.0",
|
|
34
|
+
"@kuckit/api": "^2.0.0",
|
|
35
|
+
"@kuckit/auth": "^2.0.0",
|
|
36
|
+
"@kuckit/db": "^2.0.0",
|
|
33
37
|
"awilix": "^12.0.5",
|
|
34
|
-
"dotenv": "^17.
|
|
35
|
-
"zod": "^
|
|
38
|
+
"dotenv": "^17.0.0",
|
|
39
|
+
"zod": "^3.23.0"
|
|
36
40
|
},
|
|
37
41
|
"devDependencies": {
|
|
38
42
|
"@eslint/js": "^9.17.0",
|
|
43
|
+
"drizzle-kit": "^0.31.0",
|
|
39
44
|
"eslint": "^9.17.0",
|
|
40
45
|
"eslint-config-prettier": "^10.0.1",
|
|
41
46
|
"husky": "^9.1.7",
|
|
42
47
|
"lint-staged": "^16.0.0",
|
|
43
48
|
"prettier": "^3.4.2",
|
|
44
|
-
"turbo": "^2.5.
|
|
45
|
-
"typescript": "^5.8.
|
|
46
|
-
"typescript-eslint": "^8.18.
|
|
49
|
+
"turbo": "^2.5.0",
|
|
50
|
+
"typescript": "^5.8.0",
|
|
51
|
+
"typescript-eslint": "^8.18.0"
|
|
47
52
|
},
|
|
48
53
|
"packageManager": "bun@1.2.21"
|
|
49
54
|
}
|
|
@@ -208,3 +208,86 @@ export function ItemsPage() {
|
|
|
208
208
|
```
|
|
209
209
|
|
|
210
210
|
**Important**: Never use `import.meta.env` directly in module components - use `useRpc()` instead. Module packages are bundled separately and don't have access to the host app's environment variables.
|
|
211
|
+
|
|
212
|
+
<!-- MCP_AGENT_MAIL_AND_BEADS_SNIPPET_START -->
|
|
213
|
+
|
|
214
|
+
## MCP Agent Mail: coordination for multi-agent workflows
|
|
215
|
+
|
|
216
|
+
What it is
|
|
217
|
+
|
|
218
|
+
- A mail-like layer that lets coding agents coordinate asynchronously via MCP tools and resources.
|
|
219
|
+
- Provides identities, inbox/outbox, searchable threads, and advisory file reservations, with human-auditable artifacts in Git.
|
|
220
|
+
|
|
221
|
+
Why it's useful
|
|
222
|
+
|
|
223
|
+
- Prevents agents from stepping on each other with explicit file reservations (leases) for files/globs.
|
|
224
|
+
- Keeps communication out of your token budget by storing messages in a per-project archive.
|
|
225
|
+
- Offers quick reads (`resource://inbox/...`, `resource://thread/...`) and macros that bundle common flows.
|
|
226
|
+
|
|
227
|
+
How to use effectively
|
|
228
|
+
|
|
229
|
+
1. Same repository
|
|
230
|
+
- Register an identity: call `ensure_project`, then `register_agent` using this repo's absolute path as `project_key`.
|
|
231
|
+
- Reserve files before you edit: `file_reservation_paths(project_key, agent_name, ["src/**"], ttl_seconds=3600, exclusive=true)` to signal intent and avoid conflict.
|
|
232
|
+
- Communicate with threads: use `send_message(..., thread_id="FEAT-123")`; check inbox with `fetch_inbox` and acknowledge with `acknowledge_message`.
|
|
233
|
+
- Read fast: `resource://inbox/{Agent}?project=<abs-path>&limit=20` or `resource://thread/{id}?project=<abs-path>&include_bodies=true`.
|
|
234
|
+
- Tip: set `AGENT_NAME` in your environment so the pre-commit guard can block commits that conflict with others' active exclusive file reservations.
|
|
235
|
+
|
|
236
|
+
2. Across different repos in one project (e.g., Next.js frontend + FastAPI backend)
|
|
237
|
+
- Option A (single project bus): register both sides under the same `project_key` (shared key/path). Keep reservation patterns specific (e.g., `frontend/**` vs `backend/**`).
|
|
238
|
+
- Option B (separate projects): each repo has its own `project_key`; use `macro_contact_handshake` or `request_contact`/`respond_contact` to link agents, then message directly. Keep a shared `thread_id` (e.g., ticket key) across repos for clean summaries/audits.
|
|
239
|
+
|
|
240
|
+
Macros vs granular tools
|
|
241
|
+
|
|
242
|
+
- Prefer macros when you want speed or are on a smaller model: `macro_start_session`, `macro_prepare_thread`, `macro_file_reservation_cycle`, `macro_contact_handshake`.
|
|
243
|
+
- Use granular tools when you need control: `register_agent`, `file_reservation_paths`, `send_message`, `fetch_inbox`, `acknowledge_message`.
|
|
244
|
+
|
|
245
|
+
Common pitfalls
|
|
246
|
+
|
|
247
|
+
- "from_agent not registered": always `register_agent` in the correct `project_key` first.
|
|
248
|
+
- "FILE_RESERVATION_CONFLICT": adjust patterns, wait for expiry, or use a non-exclusive reservation when appropriate.
|
|
249
|
+
- Auth errors: if JWT+JWKS is enabled, include a bearer token with a `kid` that matches server JWKS; static bearer is used only when JWT is disabled.
|
|
250
|
+
|
|
251
|
+
## Integrating with Beads (dependency-aware task planning)
|
|
252
|
+
|
|
253
|
+
Beads provides a lightweight, dependency-aware issue database and a CLI (`bd`) for selecting "ready work," setting priorities, and tracking status. It complements MCP Agent Mail's messaging, audit trail, and file-reservation signals. Project: [steveyegge/beads](https://github.com/steveyegge/beads)
|
|
254
|
+
|
|
255
|
+
Recommended conventions
|
|
256
|
+
|
|
257
|
+
- **Single source of truth**: Use **Beads** for task status/priority/dependencies; use **Agent Mail** for conversation, decisions, and attachments (audit).
|
|
258
|
+
- **Shared identifiers**: Use the Beads issue id (e.g., `bd-123`) as the Mail `thread_id` and prefix message subjects with `[bd-123]`.
|
|
259
|
+
- **Reservations**: When starting a `bd-###` task, call `file_reservation_paths(...)` for the affected paths; include the issue id in the `reason` and release on completion.
|
|
260
|
+
|
|
261
|
+
Typical flow (agents)
|
|
262
|
+
|
|
263
|
+
1. **Pick ready work** (Beads)
|
|
264
|
+
- `bd ready --json` → choose one item (highest priority, no blockers)
|
|
265
|
+
2. **Reserve edit surface** (Mail)
|
|
266
|
+
- `file_reservation_paths(project_key, agent_name, ["src/**"], ttl_seconds=3600, exclusive=true, reason="bd-123")`
|
|
267
|
+
3. **Announce start** (Mail)
|
|
268
|
+
- `send_message(..., thread_id="bd-123", subject="[bd-123] Start: <short title>", ack_required=true)`
|
|
269
|
+
4. **Work and update**
|
|
270
|
+
- Reply in-thread with progress and attach artifacts/images; keep the discussion in one thread per issue id
|
|
271
|
+
5. **Complete and release**
|
|
272
|
+
- `bd close bd-123 --reason "Completed"` (Beads is status authority)
|
|
273
|
+
- `release_file_reservations(project_key, agent_name, paths=["src/**"])`
|
|
274
|
+
- Final Mail reply: `[bd-123] Completed` with summary and links
|
|
275
|
+
|
|
276
|
+
Mapping cheat-sheet
|
|
277
|
+
|
|
278
|
+
- **Mail `thread_id`** ↔ `bd-###`
|
|
279
|
+
- **Mail subject**: `[bd-###] …`
|
|
280
|
+
- **File reservation `reason`**: `bd-###`
|
|
281
|
+
- **Commit messages (optional)**: include `bd-###` for traceability
|
|
282
|
+
|
|
283
|
+
Event mirroring (optional automation)
|
|
284
|
+
|
|
285
|
+
- On `bd update --status blocked`, send a high-importance Mail message in thread `bd-###` describing the blocker.
|
|
286
|
+
- On Mail "ACK overdue" for a critical decision, add a Beads label (e.g., `needs-ack`) or bump priority to surface it in `bd ready`.
|
|
287
|
+
|
|
288
|
+
Pitfalls to avoid
|
|
289
|
+
|
|
290
|
+
- Don't create or manage tasks in Mail; treat Beads as the single task queue.
|
|
291
|
+
- Always include `bd-###` in message `thread_id` to avoid ID drift across tools.
|
|
292
|
+
|
|
293
|
+
<!-- MCP_AGENT_MAIL_AND_BEADS_SNIPPET_END -->
|
|
@@ -23,14 +23,14 @@
|
|
|
23
23
|
"typescript": "^5"
|
|
24
24
|
},
|
|
25
25
|
"dependencies": {
|
|
26
|
-
"@kuckit/sdk": "^
|
|
27
|
-
"@kuckit/sdk-react": "^
|
|
26
|
+
"@kuckit/sdk": "^2.0.0",
|
|
27
|
+
"@kuckit/sdk-react": "^2.0.0",
|
|
28
|
+
"@kuckit/api": "^2.0.0",
|
|
28
29
|
"@orpc/server": "^1.10.0",
|
|
29
30
|
"@orpc/zod": "^1.10.0",
|
|
30
|
-
"drizzle-orm": "^0.44.
|
|
31
|
-
"zod": "^
|
|
32
|
-
"react": "^19.
|
|
33
|
-
"@
|
|
34
|
-
"@__APP_NAME_KEBAB__/db": "workspace:*"
|
|
31
|
+
"drizzle-orm": "^0.44.0",
|
|
32
|
+
"zod": "^3.23.0",
|
|
33
|
+
"react": "^19.0.0",
|
|
34
|
+
"@tanstack/react-query": "^5.0.0"
|
|
35
35
|
}
|
|
36
36
|
}
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
import express, { type Express } from 'express'
|
|
2
|
-
import cors from 'cors'
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Create and configure Express app
|
|
6
|
-
*/
|
|
7
|
-
export const createApp = (): Express => {
|
|
8
|
-
const app = express()
|
|
9
|
-
|
|
10
|
-
app.use(
|
|
11
|
-
cors({
|
|
12
|
-
origin: process.env.CORS_ORIGIN || 'http://localhost:3001',
|
|
13
|
-
methods: ['GET', 'POST', 'OPTIONS'],
|
|
14
|
-
allowedHeaders: ['Content-Type', 'Authorization'],
|
|
15
|
-
credentials: true,
|
|
16
|
-
})
|
|
17
|
-
)
|
|
18
|
-
|
|
19
|
-
return app
|
|
20
|
-
}
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
import { auth } from '@__APP_NAME_KEBAB__/auth'
|
|
2
|
-
import { toNodeHandler } from 'better-auth/node'
|
|
3
|
-
import type { Express } from 'express'
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* Setup Better-Auth routes
|
|
7
|
-
*/
|
|
8
|
-
export const setupAuth = (app: Express) => {
|
|
9
|
-
app.all('/api/auth{/*path}', toNodeHandler(auth))
|
|
10
|
-
}
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
import type { ModuleSpec } from '@kuckit/sdk'
|
|
2
|
-
import { kuckitModule as itemsModule } from '@__APP_NAME_KEBAB__/items-module'
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Module specifications for the server application
|
|
6
|
-
*
|
|
7
|
-
* Add modules here to configure the server's functionality.
|
|
8
|
-
* Modules are loaded in order, with dependencies resolved automatically.
|
|
9
|
-
*
|
|
10
|
-
* IMPORTANT: Server and client modules must be kept in sync!
|
|
11
|
-
* If you add/remove a module here, also update apps/web/src/modules.client.ts
|
|
12
|
-
*/
|
|
13
|
-
export const getModuleSpecs = (): ModuleSpec[] => {
|
|
14
|
-
const modules: ModuleSpec[] = [
|
|
15
|
-
// KUCKIT_SERVER_MODULES_START
|
|
16
|
-
{ module: itemsModule },
|
|
17
|
-
// KUCKIT_SERVER_MODULES_END
|
|
18
|
-
]
|
|
19
|
-
|
|
20
|
-
return modules
|
|
21
|
-
}
|
|
@@ -1,81 +0,0 @@
|
|
|
1
|
-
import type { AwilixContainer } from 'awilix'
|
|
2
|
-
import {
|
|
3
|
-
createKuckitContainer,
|
|
4
|
-
loadKuckitModules,
|
|
5
|
-
type CoreConfig,
|
|
6
|
-
type CoreCradle,
|
|
7
|
-
} from '@kuckit/sdk'
|
|
8
|
-
import type { Pool } from 'pg'
|
|
9
|
-
import { ensureDatabaseUrl } from '@__APP_NAME_KEBAB__/db/connection'
|
|
10
|
-
import { getModuleSpecs } from './config/modules'
|
|
11
|
-
import { wireModuleRpcRouters } from './rpc-router-registry'
|
|
12
|
-
|
|
13
|
-
/**
|
|
14
|
-
* Server configuration
|
|
15
|
-
*/
|
|
16
|
-
export interface ServerConfig extends CoreConfig {
|
|
17
|
-
port: number
|
|
18
|
-
corsOrigin: string
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
* Server DI container cradle
|
|
23
|
-
*/
|
|
24
|
-
export interface ServerCradle extends CoreCradle {
|
|
25
|
-
config: ServerConfig
|
|
26
|
-
dbPool: Pool
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
export type AppContainer = AwilixContainer<ServerCradle>
|
|
30
|
-
export type { ServerConfig as Config, ServerCradle as Cradle }
|
|
31
|
-
|
|
32
|
-
/**
|
|
33
|
-
* Load configuration from environment
|
|
34
|
-
*/
|
|
35
|
-
const loadConfig = (): ServerConfig => {
|
|
36
|
-
const databaseUrl = ensureDatabaseUrl()
|
|
37
|
-
|
|
38
|
-
return {
|
|
39
|
-
databaseUrl,
|
|
40
|
-
enableFileLogging: process.env.ENABLE_FILE_LOGGING === 'true',
|
|
41
|
-
logDir: process.env.LOG_DIR || './logs',
|
|
42
|
-
logLevel: (process.env.LOG_LEVEL || 'INFO') as 'DEBUG' | 'INFO' | 'WARN' | 'ERROR',
|
|
43
|
-
env: process.env.NODE_ENV || 'development',
|
|
44
|
-
port: parseInt(process.env.PORT || '3000', 10),
|
|
45
|
-
corsOrigin: process.env.CORS_ORIGIN || 'http://localhost:3001',
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
/**
|
|
50
|
-
* Build root container with all modules
|
|
51
|
-
*/
|
|
52
|
-
export const buildRootContainer = async (): Promise<AppContainer> => {
|
|
53
|
-
const config = loadConfig()
|
|
54
|
-
|
|
55
|
-
const container = await createKuckitContainer({ config })
|
|
56
|
-
|
|
57
|
-
await loadKuckitModules({
|
|
58
|
-
container,
|
|
59
|
-
env: config.env,
|
|
60
|
-
modules: getModuleSpecs(),
|
|
61
|
-
onApiRegistrations: (registrations) => {
|
|
62
|
-
wireModuleRpcRouters(registrations)
|
|
63
|
-
console.log(`Loaded ${registrations.length} API registrations from modules`)
|
|
64
|
-
},
|
|
65
|
-
onComplete: () => {
|
|
66
|
-
console.log('All modules loaded successfully')
|
|
67
|
-
},
|
|
68
|
-
})
|
|
69
|
-
|
|
70
|
-
return container as AppContainer
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
/**
|
|
74
|
-
* Cleanup container resources
|
|
75
|
-
*/
|
|
76
|
-
export const disposeContainer = async (container: AppContainer): Promise<void> => {
|
|
77
|
-
const { dbPool } = container.cradle
|
|
78
|
-
if (dbPool && typeof dbPool.end === 'function') {
|
|
79
|
-
await dbPool.end()
|
|
80
|
-
}
|
|
81
|
-
}
|
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
import type { Express } from 'express'
|
|
2
|
-
import type { AppContainer } from './container'
|
|
3
|
-
|
|
4
|
-
export const setupHealth = (app: Express, container: AppContainer) => {
|
|
5
|
-
app.get('/health', async (_req, res) => {
|
|
6
|
-
const { dbPool } = container.cradle
|
|
7
|
-
|
|
8
|
-
try {
|
|
9
|
-
const client = await dbPool.connect()
|
|
10
|
-
await client.query('SELECT 1')
|
|
11
|
-
client.release()
|
|
12
|
-
|
|
13
|
-
res.status(200).json({
|
|
14
|
-
status: 'healthy',
|
|
15
|
-
timestamp: new Date().toISOString(),
|
|
16
|
-
checks: { database: 'ok' },
|
|
17
|
-
})
|
|
18
|
-
} catch (error) {
|
|
19
|
-
console.error('[Health] Database check failed:', error)
|
|
20
|
-
res.status(503).json({
|
|
21
|
-
status: 'unhealthy',
|
|
22
|
-
timestamp: new Date().toISOString(),
|
|
23
|
-
checks: { database: 'failed' },
|
|
24
|
-
})
|
|
25
|
-
}
|
|
26
|
-
})
|
|
27
|
-
}
|
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
import type { Express, Request, Response, NextFunction } from 'express'
|
|
2
|
-
import { asValue } from 'awilix'
|
|
3
|
-
import { buildRootContainer, type AppContainer } from '../container'
|
|
4
|
-
|
|
5
|
-
let rootContainer: AppContainer
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
* Initialize and setup container middleware
|
|
9
|
-
* Must be awaited before using the container
|
|
10
|
-
*/
|
|
11
|
-
export const setupContainerMiddleware = async (app: Express): Promise<void> => {
|
|
12
|
-
rootContainer = await buildRootContainer()
|
|
13
|
-
|
|
14
|
-
app.use((req: Request, res: Response, next: NextFunction) => {
|
|
15
|
-
req.scope = rootContainer.createScope()
|
|
16
|
-
|
|
17
|
-
req.scope.register({
|
|
18
|
-
requestId: asValue(crypto.randomUUID()),
|
|
19
|
-
})
|
|
20
|
-
|
|
21
|
-
res.on('finish', () => {
|
|
22
|
-
req.scope?.dispose()
|
|
23
|
-
})
|
|
24
|
-
|
|
25
|
-
res.on('close', () => {
|
|
26
|
-
req.scope?.dispose()
|
|
27
|
-
})
|
|
28
|
-
|
|
29
|
-
next()
|
|
30
|
-
})
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
/**
|
|
34
|
-
* Get root container (for graceful shutdown)
|
|
35
|
-
*/
|
|
36
|
-
export const getRootContainer = (): AppContainer => {
|
|
37
|
-
if (!rootContainer) {
|
|
38
|
-
throw new Error('Container not initialized')
|
|
39
|
-
}
|
|
40
|
-
return rootContainer
|
|
41
|
-
}
|