ont-run 0.0.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.
Files changed (54) hide show
  1. package/README.md +228 -0
  2. package/bin/ont.ts +5 -0
  3. package/dist/bin/ont.d.ts +2 -0
  4. package/dist/bin/ont.js +13667 -0
  5. package/dist/index.js +23152 -0
  6. package/dist/src/browser/server.d.ts +16 -0
  7. package/dist/src/browser/transform.d.ts +87 -0
  8. package/dist/src/cli/commands/init.d.ts +12 -0
  9. package/dist/src/cli/commands/review.d.ts +17 -0
  10. package/dist/src/cli/index.d.ts +1 -0
  11. package/dist/src/cli/utils/config-loader.d.ts +13 -0
  12. package/dist/src/config/categorical.d.ts +76 -0
  13. package/dist/src/config/define.d.ts +46 -0
  14. package/dist/src/config/index.d.ts +4 -0
  15. package/dist/src/config/schema.d.ts +162 -0
  16. package/dist/src/config/types.d.ts +94 -0
  17. package/dist/src/index.d.ts +37 -0
  18. package/dist/src/lockfile/differ.d.ts +11 -0
  19. package/dist/src/lockfile/hasher.d.ts +31 -0
  20. package/dist/src/lockfile/index.d.ts +53 -0
  21. package/dist/src/lockfile/types.d.ts +90 -0
  22. package/dist/src/runtime/index.d.ts +28 -0
  23. package/dist/src/server/api/index.d.ts +20 -0
  24. package/dist/src/server/api/middleware.d.ts +34 -0
  25. package/dist/src/server/api/router.d.ts +18 -0
  26. package/dist/src/server/mcp/index.d.ts +23 -0
  27. package/dist/src/server/mcp/tools.d.ts +35 -0
  28. package/dist/src/server/resolver.d.ts +30 -0
  29. package/dist/src/server/start.d.ts +37 -0
  30. package/package.json +63 -0
  31. package/src/browser/server.ts +2567 -0
  32. package/src/browser/transform.ts +473 -0
  33. package/src/cli/commands/init.ts +226 -0
  34. package/src/cli/commands/review.ts +126 -0
  35. package/src/cli/index.ts +19 -0
  36. package/src/cli/utils/config-loader.ts +78 -0
  37. package/src/config/categorical.ts +101 -0
  38. package/src/config/define.ts +78 -0
  39. package/src/config/index.ts +23 -0
  40. package/src/config/schema.ts +196 -0
  41. package/src/config/types.ts +121 -0
  42. package/src/index.ts +53 -0
  43. package/src/lockfile/differ.ts +242 -0
  44. package/src/lockfile/hasher.ts +175 -0
  45. package/src/lockfile/index.ts +159 -0
  46. package/src/lockfile/types.ts +95 -0
  47. package/src/runtime/index.ts +114 -0
  48. package/src/server/api/index.ts +92 -0
  49. package/src/server/api/middleware.ts +118 -0
  50. package/src/server/api/router.ts +102 -0
  51. package/src/server/mcp/index.ts +182 -0
  52. package/src/server/mcp/tools.ts +199 -0
  53. package/src/server/resolver.ts +109 -0
  54. package/src/server/start.ts +151 -0
