ont-run 0.0.2 → 0.0.4
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/README.md +70 -3
- package/dist/bin/ont.js +116 -8
- package/dist/index.js +17 -5
- package/dist/src/browser/transform.d.ts +1 -0
- package/dist/src/config/categorical.d.ts +3 -0
- package/package.json +1 -1
- package/src/browser/server.ts +31 -0
- package/src/browser/transform.ts +6 -0
- package/src/cli/index.ts +2 -1
- package/src/cli/utils/config-loader.ts +18 -3
- package/src/config/categorical.ts +7 -2
package/README.md
CHANGED
|
@@ -150,11 +150,16 @@ export default defineOntology({
|
|
|
150
150
|
prod: { debug: false },
|
|
151
151
|
},
|
|
152
152
|
|
|
153
|
+
// Auth returns access groups (and optional user identity)
|
|
153
154
|
auth: async (req: Request) => {
|
|
154
155
|
const token = req.headers.get('Authorization');
|
|
155
|
-
if (!token) return ['public'];
|
|
156
|
-
|
|
157
|
-
|
|
156
|
+
if (!token) return { groups: ['public'] };
|
|
157
|
+
|
|
158
|
+
const user = await verifyToken(token);
|
|
159
|
+
return {
|
|
160
|
+
groups: user.isAdmin ? ['admin', 'user', 'public'] : ['user', 'public'],
|
|
161
|
+
user: { id: user.id, email: user.email }, // Optional: for row-level access
|
|
162
|
+
};
|
|
158
163
|
},
|
|
159
164
|
|
|
160
165
|
accessGroups: {
|
|
@@ -180,6 +185,68 @@ export default defineOntology({
|
|
|
180
185
|
});
|
|
181
186
|
```
|
|
182
187
|
|
|
188
|
+
## Row-Level Access Control
|
|
189
|
+
|
|
190
|
+
The framework handles **group-based access** (user → group → function) out of the box. For **row-level ownership** (e.g., "users can only edit their own posts"), use `userContext()`:
|
|
191
|
+
|
|
192
|
+
```typescript
|
|
193
|
+
import { defineOntology, userContext, z } from 'ont-run';
|
|
194
|
+
|
|
195
|
+
export default defineOntology({
|
|
196
|
+
// Auth must return user identity for userContext to work
|
|
197
|
+
auth: async (req) => {
|
|
198
|
+
const user = await verifyToken(req);
|
|
199
|
+
return {
|
|
200
|
+
groups: ['user'],
|
|
201
|
+
user: { id: user.id, email: user.email },
|
|
202
|
+
};
|
|
203
|
+
},
|
|
204
|
+
|
|
205
|
+
functions: {
|
|
206
|
+
editPost: {
|
|
207
|
+
description: 'Edit a post',
|
|
208
|
+
access: ['user', 'admin'],
|
|
209
|
+
entities: ['Post'],
|
|
210
|
+
inputs: z.object({
|
|
211
|
+
postId: z.string(),
|
|
212
|
+
title: z.string(),
|
|
213
|
+
// currentUser is injected at runtime, hidden from API callers
|
|
214
|
+
currentUser: userContext(z.object({
|
|
215
|
+
id: z.string(),
|
|
216
|
+
email: z.string(),
|
|
217
|
+
})),
|
|
218
|
+
}),
|
|
219
|
+
resolver: './resolvers/editPost.ts',
|
|
220
|
+
},
|
|
221
|
+
},
|
|
222
|
+
});
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
In the resolver, you receive the typed user object:
|
|
226
|
+
|
|
227
|
+
```typescript
|
|
228
|
+
// resolvers/editPost.ts
|
|
229
|
+
export default async function editPost(
|
|
230
|
+
ctx: ResolverContext,
|
|
231
|
+
args: { postId: string; title: string; currentUser: { id: string; email: string } }
|
|
232
|
+
) {
|
|
233
|
+
const post = await db.posts.findById(args.postId);
|
|
234
|
+
|
|
235
|
+
// Row-level check: only author or admin can edit
|
|
236
|
+
if (args.currentUser.id !== post.authorId && !ctx.accessGroups.includes('admin')) {
|
|
237
|
+
throw new Error('Not authorized to edit this post');
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
return db.posts.update(args.postId, { title: args.title });
|
|
241
|
+
}
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
**Key points:**
|
|
245
|
+
- `userContext()` fields are **injected** from `auth()` result's `user` field
|
|
246
|
+
- They're **hidden** from public API/MCP schemas (callers don't see or provide them)
|
|
247
|
+
- They're **type-safe** in resolvers
|
|
248
|
+
- The review UI shows a badge for functions using user context
|
|
249
|
+
|
|
183
250
|
## The Lockfile
|
|
184
251
|
|
|
185
252
|
`ont.lock` is the enforcement mechanism. It contains a hash of your ontology:
|
package/dist/bin/ont.js
CHANGED
|
@@ -6170,8 +6170,9 @@ function hasUserContextMetadata(schema) {
|
|
|
6170
6170
|
}
|
|
6171
6171
|
function getUserContextFields(schema) {
|
|
6172
6172
|
const fields = [];
|
|
6173
|
-
|
|
6174
|
-
|
|
6173
|
+
const def = schema._def;
|
|
6174
|
+
if (def?.typeName === "ZodObject" && typeof def.shape === "function") {
|
|
6175
|
+
const shape = def.shape();
|
|
6175
6176
|
for (const [key, value] of Object.entries(shape)) {
|
|
6176
6177
|
if (hasUserContextMetadata(value)) {
|
|
6177
6178
|
fields.push(key);
|
|
@@ -6182,7 +6183,6 @@ function getUserContextFields(schema) {
|
|
|
6182
6183
|
}
|
|
6183
6184
|
var FIELD_FROM_METADATA, USER_CONTEXT_METADATA;
|
|
6184
6185
|
var init_categorical = __esm(() => {
|
|
6185
|
-
init_zod();
|
|
6186
6186
|
FIELD_FROM_METADATA = Symbol.for("ont:fieldFrom");
|
|
6187
6187
|
USER_CONTEXT_METADATA = Symbol.for("ont:userContext");
|
|
6188
6188
|
});
|
|
@@ -8656,10 +8656,21 @@ async function loadConfig(configPath) {
|
|
|
8656
8656
|
}
|
|
8657
8657
|
return { config, configDir, configPath: resolvedPath };
|
|
8658
8658
|
} catch (error) {
|
|
8659
|
-
|
|
8660
|
-
|
|
8659
|
+
const err = error;
|
|
8660
|
+
if (err.code === "ERR_MODULE_NOT_FOUND") {
|
|
8661
|
+
const message = err.message || "";
|
|
8662
|
+
if (message.includes("ont-run") || message.includes("zod")) {
|
|
8663
|
+
throw new Error(`Failed to load config: ${resolvedPath}
|
|
8664
|
+
|
|
8665
|
+
Missing dependencies. Run 'bun install' first.`);
|
|
8666
|
+
}
|
|
8667
|
+
throw new Error(`Failed to load config: ${resolvedPath}
|
|
8668
|
+
|
|
8669
|
+
Module not found: ${message}`);
|
|
8661
8670
|
}
|
|
8662
|
-
throw
|
|
8671
|
+
throw new Error(`Failed to load config: ${resolvedPath}
|
|
8672
|
+
|
|
8673
|
+
${err.message || error}`);
|
|
8663
8674
|
}
|
|
8664
8675
|
}
|
|
8665
8676
|
|
|
@@ -10829,6 +10840,7 @@ function transformToGraphData(config) {
|
|
|
10829
10840
|
const edges = [];
|
|
10830
10841
|
const accessGroupCounts = {};
|
|
10831
10842
|
const entityCounts = {};
|
|
10843
|
+
let userContextFunctionCount = 0;
|
|
10832
10844
|
for (const groupName of Object.keys(config.accessGroups)) {
|
|
10833
10845
|
accessGroupCounts[groupName] = 0;
|
|
10834
10846
|
}
|
|
@@ -10846,6 +10858,9 @@ function transformToGraphData(config) {
|
|
|
10846
10858
|
}
|
|
10847
10859
|
const userContextFields = getUserContextFields(fn.inputs);
|
|
10848
10860
|
const usesUserContext = userContextFields.length > 0;
|
|
10861
|
+
if (usesUserContext) {
|
|
10862
|
+
userContextFunctionCount++;
|
|
10863
|
+
}
|
|
10849
10864
|
nodes.push({
|
|
10850
10865
|
id: `function:${name}`,
|
|
10851
10866
|
type: "function",
|
|
@@ -10916,7 +10931,8 @@ function transformToGraphData(config) {
|
|
|
10916
10931
|
ontologyName: config.name,
|
|
10917
10932
|
totalFunctions: Object.keys(config.functions).length,
|
|
10918
10933
|
totalEntities: config.entities ? Object.keys(config.entities).length : 0,
|
|
10919
|
-
totalAccessGroups: Object.keys(config.accessGroups).length
|
|
10934
|
+
totalAccessGroups: Object.keys(config.accessGroups).length,
|
|
10935
|
+
totalUserContextFunctions: userContextFunctionCount
|
|
10920
10936
|
}
|
|
10921
10937
|
};
|
|
10922
10938
|
}
|
|
@@ -11162,6 +11178,9 @@ Ontology ${hasChanges ? "Review" : "Browser"} available at: ${url}`);
|
|
|
11162
11178
|
});
|
|
11163
11179
|
}
|
|
11164
11180
|
function generateBrowserUI(graphData) {
|
|
11181
|
+
const userContextFilterBtn = graphData.meta.totalUserContextFunctions > 0 ? `<button class="filter-btn" data-filter="userContext" title="Functions using userContext()">
|
|
11182
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="width:14px;height:14px;vertical-align:middle;margin-right:4px;"><path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"/><circle cx="12" cy="7" r="4"/></svg>User Context (${graphData.meta.totalUserContextFunctions})
|
|
11183
|
+
</button>` : "";
|
|
11165
11184
|
return `<!DOCTYPE html>
|
|
11166
11185
|
<html lang="en">
|
|
11167
11186
|
<head>
|
|
@@ -12480,6 +12499,7 @@ function generateBrowserUI(graphData) {
|
|
|
12480
12499
|
<button class="filter-btn" data-filter="accessGroup">
|
|
12481
12500
|
<span class="dot access"></span> Access
|
|
12482
12501
|
</button>
|
|
12502
|
+
${userContextFilterBtn}
|
|
12483
12503
|
</div>
|
|
12484
12504
|
|
|
12485
12505
|
<div class="layout-selector">
|
|
@@ -12624,6 +12644,7 @@ function generateBrowserUI(graphData) {
|
|
|
12624
12644
|
metadata: node.metadata,
|
|
12625
12645
|
changeStatus: node.changeStatus || 'unchanged',
|
|
12626
12646
|
changeDetails: node.changeDetails || null,
|
|
12647
|
+
usesUserContext: node.metadata?.usesUserContext || false,
|
|
12627
12648
|
},
|
|
12628
12649
|
});
|
|
12629
12650
|
}
|
|
@@ -12676,6 +12697,19 @@ function generateBrowserUI(graphData) {
|
|
|
12676
12697
|
'height': 55,
|
|
12677
12698
|
},
|
|
12678
12699
|
},
|
|
12700
|
+
// Function nodes with userContext - show indicator below label
|
|
12701
|
+
{
|
|
12702
|
+
selector: 'node[type="function"][?usesUserContext]',
|
|
12703
|
+
style: {
|
|
12704
|
+
'background-image': 'data:image/svg+xml,' + encodeURIComponent('<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32"><circle cx="16" cy="16" r="14" fill="#e8f4f8" stroke="#023d60" stroke-width="1.5"/><g transform="translate(4, 4)" fill="none" stroke="#023d60" stroke-width="2"><path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"/><circle cx="12" cy="7" r="4"/></g></svg>'),
|
|
12705
|
+
'background-width': '18px',
|
|
12706
|
+
'background-height': '18px',
|
|
12707
|
+
'background-position-x': '50%',
|
|
12708
|
+
'background-position-y': '75%',
|
|
12709
|
+
'text-valign': 'center',
|
|
12710
|
+
'text-margin-y': -8,
|
|
12711
|
+
},
|
|
12712
|
+
},
|
|
12679
12713
|
// Entity nodes - Teal
|
|
12680
12714
|
{
|
|
12681
12715
|
selector: 'node[type="entity"]',
|
|
@@ -13217,6 +13251,16 @@ function generateBrowserUI(graphData) {
|
|
|
13217
13251
|
if (filter === 'all') {
|
|
13218
13252
|
cy.nodes().removeClass('hidden');
|
|
13219
13253
|
cy.edges().removeClass('hidden');
|
|
13254
|
+
} else if (filter === 'userContext') {
|
|
13255
|
+
// Special filter: show only functions with userContext
|
|
13256
|
+
cy.nodes().forEach(node => {
|
|
13257
|
+
if (node.data('type') === 'function' && node.data('usesUserContext')) {
|
|
13258
|
+
node.removeClass('hidden');
|
|
13259
|
+
} else {
|
|
13260
|
+
node.addClass('hidden');
|
|
13261
|
+
}
|
|
13262
|
+
});
|
|
13263
|
+
cy.edges().addClass('hidden');
|
|
13220
13264
|
} else {
|
|
13221
13265
|
cy.nodes().forEach(node => {
|
|
13222
13266
|
if (node.data('type') === filter) {
|
|
@@ -13733,13 +13777,77 @@ var reviewCommand = defineCommand({
|
|
|
13733
13777
|
}
|
|
13734
13778
|
}
|
|
13735
13779
|
});
|
|
13780
|
+
// package.json
|
|
13781
|
+
var package_default = {
|
|
13782
|
+
name: "ont-run",
|
|
13783
|
+
version: "0.0.4",
|
|
13784
|
+
description: "Ontology-enforced API framework for AI coding agents",
|
|
13785
|
+
type: "module",
|
|
13786
|
+
bin: {
|
|
13787
|
+
"ont-run": "./dist/bin/ont.js"
|
|
13788
|
+
},
|
|
13789
|
+
exports: {
|
|
13790
|
+
".": {
|
|
13791
|
+
types: "./dist/src/index.d.ts",
|
|
13792
|
+
bun: "./src/index.ts",
|
|
13793
|
+
default: "./dist/index.js"
|
|
13794
|
+
}
|
|
13795
|
+
},
|
|
13796
|
+
files: [
|
|
13797
|
+
"dist",
|
|
13798
|
+
"src",
|
|
13799
|
+
"bin"
|
|
13800
|
+
],
|
|
13801
|
+
scripts: {
|
|
13802
|
+
dev: "bun run bin/ont.ts",
|
|
13803
|
+
build: "bun build ./src/index.ts --outdir ./dist --target node",
|
|
13804
|
+
"build:cli": "bun build ./bin/ont.ts --outfile ./dist/bin/ont.js --target node --external @modelcontextprotocol/sdk",
|
|
13805
|
+
"build:types": "tsc --declaration --emitDeclarationOnly",
|
|
13806
|
+
prepublishOnly: "bun run build && bun run build:cli && bun run build:types",
|
|
13807
|
+
docs: "cd docs && bun run dev",
|
|
13808
|
+
"docs:build": "cd docs && bun run build",
|
|
13809
|
+
"docs:preview": "cd docs && bun run preview"
|
|
13810
|
+
},
|
|
13811
|
+
dependencies: {
|
|
13812
|
+
"@hono/node-server": "^1.19.8",
|
|
13813
|
+
"@modelcontextprotocol/sdk": "^1.0.0",
|
|
13814
|
+
citty: "^0.1.6",
|
|
13815
|
+
consola: "^3.2.0",
|
|
13816
|
+
hono: "^4.6.0",
|
|
13817
|
+
open: "^10.0.0",
|
|
13818
|
+
zod: "^3.24.0",
|
|
13819
|
+
"zod-to-json-schema": "^3.23.0"
|
|
13820
|
+
},
|
|
13821
|
+
devDependencies: {
|
|
13822
|
+
"@types/bun": "latest",
|
|
13823
|
+
typescript: "^5.5.0"
|
|
13824
|
+
},
|
|
13825
|
+
peerDependencies: {
|
|
13826
|
+
bun: ">=1.0.0"
|
|
13827
|
+
},
|
|
13828
|
+
peerDependenciesMeta: {
|
|
13829
|
+
bun: {
|
|
13830
|
+
optional: true
|
|
13831
|
+
}
|
|
13832
|
+
},
|
|
13833
|
+
keywords: [
|
|
13834
|
+
"api",
|
|
13835
|
+
"framework",
|
|
13836
|
+
"access-control",
|
|
13837
|
+
"ai-agents",
|
|
13838
|
+
"hono",
|
|
13839
|
+
"zod",
|
|
13840
|
+
"typescript"
|
|
13841
|
+
],
|
|
13842
|
+
license: "MIT"
|
|
13843
|
+
};
|
|
13736
13844
|
|
|
13737
13845
|
// src/cli/index.ts
|
|
13738
13846
|
var main = defineCommand({
|
|
13739
13847
|
meta: {
|
|
13740
13848
|
name: "ont",
|
|
13741
13849
|
description: "Ontology - Ontology-first backends with human-approved AI access & edits",
|
|
13742
|
-
version:
|
|
13850
|
+
version: package_default.version
|
|
13743
13851
|
},
|
|
13744
13852
|
subCommands: {
|
|
13745
13853
|
init: initCommand,
|
package/dist/index.js
CHANGED
|
@@ -4017,8 +4017,9 @@ function hasUserContextMetadata(schema) {
|
|
|
4017
4017
|
}
|
|
4018
4018
|
function getUserContextFields(schema) {
|
|
4019
4019
|
const fields = [];
|
|
4020
|
-
|
|
4021
|
-
|
|
4020
|
+
const def = schema._def;
|
|
4021
|
+
if (def?.typeName === "ZodObject" && typeof def.shape === "function") {
|
|
4022
|
+
const shape = def.shape();
|
|
4022
4023
|
for (const [key, value] of Object.entries(shape)) {
|
|
4023
4024
|
if (hasUserContextMetadata(value)) {
|
|
4024
4025
|
fields.push(key);
|
|
@@ -14623,10 +14624,21 @@ async function loadConfig(configPath) {
|
|
|
14623
14624
|
}
|
|
14624
14625
|
return { config, configDir, configPath: resolvedPath };
|
|
14625
14626
|
} catch (error) {
|
|
14626
|
-
|
|
14627
|
-
|
|
14627
|
+
const err = error;
|
|
14628
|
+
if (err.code === "ERR_MODULE_NOT_FOUND") {
|
|
14629
|
+
const message = err.message || "";
|
|
14630
|
+
if (message.includes("ont-run") || message.includes("zod")) {
|
|
14631
|
+
throw new Error(`Failed to load config: ${resolvedPath}
|
|
14632
|
+
|
|
14633
|
+
Missing dependencies. Run 'bun install' first.`);
|
|
14634
|
+
}
|
|
14635
|
+
throw new Error(`Failed to load config: ${resolvedPath}
|
|
14636
|
+
|
|
14637
|
+
Module not found: ${message}`);
|
|
14628
14638
|
}
|
|
14629
|
-
throw
|
|
14639
|
+
throw new Error(`Failed to load config: ${resolvedPath}
|
|
14640
|
+
|
|
14641
|
+
${err.message || error}`);
|
|
14630
14642
|
}
|
|
14631
14643
|
}
|
|
14632
14644
|
|
|
@@ -131,5 +131,8 @@ export declare function userContext<T extends z.ZodType>(schema: T): UserContext
|
|
|
131
131
|
export declare function hasUserContextMetadata(schema: unknown): schema is UserContextSchema<z.ZodType>;
|
|
132
132
|
/**
|
|
133
133
|
* Get all userContext field names from a Zod object schema
|
|
134
|
+
*
|
|
135
|
+
* Note: Uses _def.typeName check instead of instanceof to work across
|
|
136
|
+
* module boundaries in bundled CLI.
|
|
134
137
|
*/
|
|
135
138
|
export declare function getUserContextFields(schema: z.ZodType): string[];
|
package/package.json
CHANGED
package/src/browser/server.ts
CHANGED
|
@@ -122,6 +122,12 @@ export async function startBrowserServer(options: BrowserServerOptions): Promise
|
|
|
122
122
|
}
|
|
123
123
|
|
|
124
124
|
function generateBrowserUI(graphData: EnhancedGraphData): string {
|
|
125
|
+
const userContextFilterBtn = graphData.meta.totalUserContextFunctions > 0
|
|
126
|
+
? `<button class="filter-btn" data-filter="userContext" title="Functions using userContext()">
|
|
127
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="width:14px;height:14px;vertical-align:middle;margin-right:4px;"><path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"/><circle cx="12" cy="7" r="4"/></svg>User Context (${graphData.meta.totalUserContextFunctions})
|
|
128
|
+
</button>`
|
|
129
|
+
: '';
|
|
130
|
+
|
|
125
131
|
return `<!DOCTYPE html>
|
|
126
132
|
<html lang="en">
|
|
127
133
|
<head>
|
|
@@ -1440,6 +1446,7 @@ function generateBrowserUI(graphData: EnhancedGraphData): string {
|
|
|
1440
1446
|
<button class="filter-btn" data-filter="accessGroup">
|
|
1441
1447
|
<span class="dot access"></span> Access
|
|
1442
1448
|
</button>
|
|
1449
|
+
${userContextFilterBtn}
|
|
1443
1450
|
</div>
|
|
1444
1451
|
|
|
1445
1452
|
<div class="layout-selector">
|
|
@@ -1584,6 +1591,7 @@ function generateBrowserUI(graphData: EnhancedGraphData): string {
|
|
|
1584
1591
|
metadata: node.metadata,
|
|
1585
1592
|
changeStatus: node.changeStatus || 'unchanged',
|
|
1586
1593
|
changeDetails: node.changeDetails || null,
|
|
1594
|
+
usesUserContext: node.metadata?.usesUserContext || false,
|
|
1587
1595
|
},
|
|
1588
1596
|
});
|
|
1589
1597
|
}
|
|
@@ -1636,6 +1644,19 @@ function generateBrowserUI(graphData: EnhancedGraphData): string {
|
|
|
1636
1644
|
'height': 55,
|
|
1637
1645
|
},
|
|
1638
1646
|
},
|
|
1647
|
+
// Function nodes with userContext - show indicator below label
|
|
1648
|
+
{
|
|
1649
|
+
selector: 'node[type="function"][?usesUserContext]',
|
|
1650
|
+
style: {
|
|
1651
|
+
'background-image': 'data:image/svg+xml,' + encodeURIComponent('<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32"><circle cx="16" cy="16" r="14" fill="#e8f4f8" stroke="#023d60" stroke-width="1.5"/><g transform="translate(4, 4)" fill="none" stroke="#023d60" stroke-width="2"><path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"/><circle cx="12" cy="7" r="4"/></g></svg>'),
|
|
1652
|
+
'background-width': '18px',
|
|
1653
|
+
'background-height': '18px',
|
|
1654
|
+
'background-position-x': '50%',
|
|
1655
|
+
'background-position-y': '75%',
|
|
1656
|
+
'text-valign': 'center',
|
|
1657
|
+
'text-margin-y': -8,
|
|
1658
|
+
},
|
|
1659
|
+
},
|
|
1639
1660
|
// Entity nodes - Teal
|
|
1640
1661
|
{
|
|
1641
1662
|
selector: 'node[type="entity"]',
|
|
@@ -2177,6 +2198,16 @@ function generateBrowserUI(graphData: EnhancedGraphData): string {
|
|
|
2177
2198
|
if (filter === 'all') {
|
|
2178
2199
|
cy.nodes().removeClass('hidden');
|
|
2179
2200
|
cy.edges().removeClass('hidden');
|
|
2201
|
+
} else if (filter === 'userContext') {
|
|
2202
|
+
// Special filter: show only functions with userContext
|
|
2203
|
+
cy.nodes().forEach(node => {
|
|
2204
|
+
if (node.data('type') === 'function' && node.data('usesUserContext')) {
|
|
2205
|
+
node.removeClass('hidden');
|
|
2206
|
+
} else {
|
|
2207
|
+
node.addClass('hidden');
|
|
2208
|
+
}
|
|
2209
|
+
});
|
|
2210
|
+
cy.edges().addClass('hidden');
|
|
2180
2211
|
} else {
|
|
2181
2212
|
cy.nodes().forEach(node => {
|
|
2182
2213
|
if (node.data('type') === filter) {
|
package/src/browser/transform.ts
CHANGED
|
@@ -38,6 +38,7 @@ export interface GraphData {
|
|
|
38
38
|
totalFunctions: number;
|
|
39
39
|
totalEntities: number;
|
|
40
40
|
totalAccessGroups: number;
|
|
41
|
+
totalUserContextFunctions: number;
|
|
41
42
|
};
|
|
42
43
|
}
|
|
43
44
|
|
|
@@ -132,6 +133,7 @@ export function transformToGraphData(config: OntologyConfig): GraphData {
|
|
|
132
133
|
// Track function counts for access groups and entities
|
|
133
134
|
const accessGroupCounts: Record<string, number> = {};
|
|
134
135
|
const entityCounts: Record<string, number> = {};
|
|
136
|
+
let userContextFunctionCount = 0;
|
|
135
137
|
|
|
136
138
|
// Initialize counts
|
|
137
139
|
for (const groupName of Object.keys(config.accessGroups)) {
|
|
@@ -158,6 +160,9 @@ export function transformToGraphData(config: OntologyConfig): GraphData {
|
|
|
158
160
|
// Check if function uses userContext
|
|
159
161
|
const userContextFields = getUserContextFields(fn.inputs);
|
|
160
162
|
const usesUserContext = userContextFields.length > 0;
|
|
163
|
+
if (usesUserContext) {
|
|
164
|
+
userContextFunctionCount++;
|
|
165
|
+
}
|
|
161
166
|
|
|
162
167
|
// Create function node
|
|
163
168
|
nodes.push({
|
|
@@ -242,6 +247,7 @@ export function transformToGraphData(config: OntologyConfig): GraphData {
|
|
|
242
247
|
totalFunctions: Object.keys(config.functions).length,
|
|
243
248
|
totalEntities: config.entities ? Object.keys(config.entities).length : 0,
|
|
244
249
|
totalAccessGroups: Object.keys(config.accessGroups).length,
|
|
250
|
+
totalUserContextFunctions: userContextFunctionCount,
|
|
245
251
|
},
|
|
246
252
|
};
|
|
247
253
|
}
|
package/src/cli/index.ts
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import { defineCommand, runMain } from "citty";
|
|
2
2
|
import { initCommand } from "./commands/init.js";
|
|
3
3
|
import { reviewCommand } from "./commands/review.js";
|
|
4
|
+
import pkg from "../../package.json";
|
|
4
5
|
|
|
5
6
|
const main = defineCommand({
|
|
6
7
|
meta: {
|
|
7
8
|
name: "ont",
|
|
8
9
|
description: "Ontology - Ontology-first backends with human-approved AI access & edits",
|
|
9
|
-
version:
|
|
10
|
+
version: pkg.version,
|
|
10
11
|
},
|
|
11
12
|
subCommands: {
|
|
12
13
|
init: initCommand,
|
|
@@ -70,9 +70,24 @@ export async function loadConfig(configPath?: string): Promise<{
|
|
|
70
70
|
|
|
71
71
|
return { config, configDir, configPath: resolvedPath };
|
|
72
72
|
} catch (error) {
|
|
73
|
-
|
|
74
|
-
|
|
73
|
+
const err = error as NodeJS.ErrnoException & { message?: string };
|
|
74
|
+
if (err.code === "ERR_MODULE_NOT_FOUND") {
|
|
75
|
+
// Check if it's a missing dependency vs missing config
|
|
76
|
+
const message = err.message || "";
|
|
77
|
+
if (message.includes("ont-run") || message.includes("zod")) {
|
|
78
|
+
throw new Error(
|
|
79
|
+
`Failed to load config: ${resolvedPath}\n\n` +
|
|
80
|
+
`Missing dependencies. Run 'bun install' first.`
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
throw new Error(
|
|
84
|
+
`Failed to load config: ${resolvedPath}\n\n` +
|
|
85
|
+
`Module not found: ${message}`
|
|
86
|
+
);
|
|
75
87
|
}
|
|
76
|
-
throw
|
|
88
|
+
throw new Error(
|
|
89
|
+
`Failed to load config: ${resolvedPath}\n\n` +
|
|
90
|
+
`${err.message || error}`
|
|
91
|
+
);
|
|
77
92
|
}
|
|
78
93
|
}
|
|
@@ -174,12 +174,17 @@ export function hasUserContextMetadata(
|
|
|
174
174
|
|
|
175
175
|
/**
|
|
176
176
|
* Get all userContext field names from a Zod object schema
|
|
177
|
+
*
|
|
178
|
+
* Note: Uses _def.typeName check instead of instanceof to work across
|
|
179
|
+
* module boundaries in bundled CLI.
|
|
177
180
|
*/
|
|
178
181
|
export function getUserContextFields(schema: z.ZodType): string[] {
|
|
179
182
|
const fields: string[] = [];
|
|
180
183
|
|
|
181
|
-
|
|
182
|
-
|
|
184
|
+
// Use _def.typeName check for bundler compatibility (instanceof fails across module boundaries)
|
|
185
|
+
const def = (schema as unknown as { _def?: { typeName?: string; shape?: () => Record<string, unknown> } })._def;
|
|
186
|
+
if (def?.typeName === "ZodObject" && typeof def.shape === "function") {
|
|
187
|
+
const shape = def.shape();
|
|
183
188
|
for (const [key, value] of Object.entries(shape)) {
|
|
184
189
|
if (hasUserContextMetadata(value)) {
|
|
185
190
|
fields.push(key);
|