@everystack/mcp 0.3.2 → 0.4.0

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.
Files changed (104) hide show
  1. package/dist/adding-database.md +36 -23
  2. package/dist/cli.md +44 -3
  3. package/dist/core.md +19 -1
  4. package/dist/database-operations.md +236 -0
  5. package/dist/derived-objects.md +225 -0
  6. package/dist/gates/detectors/embedded-data-bundle.d.ts +13 -0
  7. package/dist/gates/detectors/embedded-data-bundle.d.ts.map +1 -0
  8. package/dist/gates/detectors/embedded-data-bundle.js +56 -0
  9. package/dist/gates/detectors/hand-written-migration.d.ts +24 -0
  10. package/dist/gates/detectors/hand-written-migration.d.ts.map +1 -0
  11. package/dist/gates/detectors/hand-written-migration.js +54 -0
  12. package/dist/gates/detectors/secret-in-public-env.d.ts +12 -0
  13. package/dist/gates/detectors/secret-in-public-env.d.ts.map +1 -0
  14. package/dist/gates/detectors/secret-in-public-env.js +38 -0
  15. package/dist/gates/engine.d.ts +28 -0
  16. package/dist/gates/engine.d.ts.map +1 -0
  17. package/dist/gates/engine.js +74 -0
  18. package/dist/gates/registry.d.ts +14 -0
  19. package/dist/gates/registry.d.ts.map +1 -0
  20. package/dist/gates/registry.js +21 -0
  21. package/dist/gates/telemetry.d.ts +47 -0
  22. package/dist/gates/telemetry.d.ts.map +1 -0
  23. package/dist/gates/telemetry.js +121 -0
  24. package/dist/gates/types.d.ts +67 -0
  25. package/dist/gates/types.d.ts.map +1 -0
  26. package/dist/gates/types.js +14 -0
  27. package/dist/governance/cli.d.ts +57 -0
  28. package/dist/governance/cli.d.ts.map +1 -0
  29. package/dist/governance/cli.js +169 -0
  30. package/dist/governance/grounding.d.ts +78 -0
  31. package/dist/governance/grounding.d.ts.map +1 -0
  32. package/dist/governance/grounding.js +299 -0
  33. package/dist/index.cjs +119 -107
  34. package/dist/index.d.ts +2 -0
  35. package/dist/index.d.ts.map +1 -0
  36. package/dist/index.js +94 -0
  37. package/dist/project-claude-md.md +35 -18
  38. package/dist/prompts/add-feature.d.ts +3 -0
  39. package/dist/prompts/add-feature.d.ts.map +1 -0
  40. package/dist/prompts/add-feature.js +154 -0
  41. package/dist/prompts/claude-md.d.ts +12 -0
  42. package/dist/prompts/claude-md.d.ts.map +1 -0
  43. package/dist/prompts/claude-md.js +87 -0
  44. package/dist/prompts/debug.d.ts +3 -0
  45. package/dist/prompts/debug.d.ts.map +1 -0
  46. package/dist/prompts/debug.js +129 -0
  47. package/dist/prompts/deploy.d.ts +3 -0
  48. package/dist/prompts/deploy.d.ts.map +1 -0
  49. package/dist/prompts/deploy.js +118 -0
  50. package/dist/prompts/design-schema.d.ts +3 -0
  51. package/dist/prompts/design-schema.d.ts.map +1 -0
  52. package/dist/prompts/design-schema.js +97 -0
  53. package/dist/prompts/governance-setup.d.ts +9 -0
  54. package/dist/prompts/governance-setup.d.ts.map +1 -0
  55. package/dist/prompts/governance-setup.js +76 -0
  56. package/dist/prompts/index.d.ts +3 -0
  57. package/dist/prompts/index.d.ts.map +1 -0
  58. package/dist/prompts/index.js +20 -0
  59. package/dist/prompts/new-app.d.ts +3 -0
  60. package/dist/prompts/new-app.d.ts.map +1 -0
  61. package/dist/prompts/new-app.js +203 -0
  62. package/dist/prompts/runbook.d.ts +12 -0
  63. package/dist/prompts/runbook.d.ts.map +1 -0
  64. package/dist/prompts/runbook.js +70 -0
  65. package/dist/prompts/secure.d.ts +3 -0
  66. package/dist/prompts/secure.d.ts.map +1 -0
  67. package/dist/prompts/secure.js +219 -0
  68. package/dist/resources/index.d.ts +12 -0
  69. package/dist/resources/index.d.ts.map +1 -0
  70. package/dist/resources/index.js +182 -0
  71. package/dist/schema-patterns.md +92 -106
  72. package/dist/tools/check-environment.d.ts +25 -0
  73. package/dist/tools/check-environment.d.ts.map +1 -0
  74. package/dist/tools/check-environment.js +281 -0
  75. package/dist/tools/index.d.ts +3 -0
  76. package/dist/tools/index.d.ts.map +1 -0
  77. package/dist/tools/index.js +18 -0
  78. package/dist/tools/project-status.d.ts +28 -0
  79. package/dist/tools/project-status.d.ts.map +1 -0
  80. package/dist/tools/project-status.js +138 -0
  81. package/dist/tools/project-validate.d.ts +19 -0
  82. package/dist/tools/project-validate.d.ts.map +1 -0
  83. package/dist/tools/project-validate.js +323 -0
  84. package/dist/tools/schema-analyze.d.ts +46 -0
  85. package/dist/tools/schema-analyze.d.ts.map +1 -0
  86. package/dist/tools/schema-analyze.js +336 -0
  87. package/package.json +3 -3
  88. package/src/gates/detectors/hand-written-migration.ts +29 -13
  89. package/src/index.ts +1 -1
  90. package/src/prompts/add-feature.ts +4 -4
  91. package/src/prompts/claude-md.ts +4 -2
  92. package/src/prompts/debug.ts +3 -4
  93. package/src/prompts/deploy.ts +17 -8
  94. package/src/prompts/design-schema.ts +45 -59
  95. package/src/prompts/new-app.ts +21 -19
  96. package/src/prompts/secure.ts +3 -3
  97. package/src/resources/adding-database.md +36 -23
  98. package/src/resources/cli.md +44 -3
  99. package/src/resources/core.md +19 -1
  100. package/src/resources/database-operations.md +236 -0
  101. package/src/resources/derived-objects.md +225 -0
  102. package/src/resources/index.ts +15 -1
  103. package/src/resources/project-claude-md.md +35 -18
  104. package/src/resources/schema-patterns.md +92 -106