@@ -0,0 +1,473 @@
1
+ import { z } from "zod";
2
+ import { zodToJsonSchema } from "zod-to-json-schema";
3
+ import type { OntologyConfig } from "../config/types.js";
4
+ import type { OntologyDiff, FunctionChange } from "../lockfile/types.js";
5
+ import { getFieldFromMetadata } from "../config/categorical.js";
6
+
7
+ export type NodeType = "entity" | "function" | "accessGroup";
8
+ export type EdgeType = "operates-on" | "requires-access" | "depends-on";
9
+ export type ChangeStatus = "added" | "removed" | "modified" | "unchanged";
10
+
11
+ export interface GraphNode {
12
+ id: string;
13
+ type: NodeType;
14
+ label: string;
15
+ description: string;
16
+ metadata: {
17
+ inputs?: Record<string, unknown>;
18
+ outputs?: Record<string, unknown>;
19
+ resolver?: string;
20
+ functionCount?: number;
21
+ };
22
+ }
23
+
24
+ export interface GraphEdge {
25
+ id: string;
26
+ source: string;
27
+ target: string;
28
+ type: EdgeType;
29
+ label?: string;
30
+ }
31
+
32
+ export interface GraphData {
33
+ nodes: GraphNode[];
34
+ edges: GraphEdge[];
35
+ meta: {
36
+ ontologyName: string;
37
+ totalFunctions: number;
38
+ totalEntities: number;
39
+ totalAccessGroups: number;
40
+ };
41
+ }
42
+
43
+ export interface EnhancedGraphNode extends GraphNode {
44
+ changeStatus: ChangeStatus;
45
+ changeDetails?: FunctionChange;
46
+ }
47
+
48
+ export interface EnhancedGraphData {
49
+ nodes: EnhancedGraphNode[];
50
+ edges: GraphEdge[];
51
+ meta: GraphData["meta"] & {
52
+ hasChanges: boolean;
53
+ addedCount: number;
54
+ removedCount: number;
55
+ modifiedCount: number;
56
+ };
57
+ diff: OntologyDiff | null;
58
+ }
59
+
60
+ interface FieldReference {
61
+ path: string;
62
+ functionName: string;
63
+ }
64
+
65
+ /**
66
+ * Recursively extract fieldFrom references from a Zod schema
67
+ */
68
+ function extractFieldReferences(
69
+ schema: z.ZodType<unknown>,
70
+ path: string = ""
71
+ ): FieldReference[] {
72
+ const results: FieldReference[] = [];
73
+
74
+ const metadata = getFieldFromMetadata(schema);
75
+ if (metadata) {
76
+ results.push({
77
+ path: path || "(root)",
78
+ functionName: metadata.functionName,
79
+ });
80
+ }
81
+
82
+ if (schema instanceof z.ZodObject) {
83
+ const shape = schema.shape;
84
+ for (const [key, value] of Object.entries(shape)) {
85
+ const fieldPath = path ? `${path}.${key}` : key;
86
+ results.push(
87
+ ...extractFieldReferences(value as z.ZodType<unknown>, fieldPath)
88
+ );
89
+ }
90
+ }
91
+
92
+ if (schema instanceof z.ZodOptional) {
93
+ results.push(...extractFieldReferences(schema.unwrap(), path));
94
+ }
95
+
96
+ if (schema instanceof z.ZodNullable) {
97
+ results.push(...extractFieldReferences(schema.unwrap(), path));
98
+ }
99
+
100
+ if (schema instanceof z.ZodArray) {
101
+ results.push(...extractFieldReferences(schema.element, `${path}[]`));
102
+ }
103
+
104
+ if (schema instanceof z.ZodDefault) {
105
+ results.push(...extractFieldReferences(schema._def.innerType, path));
106
+ }
107
+
108
+ return results;
109
+ }
110
+
111
+ /**
112
+ * Convert Zod schema to JSON Schema safely
113
+ */
114
+ function safeZodToJsonSchema(schema: z.ZodTypeAny): Record<string, unknown> | undefined {
115
+ try {
116
+ const result = zodToJsonSchema(schema, { $refStrategy: "none" }) as Record<string, unknown>;
117
+ delete result.$schema;
118
+ return result;
119
+ } catch {
120
+ return { type: "unknown" };
121
+ }
122
+ }
123
+
124
+ /**
125
+ * Transform OntologyConfig into graph data for visualization
126
+ */
127
+ export function transformToGraphData(config: OntologyConfig): GraphData {
128
+ const nodes: GraphNode[] = [];
129
+ const edges: GraphEdge[] = [];
130
+
131
+ // Track function counts for access groups and entities
132
+ const accessGroupCounts: Record<string, number> = {};
133
+ const entityCounts: Record<string, number> = {};
134
+
135
+ // Initialize counts
136
+ for (const groupName of Object.keys(config.accessGroups)) {
137
+ accessGroupCounts[groupName] = 0;
138
+ }
139
+ if (config.entities) {
140
+ for (const entityName of Object.keys(config.entities)) {
141
+ entityCounts[entityName] = 0;
142
+ }
143
+ }
144
+
145
+ // Create function nodes and count relationships
146
+ for (const [name, fn] of Object.entries(config.functions)) {
147
+ // Count access group usage
148
+ for (const group of fn.access) {
149
+ accessGroupCounts[group] = (accessGroupCounts[group] || 0) + 1;
150
+ }
151
+
152
+ // Count entity usage
153
+ for (const entity of fn.entities) {
154
+ entityCounts[entity] = (entityCounts[entity] || 0) + 1;
155
+ }
156
+
157
+ // Create function node
158
+ nodes.push({
159
+ id: `function:${name}`,
160
+ type: "function",
161
+ label: name,
162
+ description: fn.description,
163
+ metadata: {
164
+ inputs: safeZodToJsonSchema(fn.inputs),
165
+ outputs: fn.outputs ? safeZodToJsonSchema(fn.outputs) : undefined,
166
+ resolver: fn.resolver,
167
+ },
168
+ });
169
+
170
+ // Create edges: function -> access groups
171
+ for (const group of fn.access) {
172
+ edges.push({
173
+ id: `function:${name}->accessGroup:${group}`,
174
+ source: `function:${name}`,
175
+ target: `accessGroup:${group}`,
176
+ type: "requires-access",
177
+ });
178
+ }
179
+
180
+ // Create edges: function -> entities
181
+ for (const entity of fn.entities) {
182
+ edges.push({
183
+ id: `function:${name}->entity:${entity}`,
184
+ source: `function:${name}`,
185
+ target: `entity:${entity}`,
186
+ type: "operates-on",
187
+ });
188
+ }
189
+
190
+ // Create edges: function -> function (fieldFrom dependencies)
191
+ const fieldRefs = extractFieldReferences(fn.inputs);
192
+ for (const ref of fieldRefs) {
193
+ edges.push({
194
+ id: `function:${name}->function:${ref.functionName}:${ref.path}`,
195
+ source: `function:${name}`,
196
+ target: `function:${ref.functionName}`,
197
+ type: "depends-on",
198
+ label: ref.path,
199
+ });
200
+ }
201
+ }
202
+
203
+ // Create access group nodes
204
+ for (const [name, group] of Object.entries(config.accessGroups)) {
205
+ nodes.push({
206
+ id: `accessGroup:${name}`,
207
+ type: "accessGroup",
208
+ label: name,
209
+ description: group.description,
210
+ metadata: {
211
+ functionCount: accessGroupCounts[name] || 0,
212
+ },
213
+ });
214
+ }
215
+
216
+ // Create entity nodes
217
+ if (config.entities) {
218
+ for (const [name, entity] of Object.entries(config.entities)) {
219
+ nodes.push({
220
+ id: `entity:${name}`,
221
+ type: "entity",
222
+ label: name,
223
+ description: entity.description,
224
+ metadata: {
225
+ functionCount: entityCounts[name] || 0,
226
+ },
227
+ });
228
+ }
229
+ }
230
+
231
+ return {
232
+ nodes,
233
+ edges,
234
+ meta: {
235
+ ontologyName: config.name,
236
+ totalFunctions: Object.keys(config.functions).length,
237
+ totalEntities: config.entities ? Object.keys(config.entities).length : 0,
238
+ totalAccessGroups: Object.keys(config.accessGroups).length,
239
+ },
240
+ };
241
+ }
242
+
243
+ /**
244
+ * Search nodes by label or description
245
+ */
246
+ export function searchNodes(
247
+ graphData: GraphData,
248
+ query: string
249
+ ): Array<{ id: string; type: NodeType; label: string; matchType: "label" | "description" }> {
250
+ const lowerQuery = query.toLowerCase();
251
+ const results: Array<{ id: string; type: NodeType; label: string; matchType: "label" | "description" }> = [];
252
+
253
+ for (const node of graphData.nodes) {
254
+ if (node.label.toLowerCase().includes(lowerQuery)) {
255
+ results.push({ id: node.id, type: node.type, label: node.label, matchType: "label" });
256
+ } else if (node.description.toLowerCase().includes(lowerQuery)) {
257
+ results.push({ id: node.id, type: node.type, label: node.label, matchType: "description" });
258
+ }
259
+ }
260
+
261
+ return results;
262
+ }
263
+
264
+ /**
265
+ * Get detailed node information including connections
266
+ */
267
+ export function getNodeDetails(
268
+ graphData: GraphData,
269
+ nodeId: string
270
+ ): {
271
+ node: GraphNode | null;
272
+ connections: {
273
+ accessGroups: string[];
274
+ entities: string[];
275
+ dependsOn: Array<{ functionName: string; path: string }>;
276
+ dependedOnBy: string[];
277
+ functions: Array<{
278
+ name: string;
279
+ description: string;
280
+ inputs?: Record<string, unknown>;
281
+ outputs?: Record<string, unknown>;
282
+ }>;
283
+ };
284
+ } {
285
+ const node = graphData.nodes.find((n) => n.id === nodeId) || null;
286
+
287
+ const connections = {
288
+ accessGroups: [] as string[],
289
+ entities: [] as string[],
290
+ dependsOn: [] as Array<{ functionName: string; path: string }>,
291
+ dependedOnBy: [] as string[],
292
+ functions: [] as Array<{
293
+ name: string;
294
+ description: string;
295
+ inputs?: Record<string, unknown>;
296
+ outputs?: Record<string, unknown>;
297
+ }>,
298
+ };
299
+
300
+ if (!node) return { node, connections };
301
+
302
+ for (const edge of graphData.edges) {
303
+ if (edge.source === nodeId) {
304
+ if (edge.type === "requires-access") {
305
+ connections.accessGroups.push(edge.target.replace("accessGroup:", ""));
306
+ } else if (edge.type === "operates-on") {
307
+ connections.entities.push(edge.target.replace("entity:", ""));
308
+ } else if (edge.type === "depends-on") {
309
+ connections.dependsOn.push({
310
+ functionName: edge.target.replace("function:", ""),
311
+ path: edge.label || "",
312
+ });
313
+ }
314
+ }
315
+
316
+ if (edge.target === nodeId) {
317
+ if (edge.type === "depends-on") {
318
+ connections.dependedOnBy.push(edge.source.replace("function:", ""));
319
+ } else if (edge.type === "requires-access" && node.type === "accessGroup") {
320
+ const funcName = edge.source.replace("function:", "");
321
+ const funcNode = graphData.nodes.find((n) => n.id === edge.source);
322
+ if (funcNode) {
323
+ connections.functions.push({
324
+ name: funcName,
325
+ description: funcNode.description,
326
+ inputs: funcNode.metadata.inputs,
327
+ outputs: funcNode.metadata.outputs,
328
+ });
329
+ }
330
+ } else if (edge.type === "operates-on" && node.type === "entity") {
331
+ const funcName = edge.source.replace("function:", "");
332
+ const funcNode = graphData.nodes.find((n) => n.id === edge.source);
333
+ if (funcNode) {
334
+ connections.functions.push({
335
+ name: funcName,
336
+ description: funcNode.description,
337
+ inputs: funcNode.metadata.inputs,
338
+ outputs: funcNode.metadata.outputs,
339
+ });
340
+ }
341
+ }
342
+ }
343
+ }
344
+
345
+ return { node, connections };
346
+ }
347
+
348
+ /**
349
+ * Enhance graph data with diff information for review mode
350
+ */
351
+ export function enhanceWithDiff(
352
+ graphData: GraphData,
353
+ diff: OntologyDiff | null
354
+ ): EnhancedGraphData {
355
+ if (!diff) {
356
+ // No diff - all nodes are unchanged
357
+ return {
358
+ nodes: graphData.nodes.map((node) => ({
359
+ ...node,
360
+ changeStatus: "unchanged" as ChangeStatus,
361
+ })),
362
+ edges: graphData.edges,
363
+ meta: {
364
+ ...graphData.meta,
365
+ hasChanges: false,
366
+ addedCount: 0,
367
+ removedCount: 0,
368
+ modifiedCount: 0,
369
+ },
370
+ diff: null,
371
+ };
372
+ }
373
+
374
+ // Build lookup maps for changes
375
+ const functionChanges = new Map<string, FunctionChange>();
376
+ for (const fn of diff.functions) {
377
+ functionChanges.set(fn.name, fn);
378
+ }
379
+
380
+ const addedGroups = new Set(diff.addedGroups);
381
+ const removedGroups = new Set(diff.removedGroups);
382
+ const addedEntities = new Set(diff.addedEntities);
383
+ const removedEntities = new Set(diff.removedEntities);
384
+
385
+ // Enhance existing nodes with change status
386
+ const enhancedNodes: EnhancedGraphNode[] = graphData.nodes.map((node) => {
387
+ let changeStatus: ChangeStatus = "unchanged";
388
+ let changeDetails: FunctionChange | undefined;
389
+
390
+ if (node.type === "function") {
391
+ const fnName = node.label;
392
+ const change = functionChanges.get(fnName);
393
+ if (change) {
394
+ changeStatus = change.type;
395
+ changeDetails = change;
396
+ }
397
+ } else if (node.type === "accessGroup") {
398
+ if (addedGroups.has(node.label)) {
399
+ changeStatus = "added";
400
+ }
401
+ } else if (node.type === "entity") {
402
+ if (addedEntities.has(node.label)) {
403
+ changeStatus = "added";
404
+ }
405
+ }
406
+
407
+ return {
408
+ ...node,
409
+ changeStatus,
410
+ changeDetails,
411
+ };
412
+ });
413
+
414
+ // Add ghost nodes for removed items
415
+ for (const group of removedGroups) {
416
+ enhancedNodes.push({
417
+ id: `accessGroup:${group}`,
418
+ type: "accessGroup",
419
+ label: group,
420
+ description: "Removed access group",
421
+ metadata: { functionCount: 0 },
422
+ changeStatus: "removed",
423
+ });
424
+ }
425
+
426
+ for (const entity of removedEntities) {
427
+ enhancedNodes.push({
428
+ id: `entity:${entity}`,
429
+ type: "entity",
430
+ label: entity,
431
+ description: "Removed entity",
432
+ metadata: { functionCount: 0 },
433
+ changeStatus: "removed",
434
+ });
435
+ }
436
+
437
+ // Add ghost nodes for removed functions
438
+ for (const fn of diff.functions.filter((f) => f.type === "removed")) {
439
+ enhancedNodes.push({
440
+ id: `function:${fn.name}`,
441
+ type: "function",
442
+ label: fn.name,
443
+ description: fn.oldDescription || "Removed function",
444
+ metadata: {},
445
+ changeStatus: "removed",
446
+ changeDetails: fn,
447
+ });
448
+ }
449
+
450
+ // Count changes
451
+ const addedCount =
452
+ diff.addedGroups.length +
453
+ diff.addedEntities.length +
454
+ diff.functions.filter((f) => f.type === "added").length;
455
+ const removedCount =
456
+ diff.removedGroups.length +
457
+ diff.removedEntities.length +
458
+ diff.functions.filter((f) => f.type === "removed").length;
459
+ const modifiedCount = diff.functions.filter((f) => f.type === "modified").length;
460
+
461
+ return {
462
+ nodes: enhancedNodes,
463
+ edges: graphData.edges,
464
+ meta: {
465
+ ...graphData.meta,
466
+ hasChanges: diff.hasChanges,
467
+ addedCount,
468
+ removedCount,
469
+ modifiedCount,
470
+ },
471
+ diff,
472
+ };
473
+ }
@@ -0,0 +1,226 @@
1
+ import { defineCommand } from "citty";
2
+ import { existsSync, mkdirSync, writeFileSync, readFileSync } from "fs";
3
+ import { join, dirname } from "path";
4
+ import { fileURLToPath } from "url";
5
+ import consola from "consola";
6
+
7
+ const __filename = fileURLToPath(import.meta.url);
8
+ const __dirname = dirname(__filename);
9
+
10
+ // Templates are bundled in the package
11
+ const TEMPLATES_DIR = join(__dirname, "..", "..", "..", "templates");
12
+
13
+ export const initCommand = defineCommand({
14
+ meta: {
15
+ name: "init",
16
+ description: "Initialize a new Ontology project",
17
+ },
18
+ args: {
19
+ dir: {
20
+ type: "positional",
21
+ description: "Directory to initialize (default: current directory)",
22
+ default: ".",
23
+ },
24
+ force: {
25
+ type: "boolean",
26
+ description: "Overwrite existing files",
27
+ default: false,
28
+ },
29
+ },
30
+ async run({ args }) {
31
+ const targetDir = args.dir === "." ? process.cwd() : join(process.cwd(), args.dir);
32
+
33
+ consola.info(`Initializing Ontology project in ${targetDir}`);
34
+
35
+ // Create directory if needed
36
+ if (!existsSync(targetDir)) {
37
+ mkdirSync(targetDir, { recursive: true });
38
+ }
39
+
40
+ // Check for existing config
41
+ const configPath = join(targetDir, "ontology.config.ts");
42
+ if (existsSync(configPath) && !args.force) {
43
+ consola.error("ontology.config.ts already exists. Use --force to overwrite.");
44
+ process.exit(1);
45
+ }
46
+
47
+ // Create resolvers directory
48
+ const resolversDir = join(targetDir, "resolvers");
49
+ if (!existsSync(resolversDir)) {
50
+ mkdirSync(resolversDir, { recursive: true });
51
+ }
52
+
53
+ // Write ontology.config.ts
54
+ const configTemplate = `import { defineOntology } from 'ont-run';
55
+ import { z } from 'zod';
56
+
57
+ export default defineOntology({
58
+ name: 'my-api',
59
+
60
+ environments: {
61
+ dev: { debug: true },
62
+ prod: { debug: false },
63
+ },
64
+
65
+ // Pluggable auth - customize this for your use case
66
+ auth: async (req) => {
67
+ const token = req.headers.get('Authorization');
68
+ // Return access groups based on auth
69
+ // This is where you'd verify JWTs, API keys, etc.
70
+ if (!token) return ['public'];
71
+ if (token === 'admin-secret') return ['admin', 'support', 'public'];
72
+ return ['support', 'public'];
73
+ },
74
+
75
+ accessGroups: {
76
+ public: { description: 'Unauthenticated users' },
77
+ support: { description: 'Support agents' },
78
+ admin: { description: 'Administrators' },
79
+ },
80
+
81
+ functions: {
82
+ // Example: Public function
83
+ healthCheck: {
84
+ description: 'Check API health status',
85
+ access: ['public', 'support', 'admin'],
86
+ inputs: z.object({}),
87
+ resolver: './resolvers/healthCheck.ts',
88
+ },
89
+
90
+ // Example: Restricted function
91
+ getUser: {
92
+ description: 'Get user details by ID',
93
+ access: ['support', 'admin'],
94
+ inputs: z.object({
95
+ userId: z.string().uuid(),
96
+ }),
97
+ resolver: './resolvers/getUser.ts',
98
+ },
99
+
100
+ // Example: Admin-only function
101
+ deleteUser: {
102
+ description: 'Delete a user account',
103
+ access: ['admin'],
104
+ inputs: z.object({
105
+ userId: z.string().uuid(),
106
+ reason: z.string().optional(),
107
+ }),
108
+ resolver: './resolvers/deleteUser.ts',
109
+ },
110
+ },
111
+ });
112
+ `;
113
+
114
+ writeFileSync(configPath, configTemplate);
115
+ consola.success("Created ontology.config.ts");
116
+
117
+ // Write example resolvers
118
+ const healthCheckResolver = `import type { ResolverContext } from 'ont-run';
119
+
120
+ export default async function healthCheck(ctx: ResolverContext, args: {}) {
121
+ ctx.logger.info('Health check called');
122
+
123
+ return {
124
+ status: 'ok',
125
+ env: ctx.env,
126
+ timestamp: new Date().toISOString(),
127
+ };
128
+ }
129
+ `;
130
+
131
+ const getUserResolver = `import type { ResolverContext } from 'ont-run';
132
+
133
+ interface GetUserArgs {
134
+ userId: string;
135
+ }
136
+
137
+ export default async function getUser(ctx: ResolverContext, args: GetUserArgs) {
138
+ ctx.logger.info(\`Getting user: \${args.userId}\`);
139
+
140
+ // This is where you'd query your database
141
+ // Example response:
142
+ return {
143
+ id: args.userId,
144
+ name: 'Example User',
145
+ email: 'user@example.com',
146
+ createdAt: '2025-01-01T00:00:00Z',
147
+ };
148
+ }
149
+ `;
150
+
151
+ const deleteUserResolver = `import type { ResolverContext } from 'ont-run';
152
+
153
+ interface DeleteUserArgs {
154
+ userId: string;
155
+ reason?: string;
156
+ }
157
+
158
+ export default async function deleteUser(ctx: ResolverContext, args: DeleteUserArgs) {
159
+ ctx.logger.warn(\`Deleting user: \${args.userId}, reason: \${args.reason || 'none'}\`);
160
+
161
+ // This is where you'd delete from your database
162
+ // Example response:
163
+ return {
164
+ success: true,
165
+ deletedUserId: args.userId,
166
+ deletedAt: new Date().toISOString(),
167
+ };
168
+ }
169
+ `;
170
+
171
+ writeFileSync(join(resolversDir, "healthCheck.ts"), healthCheckResolver);
172
+ writeFileSync(join(resolversDir, "getUser.ts"), getUserResolver);
173
+ writeFileSync(join(resolversDir, "deleteUser.ts"), deleteUserResolver);
174
+ consola.success("Created example resolvers in resolvers/");
175
+
176
+ // Write server.ts
177
+ const serverTemplate = `import { startOnt } from 'ont-run';
178
+
179
+ await startOnt();
180
+ `;
181
+
182
+ writeFileSync(join(targetDir, "server.ts"), serverTemplate);
183
+ consola.success("Created server.ts");
184
+
185
+ // Write package.json
186
+ const packageJsonPath = join(targetDir, "package.json");
187
+ let packageJson: Record<string, unknown> = {};
188
+
189
+ if (existsSync(packageJsonPath)) {
190
+ try {
191
+ packageJson = JSON.parse(readFileSync(packageJsonPath, "utf-8"));
192
+ } catch {
193
+ // If parsing fails, start fresh
194
+ }
195
+ }
196
+
197
+ // Merge in our scripts and dependencies
198
+ packageJson.type = "module";
199
+ packageJson.scripts = {
200
+ ...(packageJson.scripts as Record<string, string> || {}),
201
+ dev: "bun run server.ts",
202
+ start: "NODE_ENV=production bun run server.ts",
203
+ review: "bunx ont-run review",
204
+ };
205
+ packageJson.dependencies = {
206
+ ...(packageJson.dependencies as Record<string, string> || {}),
207
+ "ont-run": "latest",
208
+ zod: "^3.24.0",
209
+ };
210
+
211
+ writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 2));
212
+ consola.success("Updated package.json with scripts and dependencies");
213
+
214
+ // Instructions
215
+ console.log("\n");
216
+ consola.box(
217
+ "Ontology project initialized!\n\n" +
218
+ "Next steps:\n" +
219
+ " 1. Run `bun install` to install dependencies\n" +
220
+ " 2. Review ontology.config.ts and customize\n" +
221
+ " 3. Run `bun run review` to approve the initial ontology\n" +
222
+ " 4. Run `bun run dev` to start the servers\n\n" +
223
+ "Your API will be available at http://localhost:3000"
224
+ );
225
+ },
226
+ });