@@ -1,167 +1,153 @@
1
1
  # Schema Design Patterns
2
2
 
3
- > Drizzle schema design, migrations, and RLS setup for everystack apps.
3
+ > Declaring TABLES with `defineModel` in `db/models/` fields, authz, relations, indexes.
4
4
 
5
5
  ## When to Use
6
- Read this when designing your database schema or adding tables to an existing app.
6
+ Read this when designing your database schema or adding tables to an existing app. This
7
+ resource covers **tables** (`defineModel`). For the derived layer —
8
+ views, materialized views, functions, triggers, standalone sequences — read
9
+ `everystack://derived-objects` (they deploy via `db:reconcile`, not migrations). For the
10
+ operational verbs that move a database (`db:sync`, `db:generate`, `db:plan`/`db:apply`,
11
+ `db:check`), read `everystack://database-operations`.
7
12
 
8
13
  ## Schema Location
9
14
 
10
- Schemas live in `db/schema.ts` (or `db/schema/` directory for larger apps). This file is the single source of truth for both the handler and SSR.
15
+ The whole declared database lives in `db/models/` one `defineModel` per table, composed
16
+ onto a `defineModule`. This is the single source of truth for the handler, SSR, RLS, and
17
+ grants. You do **not** hand-write `db/schema.ts`, and you do **not** run
18
+ `drizzle-kit generate`/`drizzle-kit migrate` — the Model is authored, and everystack
19
+ generates the migration (`db:generate`) and the typed artifact (`db/schema.generated.ts`,
20
+ never edited). Never hand-write a SQL migration.
11
21
 
12
22
  ## Basic Table
13
23
 
14
24
  ```typescript
15
- import { pgTable, serial, text, integer, timestamp, uuid } from 'drizzle-orm/pg-core';
16
-
17
- export const posts = pgTable('posts', {
18
- id: uuid('id').defaultRandom().primaryKey(),
19
- body: text('body').notNull(),
20
- authorId: uuid('author_id').notNull().references(() => users.id),
21
- status: text('status').default('draft').notNull(),
22
- createdAt: timestamp('created_at').defaultNow().notNull(),
23
- updatedAt: timestamp('updated_at').defaultNow().notNull(),
24
- deletedAt: timestamp('deleted_at'),
25
+ import { defineModel, field, can } from '@everystack/model';
26
+
27
+ export const Post = defineModel('posts', {
28
+ fields: {
29
+ id: field.uuid().primaryKey().defaultRandom(),
30
+ body: field.text().notNull(),
31
+ authorId: field.uuid().notNull().references('users', 'id'),
32
+ status: field.text().notNull().default('draft'),
33
+ createdAt: field.timestamptz().notNull().defaultNow(),
34
+ updatedAt: field.timestamptz().notNull().defaultNow(),
35
+ deletedAt: field.timestamptz(),
36
+ },
37
+ abilities: [can('read'), can('create'), can('update'), can('delete')],
25
38
  });
26
39
  ```
27
40
 
41
+ Column names are `snake_case` in the database; the field key is the app-side name — the
42
+ generator maps `authorId` → `author_id` for you.
43
+
28
44
  ## Common Patterns
29
45
 
30
46
  ### UUID vs Serial Primary Keys
31
47
  Prefer UUID for user-facing IDs (prevents enumeration attacks). Use serial for internal-only tables.
32
48
 
33
49
  ```typescript
34
- id: uuid('id').defaultRandom().primaryKey(), // Preferred for API-exposed tables
35
- id: serial('id').primaryKey(), // OK for internal tables
50
+ id: field.uuid().primaryKey().defaultRandom(), // Preferred for API-exposed tables
51
+ id: field.serial().primaryKey(), // OK for internal tables
36
52
  ```
37
53
 
38
54
  ### Timestamps
39
55
  Always include `createdAt` and `updatedAt`:
40
56
  ```typescript
41
- createdAt: timestamp('created_at').defaultNow().notNull(),
42
- updatedAt: timestamp('updated_at').defaultNow().notNull(),
57
+ createdAt: field.timestamptz().notNull().defaultNow(),
58
+ updatedAt: field.timestamptz().notNull().defaultNow(),
43
59
  ```
44
60
 
45
61
  ### Soft Delete
46
62
  Add `deletedAt` for soft-deletable tables:
47
63
  ```typescript
48
- deletedAt: timestamp('deleted_at'),
64
+ deletedAt: field.timestamptz(),
49
65
  ```
50
66
  Configure in handler: `softDelete: { column: 'deletedAt', tables: ['posts'] }`.
51
67
 
52
68
  ### Foreign Keys
53
69
  ```typescript
54
- authorId: uuid('author_id').notNull().references(() => users.id),
70
+ authorId: field.uuid().notNull().references('users', 'id'),
55
71
  ```
56
72
 
57
73
  ### Enums
58
74
  ```typescript
59
- import { pgEnum } from 'drizzle-orm/pg-core';
60
-
61
- export const roleEnum = pgEnum('role', ['user', 'admin']);
62
-
63
- export const users = pgTable('users', {
64
- role: roleEnum('role').default('user').notNull(),
65
- });
75
+ role: field.enum('role', ['user', 'admin']).notNull().default('user'),
66
76
  ```
67
77
 
68
- ## Relations (Drizzle)
78
+ ## Relations
69
79
 
70
- ```typescript
71
- import { relations } from 'drizzle-orm';
80
+ Declare relations on the Model — the generator emits both the Drizzle relations (used by
81
+ the relational query API, `db.query.posts.findMany({ with: { author: true } })`) and the
82
+ handler's embedding config, so they can't drift apart.
72
83
 
73
- export const postsRelations = relations(posts, ({ one, many }) => ({
74
- author: one(users, { fields: [posts.authorId], references: [users.id] }),
75
- comments: many(comments),
76
- }));
84
+ ```typescript
85
+ export const Post = defineModel('posts', {
86
+ fields: { /* … */ },
87
+ relations: {
88
+ author: { model: 'users', from: 'authorId', to: 'id' },
89
+ comments: { model: 'comments', from: 'id', to: 'postId', many: true },
90
+ },
91
+ abilities: [can('read')],
92
+ });
77
93
  ```
78
94
 
79
- Drizzle relations are used by the relational query API (`db.query.posts.findMany({ with: { author: true } })`). The handler also needs its own `relations` config for embedding.
80
-
81
95
  ## Indexes
82
96
 
83
97
  ```typescript
84
- import { index, uniqueIndex } from 'drizzle-orm/pg-core';
85
-
86
- export const posts = pgTable('posts', {
87
- // columns...
88
- }, (table) => [
89
- index('posts_author_id_idx').on(table.authorId),
90
- uniqueIndex('posts_slug_idx').on(table.slug),
91
- ]);
98
+ import { defineModel, field, can, index } from '@everystack/model';
99
+
100
+ export const Post = defineModel('posts', {
101
+ fields: { /* … */ },
102
+ indexes: [
103
+ index(['authorId']),
104
+ index(['slug']).unique(),
105
+ ],
106
+ abilities: [can('read')],
107
+ });
92
108
  ```
93
109
 
94
- ## Migration Workflow
95
-
96
- ```bash
97
- # Generate migration from schema changes
98
- npx drizzle-kit generate
99
-
100
- # Apply migrations locally
101
- npx drizzle-kit migrate
110
+ ## Authorization is declared, not migrated
102
111
 
103
- # Apply migrations on deployed Lambda
104
- everystack db:migrate
105
- ```
112
+ You never hand-write `CREATE ROLE`, `GRANT`, or `CREATE POLICY`. Abilities on the Model
113
+ compile to grants + RLS, and `db:sync`/`db:apply` deploy them. `can('read')` grants `anon`
114
+ + `authenticated`; `can('read', { role: 'admin' })` narrows to a role. RLS is required —
115
+ the CI gate (`db:check`) refuses a table without it. See `everystack://security` for the
116
+ policy patterns the abilities compile to.
106
117
 
107
- Migration files go in `drizzle/` directory. Each migration is a SQL file.
108
-
109
- ### RLS Migration Template
110
-
111
- After creating tables, add a migration for roles and policies:
112
-
113
- ```sql
114
- -- Create roles (idempotent)
115
- DO $$ BEGIN
116
- IF NOT EXISTS (SELECT FROM pg_roles WHERE rolname = 'authenticator') THEN
117
- CREATE ROLE authenticator LOGIN NOINHERIT;
118
- END IF;
119
- IF NOT EXISTS (SELECT FROM pg_roles WHERE rolname = 'anon') THEN
120
- CREATE ROLE anon NOLOGIN;
121
- END IF;
122
- IF NOT EXISTS (SELECT FROM pg_roles WHERE rolname = 'authenticated') THEN
123
- CREATE ROLE authenticated NOLOGIN;
124
- END IF;
125
- IF NOT EXISTS (SELECT FROM pg_roles WHERE rolname = 'admin') THEN
126
- CREATE ROLE admin NOLOGIN;
127
- END IF;
128
- END $$;
129
-
130
- GRANT anon TO authenticator;
131
- GRANT authenticated TO authenticator;
132
- GRANT admin TO authenticator;
133
-
134
- -- Table grants
135
- GRANT USAGE ON SCHEMA public TO anon, authenticated, admin;
136
- GRANT SELECT ON posts TO anon;
137
- GRANT SELECT, INSERT, UPDATE, DELETE ON posts TO authenticated;
138
- GRANT SELECT, INSERT, UPDATE, DELETE ON posts TO admin;
139
-
140
- -- Enable RLS
141
- ALTER TABLE posts ENABLE ROW LEVEL SECURITY;
142
- ALTER TABLE posts FORCE ROW LEVEL SECURITY;
143
-
144
- -- Policies (see everystack://security for templates)
118
+ ```typescript
119
+ export const Post = defineModel('posts', {
120
+ fields: { /* … */ },
121
+ abilities: [
122
+ can('read'), // anon + authenticated
123
+ can('create', { role: 'authenticated' }),
124
+ can('update', { role: 'authenticated', own: 'authorId' }), // row ownership
125
+ can('delete', { role: 'admin' }),
126
+ ],
127
+ });
145
128
  ```
146
129
 
147
130
  ## Handler Config for Schema
148
131
 
132
+ The handler config is **derived** from the Models (`deriveHandlerConfig(models)`), so
133
+ `exposedTables`, `hiddenColumns`, `protectedFields`, and `relations` come from the
134
+ declarations rather than a hand-kept parallel list:
135
+
149
136
  ```typescript
150
- createHandler(db, schema, {
151
- exposedTables: ['posts', 'profiles'], // Only these are API-accessible
152
- hiddenColumns: { users: ['passwordHash', 'resetToken'] },
153
- protectedFields: { profiles: ['role', 'deletedAt'] },
154
- relations: {
155
- posts: { author: { table: 'users', from: 'authorId', to: 'id' } },
156
- },
157
- });
137
+ import { deriveHandlerConfig } from '@everystack/model';
138
+ import { models } from '../db/models';
139
+
140
+ createHandler(db, schema, deriveHandlerConfig(models));
158
141
  ```
159
142
 
160
143
  ## Gotchas
161
144
 
162
- - Drizzle column names (camelCase) and SQL column names (snake_case) are both specified: `authorId: uuid('author_id')`
163
- - The handler's `relations` config uses Drizzle property names, not SQL column names
164
- - `exposedTables` uses Drizzle table variable names, not SQL table names
165
- - Always run `drizzle-kit generate` after schema changes
166
- - RLS migration must be run AFTER table creation migration
167
- - Connect as `authenticator` in production (never RDS master/superuser)
145
+ - Field keys are app-side names (camelCase); the database column is `snake_case` the
146
+ generator maps `authorId` `author_id`.
147
+ - Edit the Model, then move the database with `db:sync` (dev) or `db:generate` +
148
+ `db:plan`/`db:apply` (protected stages). Never run `drizzle-kit generate`.
149
+ - RLS and grants are declared with `can()`, never hand-written; `db:check` fails a table
150
+ without RLS.
151
+ - `db/schema.generated.ts` is a generated artifact — never edit it; `db:check` refuses
152
+ drift.
153
+ - Connect as `authenticator` in production (never RDS master/superuser).
@@ -0,0 +1,25 @@
1
+ export type EnvironmentPhase = 'local' | 'deploy';
2
+ export interface PrerequisiteCheck {
3
+ name: string;
4
+ required: boolean;
5
+ status: 'found' | 'not_found';
6
+ version: string | null;
7
+ installHint: string;
8
+ tier: 'V1' | 'V2';
9
+ phase: 'local' | 'deploy';
10
+ description: string;
11
+ whyNeeded: string;
12
+ installCommands: string[];
13
+ serviceRunning?: boolean;
14
+ }
15
+ export interface EnvironmentReport {
16
+ phase: EnvironmentPhase;
17
+ prerequisites: PrerequisiteCheck[];
18
+ ready: boolean;
19
+ readyForTier: 'V1' | 'V2' | 'V3' | 'none';
20
+ suggestions: string[];
21
+ platform: 'macOS' | 'linux' | 'windows' | 'unknown';
22
+ nextSteps: string[];
23
+ }
24
+ export declare function checkEnvironment(phase?: EnvironmentPhase): EnvironmentReport;
25
+ //# sourceMappingURL=check-environment.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"check-environment.d.ts","sourceRoot":"","sources":["../../src/tools/check-environment.ts"],"names":[],"mappings":"AAKA,MAAM,MAAM,gBAAgB,GAAG,OAAO,GAAG,QAAQ,CAAC;AAElD,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,OAAO,CAAC;IAClB,MAAM,EAAE,OAAO,GAAG,WAAW,CAAC;IAC9B,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,WAAW,EAAE,MAAM,CAAC;IACpB,IAAI,EAAE,IAAI,GAAG,IAAI,CAAC;IAClB,KAAK,EAAE,OAAO,GAAG,QAAQ,CAAC;IAC1B,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,eAAe,EAAE,MAAM,EAAE,CAAC;IAC1B,cAAc,CAAC,EAAE,OAAO,CAAC;CAC1B;AAED,MAAM,WAAW,iBAAiB;IAChC,KAAK,EAAE,gBAAgB,CAAC;IACxB,aAAa,EAAE,iBAAiB,EAAE,CAAC;IACnC,KAAK,EAAE,OAAO,CAAC;IACf,YAAY,EAAE,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,MAAM,CAAC;IAC1C,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,QAAQ,EAAE,OAAO,GAAG,OAAO,GAAG,SAAS,GAAG,SAAS,CAAC;IACpD,SAAS,EAAE,MAAM,EAAE,CAAC;CACrB;AAiCD,wBAAgB,gBAAgB,CAAC,KAAK,GAAE,gBAA0B,GAAG,iBAAiB,CAgQrF"}
@@ -0,0 +1,281 @@
1
+ import { execSync } from 'node:child_process';
2
+ import { existsSync } from 'node:fs';
3
+ import { join } from 'node:path';
4
+ import { homedir } from 'node:os';
5
+ function detectPlatform() {
6
+ const p = process.platform;
7
+ if (p === 'darwin')
8
+ return 'macOS';
9
+ if (p === 'linux')
10
+ return 'linux';
11
+ if (p === 'win32')
12
+ return 'windows';
13
+ return 'unknown';
14
+ }
15
+ function tryExec(cmd, timeout = 10000) {
16
+ try {
17
+ return execSync(cmd, { timeout, stdio: 'pipe', encoding: 'utf-8' }).trim();
18
+ }
19
+ catch {
20
+ return null;
21
+ }
22
+ }
23
+ function parseVersion(output, pattern) {
24
+ if (!output)
25
+ return null;
26
+ if (pattern) {
27
+ const match = output.match(pattern);
28
+ return match?.[1] ?? null;
29
+ }
30
+ return output.replace(/^v/, '');
31
+ }
32
+ function meetsMinVersion(version, major) {
33
+ if (!version)
34
+ return false;
35
+ const parsed = parseInt(version.split('.')[0], 10);
36
+ return !isNaN(parsed) && parsed >= major;
37
+ }
38
+ export function checkEnvironment(phase = 'local') {
39
+ const prerequisites = [];
40
+ const suggestions = [];
41
+ const platform = detectPlatform();
42
+ const isLocal = phase === 'local';
43
+ // Node.js
44
+ const nodeOutput = tryExec('node --version');
45
+ const nodeVersion = parseVersion(nodeOutput);
46
+ const nodeOk = meetsMinVersion(nodeVersion, 20);
47
+ prerequisites.push({
48
+ name: 'node',
49
+ required: true,
50
+ status: nodeVersion ? 'found' : 'not_found',
51
+ version: nodeVersion,
52
+ installHint: 'Install Node.js 20+ from https://nodejs.org',
53
+ tier: 'V1',
54
+ phase: 'local',
55
+ description: 'Node.js runs your code. Your app is written in TypeScript, and Node.js is the engine that executes it.',
56
+ whyNeeded: 'Without Node.js, none of the code can run.',
57
+ installCommands: platform === 'macOS'
58
+ ? ['brew install node', 'Or download from https://nodejs.org']
59
+ : platform === 'linux'
60
+ ? ['sudo apt install nodejs npm', 'Or download from https://nodejs.org']
61
+ : ['Download and install from https://nodejs.org'],
62
+ });
63
+ if (!nodeVersion) {
64
+ suggestions.push('Node.js is not installed. Install Node.js 20+ from https://nodejs.org');
65
+ }
66
+ else if (!nodeOk) {
67
+ suggestions.push(`Node.js ${nodeVersion} is installed but version 20+ is required. Upgrade at https://nodejs.org`);
68
+ }
69
+ // pnpm (recommended, not required)
70
+ const pnpmOutput = tryExec('pnpm --version');
71
+ const pnpmVersion = parseVersion(pnpmOutput);
72
+ prerequisites.push({
73
+ name: 'pnpm',
74
+ required: false,
75
+ status: pnpmVersion ? 'found' : 'not_found',
76
+ version: pnpmVersion,
77
+ installHint: 'Install pnpm: npm install -g pnpm (recommended for monorepos)',
78
+ tier: 'V1',
79
+ phase: 'local',
80
+ description: 'A faster way to install code libraries your app depends on. Recommended but not required.',
81
+ whyNeeded: 'Makes installing dependencies faster and more reliable.',
82
+ installCommands: ['npm install -g pnpm'],
83
+ });
84
+ if (!pnpmVersion) {
85
+ suggestions.push('pnpm is not installed. Recommended for monorepos: npm install -g pnpm');
86
+ }
87
+ // git
88
+ const gitOutput = tryExec('git --version');
89
+ const gitVersion = parseVersion(gitOutput, /git version (\d+\.\d+\.\d+)/);
90
+ prerequisites.push({
91
+ name: 'git',
92
+ required: true,
93
+ status: gitVersion ? 'found' : 'not_found',
94
+ version: gitVersion,
95
+ installHint: 'Install git from https://git-scm.com',
96
+ tier: 'V1',
97
+ phase: 'local',
98
+ description: 'Tracks every change to your code. Like an unlimited undo button for your entire project.',
99
+ whyNeeded: 'Without git, you cannot track changes, undo mistakes, or collaborate.',
100
+ installCommands: platform === 'macOS'
101
+ ? ['xcode-select --install']
102
+ : platform === 'linux'
103
+ ? ['sudo apt install git']
104
+ : ['Download from https://git-scm.com'],
105
+ });
106
+ if (!gitVersion) {
107
+ suggestions.push('git is not installed. Install from https://git-scm.com');
108
+ }
109
+ // Expo CLI
110
+ const expoOutput = tryExec('npx expo --version');
111
+ const expoVersion = parseVersion(expoOutput);
112
+ prerequisites.push({
113
+ name: 'expo',
114
+ required: true,
115
+ status: expoVersion ? 'found' : 'not_found',
116
+ version: expoVersion,
117
+ installHint: 'Expo is available via npx. Ensure Node.js is installed, then run: npx create-expo-app',
118
+ tier: 'V1',
119
+ phase: 'local',
120
+ description: 'The framework that makes your app work on phones, tablets, and the web from the same code.',
121
+ whyNeeded: 'Without Expo, you cannot create or run the app.',
122
+ installCommands: ['Runs via npx (no separate install needed). Requires Node.js.'],
123
+ });
124
+ if (!expoVersion) {
125
+ suggestions.push('Expo CLI not available. Ensure Node.js is installed — Expo runs via npx.');
126
+ }
127
+ // SST (deploy phase only)
128
+ const sstOutput = tryExec('npx sst version');
129
+ const sstVersion = parseVersion(sstOutput, /(\d+\.\d+\.\d+)/);
130
+ prerequisites.push({
131
+ name: 'sst',
132
+ required: !isLocal,
133
+ status: sstVersion ? 'found' : 'not_found',
134
+ version: sstVersion,
135
+ installHint: 'SST is installed as a dev dependency: pnpm add -D sst (or npx sst)',
136
+ tier: 'V1',
137
+ phase: 'deploy',
138
+ description: isLocal
139
+ ? 'Manages your cloud infrastructure. Not needed yet — you can build and run your app locally first.'
140
+ : 'Manages your cloud infrastructure. Tells AWS what servers, databases, and storage your app needs.',
141
+ whyNeeded: isLocal
142
+ ? 'Only needed when you are ready to deploy your app to the internet.'
143
+ : 'Without SST, you cannot deploy your app to the internet.',
144
+ installCommands: ['Installed as a project dependency when you create your app.'],
145
+ });
146
+ if (!sstVersion && !isLocal) {
147
+ suggestions.push('SST not found. It installs as a dev dependency: pnpm add -D sst');
148
+ }
149
+ // AWS CLI (deploy phase only)
150
+ const awsOutput = tryExec('aws --version');
151
+ const awsVersion = parseVersion(awsOutput, /aws-cli\/(\d+\.\d+\.\d+)/);
152
+ prerequisites.push({
153
+ name: 'aws-cli',
154
+ required: !isLocal,
155
+ status: awsVersion ? 'found' : 'not_found',
156
+ version: awsVersion,
157
+ installHint: 'Install AWS CLI from https://aws.amazon.com/cli/',
158
+ tier: 'V1',
159
+ phase: 'deploy',
160
+ description: isLocal
161
+ ? 'A command-line tool for Amazon Web Services. Not needed yet — you can build and run your app locally first.'
162
+ : 'A command-line tool that lets your computer talk to Amazon Web Services, where your app runs.',
163
+ whyNeeded: isLocal
164
+ ? 'Only needed when you are ready to deploy your app to the internet.'
165
+ : 'Without it, you cannot deploy or manage your app on AWS.',
166
+ installCommands: platform === 'macOS'
167
+ ? ['brew install awscli']
168
+ : platform === 'linux'
169
+ ? ['curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip" && unzip awscliv2.zip && sudo ./aws/install']
170
+ : ['Download from https://aws.amazon.com/cli/'],
171
+ });
172
+ if (!awsVersion && !isLocal) {
173
+ suggestions.push('AWS CLI is not installed. Install from https://aws.amazon.com/cli/');
174
+ }
175
+ // AWS credentials (deploy phase only)
176
+ const home = homedir();
177
+ const hasAwsCreds = existsSync(join(home, '.aws', 'credentials')) || existsSync(join(home, '.aws', 'config'));
178
+ prerequisites.push({
179
+ name: 'aws-credentials',
180
+ required: !isLocal,
181
+ status: hasAwsCreds ? 'found' : 'not_found',
182
+ version: null,
183
+ installHint: 'Read everystack://aws-setup for step-by-step AWS account and credential setup.',
184
+ tier: 'V1',
185
+ phase: 'deploy',
186
+ description: isLocal
187
+ ? 'A password file for your AWS account. Not needed yet — you can build and run your app locally first.'
188
+ : 'A password file that proves your computer is allowed to deploy to your AWS account.',
189
+ whyNeeded: isLocal
190
+ ? 'Only needed when you are ready to deploy your app to the internet.'
191
+ : 'Without credentials, AWS will reject all deploy commands.',
192
+ installCommands: ['Read everystack://aws-setup for full setup guide', 'aws configure'],
193
+ });
194
+ if (!hasAwsCreds && !isLocal) {
195
+ suggestions.push("AWS credentials not configured. Read everystack://aws-setup for step-by-step setup, or run 'aws configure' if you already have access keys.");
196
+ }
197
+ // PostgreSQL
198
+ const psqlOutput = tryExec('psql --version');
199
+ const psqlVersion = parseVersion(psqlOutput, /psql \(PostgreSQL\) (\d+\.\d+)/);
200
+ const pgReadyOutput = psqlVersion ? tryExec('pg_isready') : null;
201
+ const pgServiceUp = pgReadyOutput !== null && pgReadyOutput.includes('accepting connections');
202
+ prerequisites.push({
203
+ name: 'postgresql',
204
+ required: true,
205
+ status: psqlVersion ? 'found' : 'not_found',
206
+ version: psqlVersion,
207
+ serviceRunning: psqlVersion ? pgServiceUp : undefined,
208
+ installHint: 'Install PostgreSQL: brew install postgresql (macOS) or https://www.postgresql.org/download/',
209
+ tier: 'V2',
210
+ phase: 'local',
211
+ description: 'A database that stores your app data: users, posts, recipes, or whatever your app needs.',
212
+ whyNeeded: 'Without PostgreSQL, your app cannot have user accounts or saved data (needed for V2+).',
213
+ installCommands: platform === 'macOS'
214
+ ? ['brew install postgresql@16', 'brew services start postgresql@16']
215
+ : platform === 'linux'
216
+ ? ['sudo apt install postgresql', 'sudo systemctl start postgresql']
217
+ : ['Download from https://www.postgresql.org/download/'],
218
+ });
219
+ if (!psqlVersion) {
220
+ suggestions.push('PostgreSQL is not installed. Needed for V2+ (database tier). Install: brew install postgresql');
221
+ }
222
+ else if (!pgServiceUp) {
223
+ suggestions.push(platform === 'macOS'
224
+ ? 'PostgreSQL is installed but the service is not running. Start it: brew services start postgresql@16'
225
+ : platform === 'linux'
226
+ ? 'PostgreSQL is installed but the service is not running. Start it: sudo systemctl start postgresql'
227
+ : 'PostgreSQL is installed but the service is not running. Start the PostgreSQL service.');
228
+ }
229
+ // Determine readiness — in local phase, only local-phase prerequisites count
230
+ const relevant = isLocal
231
+ ? prerequisites.filter(c => c.phase === 'local')
232
+ : prerequisites;
233
+ const v1Required = relevant.filter(c => c.tier === 'V1' && c.required);
234
+ const v2Required = relevant.filter(c => c.tier === 'V2' && c.required);
235
+ const v1Ready = nodeOk && v1Required.every(c => c.status === 'found');
236
+ const v2Ready = v1Ready && v2Required.every(c => c.status === 'found');
237
+ // V3 uses same prerequisites as V2 (adds SQS/S3 which are AWS services, no local tooling)
238
+ const v3Ready = v2Ready;
239
+ const readyForTier = v3Ready ? 'V3' : v2Ready ? 'V2' : v1Ready ? 'V1' : 'none';
240
+ // Build nextSteps
241
+ const nextSteps = [];
242
+ const missing = relevant.filter(p => p.required && p.status === 'not_found');
243
+ if (missing.length === 0) {
244
+ if (isLocal) {
245
+ nextSteps.push('All local development prerequisites installed. Ready to create and run your app.');
246
+ // Hint about deploy-phase tools if not yet installed
247
+ const deployMissing = prerequisites.filter(p => p.phase === 'deploy' && p.status === 'not_found');
248
+ if (deployMissing.length > 0) {
249
+ nextSteps.push('When you are ready to put your app on the internet, run check_environment with phase "deploy" to see what else is needed.');
250
+ }
251
+ }
252
+ else {
253
+ nextSteps.push('All prerequisites installed. Ready to create a new app.');
254
+ }
255
+ }
256
+ else {
257
+ const installOrder = isLocal
258
+ ? ['node', 'git', 'pnpm', 'expo', 'postgresql']
259
+ : ['node', 'git', 'pnpm', 'aws-cli', 'aws-credentials', 'expo', 'sst', 'postgresql'];
260
+ const ordered = installOrder
261
+ .map(name => missing.find(p => p.name === name))
262
+ .filter((p) => p !== undefined);
263
+ for (const p of ordered) {
264
+ if (p.tier === 'V2') {
265
+ nextSteps.push(`Optional for now: Install ${p.name} — ${p.description.split('.')[0]}. Only needed for apps with user accounts or a database (V2+).`);
266
+ }
267
+ else {
268
+ nextSteps.push(`Install ${p.name} — ${p.description.split('.')[0]}. ${p.installCommands[0]}`);
269
+ }
270
+ }
271
+ }
272
+ return {
273
+ phase,
274
+ prerequisites,
275
+ ready: readyForTier !== 'none',
276
+ readyForTier,
277
+ suggestions,
278
+ platform,
279
+ nextSteps,
280
+ };
281
+ }
@@ -0,0 +1,3 @@
1
+ import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ export declare function registerTools(server: McpServer): void;
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/tools/index.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AASzE,wBAAgB,aAAa,CAAC,MAAM,EAAE,SAAS,GAAG,IAAI,CAcrD"}
@@ -0,0 +1,18 @@
1
+ import { z } from 'zod';
2
+ import { checkEnvironment } from './check-environment.js';
3
+ // NOTE (Brick 0): the legacy regex tools — project_status, schema_analyze,
4
+ // project_validate — are intentionally NOT registered. They predate the v3
5
+ // Model/Module surface and return wrong answers on Model-based apps (e.g. they
6
+ // report a securely-derived handler config as "all tables accessible"). They are
7
+ // rebuilt on @everystack/model in Brick 1 and re-registered there. Their source
8
+ // files remain for that rewrite to reference.
9
+ export function registerTools(server) {
10
+ server.tool('check_environment', 'Verify development prerequisites are installed (Node.js, pnpm, git, Expo, SST, AWS CLI, PostgreSQL). Run this first when a user wants to start a new project or is having setup issues. Use phase "local" (default) to check only what is needed to build and run locally. Use phase "deploy" when ready to deploy to AWS.', {
11
+ phase: z.enum(['local', 'deploy']).optional().describe('Check prerequisites for local development (default) or AWS deployment'),
12
+ }, async ({ phase }) => {
13
+ const report = checkEnvironment(phase ?? 'local');
14
+ return {
15
+ content: [{ type: 'text', text: JSON.stringify(report, null, 2) }],
16
+ };
17
+ });
18
+ }
@@ -0,0 +1,28 @@
1
+ interface ProjectStatus {
2
+ tier: 'V1' | 'V2' | 'V3' | 'unknown';
3
+ tierExplanation: string;
4
+ packages: {
5
+ installed: string[];
6
+ missing: string[];
7
+ };
8
+ files: {
9
+ sstConfig: boolean;
10
+ schemaDir: boolean;
11
+ schemaFiles: string[];
12
+ migrationsDir: boolean;
13
+ serverDir: boolean;
14
+ handlerFile: string | null;
15
+ workerFile: string | null;
16
+ imageFile: string | null;
17
+ seedFile: string | null;
18
+ };
19
+ deployment: {
20
+ sstOutputs: boolean;
21
+ stage: string | null;
22
+ secrets: string[];
23
+ };
24
+ warnings: string[];
25
+ }
26
+ export declare function analyzeProjectStatus(projectPath: string): ProjectStatus;
27
+ export {};
28
+ //# sourceMappingURL=project-status.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"project-status.d.ts","sourceRoot":"","sources":["../../src/tools/project-status.ts"],"names":[],"mappings":"AAUA,UAAU,aAAa;IACrB,IAAI,EAAE,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,SAAS,CAAC;IACrC,eAAe,EAAE,MAAM,CAAC;IACxB,QAAQ,EAAE;QACR,SAAS,EAAE,MAAM,EAAE,CAAC;QACpB,OAAO,EAAE,MAAM,EAAE,CAAC;KACnB,CAAC;IACF,KAAK,EAAE;QACL,SAAS,EAAE,OAAO,CAAC;QACnB,SAAS,EAAE,OAAO,CAAC;QACnB,WAAW,EAAE,MAAM,EAAE,CAAC;QACtB,aAAa,EAAE,OAAO,CAAC;QACvB,SAAS,EAAE,OAAO,CAAC;QACnB,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;QAC3B,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;QAC1B,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;QACzB,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;KACzB,CAAC;IACF,UAAU,EAAE;QACV,UAAU,EAAE,OAAO,CAAC;QACpB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;QACrB,OAAO,EAAE,MAAM,EAAE,CAAC;KACnB,CAAC;IACF,QAAQ,EAAE,MAAM,EAAE,CAAC;CACpB;AAgCD,wBAAgB,oBAAoB,CAAC,WAAW,EAAE,MAAM,GAAG,aAAa,CAoHvE"}