agent-manifest 3.2.0 → 4.0.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.
- package/dist/cli.js +84 -13
- package/dist/discovery/service.js +48 -12
- package/dist/generator/json.js +3 -1
- package/dist/index.js +22 -1
- package/dist/parser/auth-detector.js +88 -6
- package/dist/parser/contract-parser.js +6 -6
- package/dist/parser/express-parser.js +3 -3
- package/dist/parser/intent-classifier.js +37 -12
- package/dist/parser/openapi-parser.js +156 -0
- package/dist/parser/prisma-parser.js +94 -0
- package/dist/parser/remix-parser.js +158 -0
- package/dist/parser/socketio-parser.js +169 -0
- package/dist/parser/sse-parser.js +141 -0
- package/dist/parser/trpc-parser.js +192 -0
- package/dist/parser/ts-parser.js +20 -20
- package/dist/parser/websocket-parser.js +168 -0
- package/dist/parser/zod-extractor.js +9 -2
- package/package.json +1 -1
- package/schema/agent.schema.json +36 -6
package/dist/cli.js
CHANGED
|
@@ -43,7 +43,7 @@ const program = new commander_1.Command();
|
|
|
43
43
|
program
|
|
44
44
|
.name('agentjson')
|
|
45
45
|
.description('Universal agent manifest compiler — generates agent.json for any web app')
|
|
46
|
-
.version('
|
|
46
|
+
.version('4.0.0')
|
|
47
47
|
.option('-p, --path <path>', 'path to the project root', '.')
|
|
48
48
|
.option('-o, --output <output>', 'output path for agent.json', './public/agent.json')
|
|
49
49
|
.option('--author <author>', 'author name or organization')
|
|
@@ -51,6 +51,7 @@ program
|
|
|
51
51
|
.option('--auth-type <type>', 'override detected auth type: none | bearer | api-key | oauth2 | basic | farcaster-frame | cookie')
|
|
52
52
|
.option('--auth-header <header>', 'auth header name (default: Authorization)')
|
|
53
53
|
.option('--auth-docs <url>', 'URL where agents can obtain credentials')
|
|
54
|
+
.option('--base-url <url>', 'canonical base URL for the app (e.g. https://myapp.com)')
|
|
54
55
|
.action(async (options) => {
|
|
55
56
|
const projectPath = path.resolve(options.path);
|
|
56
57
|
const outputPath = path.resolve(options.output);
|
|
@@ -59,18 +60,71 @@ program
|
|
|
59
60
|
const files = await discovery.findRelevantFiles();
|
|
60
61
|
console.log(`🔍 Found ${files.length} relevant files.`);
|
|
61
62
|
const tsParser = new index_1.TSParser(projectPath);
|
|
62
|
-
const
|
|
63
|
+
const sharedProject = tsParser.getProject();
|
|
64
|
+
const expressParser = new index_1.ExpressParser(sharedProject);
|
|
65
|
+
const socketParser = new index_1.SocketIOParser(sharedProject);
|
|
66
|
+
const trpcParser = new index_1.TRPCParser(sharedProject);
|
|
67
|
+
const sseParser = new index_1.SSEParser(sharedProject);
|
|
68
|
+
const remixParser = new index_1.RemixParser(sharedProject);
|
|
69
|
+
const wsParser = new index_1.WebSocketParser(sharedProject);
|
|
70
|
+
const openApiParser = new index_1.OpenAPIParser();
|
|
71
|
+
const prismaParser = new index_1.PrismaParser();
|
|
63
72
|
const actions = [];
|
|
73
|
+
const dataModel = {};
|
|
74
|
+
// Determine which files are OpenAPI / Prisma by extension/name
|
|
75
|
+
const openApiExts = new Set(['.json', '.yaml', '.yml']);
|
|
76
|
+
const prismaExt = '.prisma';
|
|
64
77
|
for (const file of files) {
|
|
65
78
|
console.log(`📄 Parsing: ${path.relative(projectPath, file)}`);
|
|
66
|
-
const
|
|
67
|
-
|
|
68
|
-
|
|
79
|
+
const ext = path.extname(file).toLowerCase();
|
|
80
|
+
const base = path.basename(file).toLowerCase();
|
|
81
|
+
// ── OpenAPI spec ──────────────────────────────────────────────────────
|
|
82
|
+
if (openApiExts.has(ext) && index_1.OPENAPI_PATTERNS.some(p => {
|
|
83
|
+
const pat = p.replace('**/', '').replace('*', '');
|
|
84
|
+
return base.includes(pat.split('.')[0]);
|
|
85
|
+
})) {
|
|
86
|
+
try {
|
|
87
|
+
actions.push(...openApiParser.parseFile(file, projectPath));
|
|
88
|
+
}
|
|
89
|
+
catch { /* ignore */ }
|
|
90
|
+
continue;
|
|
91
|
+
}
|
|
92
|
+
// ── Prisma schema ─────────────────────────────────────────────────────
|
|
93
|
+
if (ext === prismaExt || base === 'schema.prisma') {
|
|
94
|
+
try {
|
|
95
|
+
const prismaResult = prismaParser.parseFile(file);
|
|
96
|
+
Object.assign(dataModel, prismaResult.dataModel);
|
|
97
|
+
}
|
|
98
|
+
catch { /* ignore */ }
|
|
99
|
+
continue;
|
|
100
|
+
}
|
|
101
|
+
// ── TypeScript / JavaScript source files ──────────────────────────────
|
|
102
|
+
if (ext === '.ts' || ext === '.tsx' || ext === '.js' || ext === '.jsx') {
|
|
103
|
+
// Standard TS server-action / contract / annotation parsing
|
|
104
|
+
try {
|
|
105
|
+
const fileActions = await tsParser.parseFile(file);
|
|
106
|
+
actions.push(...fileActions);
|
|
107
|
+
}
|
|
108
|
+
catch { /* ignore */ }
|
|
69
109
|
try {
|
|
70
110
|
const content = fs.readFileSync(file, 'utf8');
|
|
71
111
|
if ((0, index_1.looksLikeRouteFile)(content)) {
|
|
72
|
-
|
|
73
|
-
|
|
112
|
+
actions.push(...await expressParser.parseFile(file, projectPath));
|
|
113
|
+
}
|
|
114
|
+
if ((0, index_1.looksLikeSocketIOFile)(content)) {
|
|
115
|
+
actions.push(...await socketParser.parseFile(file, projectPath));
|
|
116
|
+
}
|
|
117
|
+
if ((0, index_1.looksLikeTRPCFile)(content)) {
|
|
118
|
+
actions.push(...await trpcParser.parseFile(file, projectPath));
|
|
119
|
+
}
|
|
120
|
+
if ((0, index_1.looksLikeSSEFile)(content)) {
|
|
121
|
+
actions.push(...await sseParser.parseFile(file, projectPath));
|
|
122
|
+
}
|
|
123
|
+
if ((0, index_1.looksLikeRemixRouteFile)(content)) {
|
|
124
|
+
actions.push(...await remixParser.parseFile(file, projectPath));
|
|
125
|
+
}
|
|
126
|
+
if ((0, index_1.looksLikeWebSocketFile)(content)) {
|
|
127
|
+
actions.push(...await wsParser.parseFile(file, projectPath));
|
|
74
128
|
}
|
|
75
129
|
}
|
|
76
130
|
catch { /* ignore */ }
|
|
@@ -80,7 +134,7 @@ program
|
|
|
80
134
|
const uniqueActions = new Map();
|
|
81
135
|
for (const action of actions) {
|
|
82
136
|
const existing = uniqueActions.get(action.name);
|
|
83
|
-
if (!existing || Object.keys(action.
|
|
137
|
+
if (!existing || Object.keys(action.parameters?.properties ?? {}).length > Object.keys(existing.parameters?.properties ?? {}).length) {
|
|
84
138
|
uniqueActions.set(action.name, action);
|
|
85
139
|
}
|
|
86
140
|
}
|
|
@@ -97,11 +151,18 @@ program
|
|
|
97
151
|
...(options.authDocs && { docsUrl: options.authDocs }),
|
|
98
152
|
};
|
|
99
153
|
const generator = new index_1.ManifestGenerator();
|
|
100
|
-
const manifest = generator.generate(Array.from(uniqueActions.values()), appMetadata, tsParser.getCapabilities(), auth
|
|
154
|
+
const manifest = generator.generate(Array.from(uniqueActions.values()), appMetadata, tsParser.getCapabilities(), auth, '1.0.0', {
|
|
155
|
+
baseUrl: options.baseUrl ?? appMetadata.url,
|
|
156
|
+
dataModel,
|
|
157
|
+
});
|
|
101
158
|
fs.mkdirSync(path.dirname(outputPath), { recursive: true });
|
|
102
159
|
fs.writeFileSync(outputPath, JSON.stringify(manifest, null, 2));
|
|
160
|
+
const uiCount = Array.from(uniqueActions.values()).filter(a => a.type === 'ui').length;
|
|
161
|
+
const modelCount = Object.keys(dataModel).length;
|
|
103
162
|
console.log(`✅ agent.json generated at: ${outputPath}`);
|
|
104
|
-
console.log(` ${uniqueActions.size} actions · ${manifest.capabilities.length} capabilities · auth: ${auth.type}`);
|
|
163
|
+
console.log(` ${uniqueActions.size} actions${uiCount ? ` · ${uiCount} ui-interactions` : ''} · ${manifest.capabilities.length} capabilities · auth: ${auth.type}`);
|
|
164
|
+
if (modelCount > 0)
|
|
165
|
+
console.log(` dataModel: ${modelCount} models`);
|
|
105
166
|
});
|
|
106
167
|
// ─── validate ────────────────────────────────────────────────────────────────
|
|
107
168
|
program
|
|
@@ -135,8 +196,12 @@ program
|
|
|
135
196
|
});
|
|
136
197
|
// ─── Structural validator ────────────────────────────────────────────────────
|
|
137
198
|
const SAFETY_LEVELS = new Set(['read', 'write', 'financial', 'destructive', 'confidential']);
|
|
138
|
-
const ACTION_TYPES = new Set(['api', 'contract', 'function']);
|
|
139
|
-
const AUTH_TYPES = new Set([
|
|
199
|
+
const ACTION_TYPES = new Set(['api', 'contract', 'function', 'socket', 'ui']);
|
|
200
|
+
const AUTH_TYPES = new Set([
|
|
201
|
+
'none', 'bearer', 'api-key', 'oauth2', 'basic', 'cookie',
|
|
202
|
+
'siwe', 'farcaster-siwf', 'farcaster-frame',
|
|
203
|
+
'clerk', 'privy', 'dynamic', 'magic', 'passkey', 'saml', 'supabase',
|
|
204
|
+
]);
|
|
140
205
|
const INTENT_RE = /^[a-z][a-z0-9]*\.[a-z][a-z0-9]*$/;
|
|
141
206
|
function validateManifest(m) {
|
|
142
207
|
const errors = [];
|
|
@@ -160,13 +225,19 @@ function validateManifest(m) {
|
|
|
160
225
|
else if (!INTENT_RE.test(action.intent))
|
|
161
226
|
errors.push(`${prefix}: \`intent\` must match domain.verb format`);
|
|
162
227
|
if (!ACTION_TYPES.has(action.type))
|
|
163
|
-
errors.push(`${prefix}: \`type\` must be one of api|contract|function`);
|
|
228
|
+
errors.push(`${prefix}: \`type\` must be one of api|contract|function|socket|ui`);
|
|
164
229
|
if (!SAFETY_LEVELS.has(action.safety))
|
|
165
230
|
errors.push(`${prefix}: \`safety\` must be one of read|write|financial|destructive|confidential`);
|
|
166
231
|
if (typeof action.agentSafe !== 'boolean')
|
|
167
232
|
errors.push(`${prefix}: \`agentSafe\` must be a boolean`);
|
|
168
233
|
if (!action.requiredAuth)
|
|
169
234
|
errors.push(`${prefix}: \`requiredAuth\` is missing`);
|
|
235
|
+
else if (!['public', 'required', 'farcaster-signed'].includes(action.requiredAuth.required))
|
|
236
|
+
errors.push(`${prefix}: \`requiredAuth.required\` must be "public", "required", or "farcaster-signed"`);
|
|
237
|
+
if (!action.parameters || typeof action.parameters.properties !== 'object')
|
|
238
|
+
errors.push(`${prefix}: \`parameters.properties\` must be an object`);
|
|
239
|
+
if (!action.returns || typeof action.returns.type !== 'string')
|
|
240
|
+
errors.push(`${prefix}: \`returns.type\` must be a string`);
|
|
170
241
|
});
|
|
171
242
|
}
|
|
172
243
|
return errors;
|
|
@@ -39,6 +39,13 @@ const path = __importStar(require("path"));
|
|
|
39
39
|
const crypto = __importStar(require("crypto"));
|
|
40
40
|
const tinyglobby_1 = require("tinyglobby");
|
|
41
41
|
const express_parser_1 = require("../parser/express-parser");
|
|
42
|
+
const socketio_parser_1 = require("../parser/socketio-parser");
|
|
43
|
+
const trpc_parser_1 = require("../parser/trpc-parser");
|
|
44
|
+
const sse_parser_1 = require("../parser/sse-parser");
|
|
45
|
+
const remix_parser_1 = require("../parser/remix-parser");
|
|
46
|
+
const websocket_parser_1 = require("../parser/websocket-parser");
|
|
47
|
+
const openapi_parser_1 = require("../parser/openapi-parser");
|
|
48
|
+
const prisma_parser_1 = require("../parser/prisma-parser");
|
|
42
49
|
/** SHA-1 of a file's content — used for change detection caching. */
|
|
43
50
|
function fileHash(filePath) {
|
|
44
51
|
try {
|
|
@@ -60,12 +67,20 @@ const ALWAYS_EXCLUDE = [
|
|
|
60
67
|
'!**/out/**',
|
|
61
68
|
'!**/build/**',
|
|
62
69
|
'!**/.vercel/**',
|
|
63
|
-
// Never scan env files — they may contain secrets
|
|
64
70
|
'!**/.env',
|
|
65
71
|
'!**/.env.*',
|
|
66
72
|
'!**/secrets/**',
|
|
67
73
|
'!**/credentials/**',
|
|
68
74
|
];
|
|
75
|
+
/**
|
|
76
|
+
* Checks if a path is strictly within the project directory.
|
|
77
|
+
* Prevents path traversal via symbolic links or malformed paths.
|
|
78
|
+
*/
|
|
79
|
+
function isPathWithinProject(filePath, projectPath) {
|
|
80
|
+
const resolved = path.resolve(filePath);
|
|
81
|
+
const root = path.resolve(projectPath);
|
|
82
|
+
return resolved.startsWith(root);
|
|
83
|
+
}
|
|
69
84
|
class DiscoveryService {
|
|
70
85
|
projectPath;
|
|
71
86
|
cache = {};
|
|
@@ -94,14 +109,14 @@ class DiscoveryService {
|
|
|
94
109
|
}
|
|
95
110
|
async findRelevantFiles() {
|
|
96
111
|
const relevantFiles = [];
|
|
97
|
-
// 0. Farcaster manifest
|
|
112
|
+
// 0. Farcaster manifest
|
|
98
113
|
const manifests = await (0, tinyglobby_1.glob)([
|
|
99
114
|
'.well-known/farcaster.json',
|
|
100
115
|
'public/.well-known/farcaster.json',
|
|
101
116
|
'**/public/.well-known/farcaster.json',
|
|
102
117
|
], { cwd: this.projectPath, absolute: true });
|
|
103
118
|
relevantFiles.push(...manifests);
|
|
104
|
-
// 0.5. ABI JSON files
|
|
119
|
+
// 0.5. ABI JSON files
|
|
105
120
|
const abis = await (0, tinyglobby_1.glob)([
|
|
106
121
|
'**/*ABI.json',
|
|
107
122
|
'**/abi/*.json',
|
|
@@ -111,34 +126,50 @@ class DiscoveryService {
|
|
|
111
126
|
...ALWAYS_EXCLUDE,
|
|
112
127
|
], { cwd: this.projectPath, absolute: true });
|
|
113
128
|
relevantFiles.push(...abis);
|
|
114
|
-
//
|
|
129
|
+
// 0.6. OpenAPI / Swagger specs
|
|
130
|
+
const openApiFiles = await (0, tinyglobby_1.glob)([
|
|
131
|
+
...openapi_parser_1.OPENAPI_PATTERNS,
|
|
132
|
+
...ALWAYS_EXCLUDE,
|
|
133
|
+
], { cwd: this.projectPath, absolute: true });
|
|
134
|
+
relevantFiles.push(...openApiFiles);
|
|
135
|
+
// 0.7. Prisma schema files
|
|
136
|
+
const prismaFiles = await (0, tinyglobby_1.glob)([
|
|
137
|
+
...prisma_parser_1.PRISMA_PATTERNS,
|
|
138
|
+
...ALWAYS_EXCLUDE,
|
|
139
|
+
], { cwd: this.projectPath, absolute: true });
|
|
140
|
+
relevantFiles.push(...prismaFiles);
|
|
141
|
+
// 1. Next.js API routes
|
|
115
142
|
const apiRoutes = await (0, tinyglobby_1.glob)([
|
|
116
|
-
// Next.js App Router
|
|
117
143
|
'**/app/api/**/*.{ts,js,tsx,jsx}',
|
|
118
|
-
// Next.js Pages Router
|
|
119
144
|
'**/pages/api/**/*.{ts,js,tsx,jsx}',
|
|
120
|
-
// Generic api/ folder
|
|
121
145
|
'**/api/**/*.{ts,js,tsx,jsx}',
|
|
122
146
|
...ALWAYS_EXCLUDE,
|
|
123
147
|
], { cwd: this.projectPath, absolute: true });
|
|
124
148
|
relevantFiles.push(...apiRoutes);
|
|
125
|
-
// 2.
|
|
149
|
+
// 2. Remix route files
|
|
150
|
+
const remixRoutes = await (0, tinyglobby_1.glob)([
|
|
151
|
+
'**/app/routes/**/*.{ts,js,tsx,jsx}',
|
|
152
|
+
'**/routes/**/*.{ts,js,tsx,jsx}',
|
|
153
|
+
...ALWAYS_EXCLUDE,
|
|
154
|
+
], { cwd: this.projectPath, absolute: true });
|
|
155
|
+
relevantFiles.push(...remixRoutes);
|
|
156
|
+
// 3. Scan all TS/TSX/JS files for signal keywords
|
|
126
157
|
const allTsFiles = await (0, tinyglobby_1.glob)([
|
|
127
|
-
'**/*.{ts,tsx}',
|
|
158
|
+
'**/*.{ts,tsx,js,jsx}',
|
|
128
159
|
...ALWAYS_EXCLUDE,
|
|
129
160
|
], { cwd: this.projectPath, absolute: true });
|
|
130
161
|
for (const file of allTsFiles) {
|
|
162
|
+
if (!isPathWithinProject(file, this.projectPath))
|
|
163
|
+
continue;
|
|
131
164
|
if (relevantFiles.includes(file))
|
|
132
165
|
continue;
|
|
133
166
|
const hash = fileHash(file);
|
|
134
167
|
const cached = this.cache[file];
|
|
135
|
-
// Cache hit: file unchanged, reuse previous relevance decision
|
|
136
168
|
if (cached && cached.hash === hash) {
|
|
137
169
|
if (cached.relevant)
|
|
138
170
|
relevantFiles.push(file);
|
|
139
171
|
continue;
|
|
140
172
|
}
|
|
141
|
-
// Cache miss: read and classify
|
|
142
173
|
const content = fs.readFileSync(file, 'utf8');
|
|
143
174
|
const relevant = content.includes('@agent-action') ||
|
|
144
175
|
content.includes('useWriteContract') ||
|
|
@@ -146,7 +177,12 @@ class DiscoveryService {
|
|
|
146
177
|
content.includes('writeContract') ||
|
|
147
178
|
content.includes("'use server'") ||
|
|
148
179
|
content.includes('"use server"') ||
|
|
149
|
-
(0, express_parser_1.looksLikeRouteFile)(content)
|
|
180
|
+
(0, express_parser_1.looksLikeRouteFile)(content) ||
|
|
181
|
+
(0, socketio_parser_1.looksLikeSocketIOFile)(content) ||
|
|
182
|
+
(0, trpc_parser_1.looksLikeTRPCFile)(content) ||
|
|
183
|
+
(0, sse_parser_1.looksLikeSSEFile)(content) ||
|
|
184
|
+
(0, remix_parser_1.looksLikeRemixRouteFile)(content) ||
|
|
185
|
+
(0, websocket_parser_1.looksLikeWebSocketFile)(content);
|
|
150
186
|
this.cache[file] = { hash, relevant };
|
|
151
187
|
this.cacheModified = true;
|
|
152
188
|
if (relevant)
|
package/dist/generator/json.js
CHANGED
|
@@ -2,13 +2,14 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.ManifestGenerator = void 0;
|
|
4
4
|
class ManifestGenerator {
|
|
5
|
-
generate(actions, metadata = {}, capabilities = [], auth = { type: 'none' }, version = '1.0.0') {
|
|
5
|
+
generate(actions, metadata = {}, capabilities = [], auth = { type: 'none' }, version = '1.0.0', options = {}) {
|
|
6
6
|
return {
|
|
7
7
|
name: metadata.name ?? 'Web App',
|
|
8
8
|
description: metadata.description ?? 'Auto-generated agent manifest',
|
|
9
9
|
version,
|
|
10
10
|
...(metadata.author && { author: metadata.author }),
|
|
11
11
|
...(metadata.url && { url: metadata.url }),
|
|
12
|
+
...(options.baseUrl && { baseUrl: options.baseUrl }),
|
|
12
13
|
auth,
|
|
13
14
|
metadata: {
|
|
14
15
|
...(metadata.iconUrl && { iconUrl: metadata.iconUrl }),
|
|
@@ -19,6 +20,7 @@ class ManifestGenerator {
|
|
|
19
20
|
},
|
|
20
21
|
capabilities,
|
|
21
22
|
actions,
|
|
23
|
+
...(options.dataModel && Object.keys(options.dataModel).length > 0 && { dataModel: options.dataModel }),
|
|
22
24
|
};
|
|
23
25
|
}
|
|
24
26
|
}
|
package/dist/index.js
CHANGED
|
@@ -14,7 +14,7 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
|
14
14
|
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
15
|
};
|
|
16
16
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
-
exports.CapabilityDetector = exports.AuthDetector = exports.ManifestGenerator = exports.ZodExtractor = exports.ContractParser = exports.looksLikeRouteFile = exports.ExpressParser = exports.TSParser = exports.DiscoveryService = void 0;
|
|
17
|
+
exports.CapabilityDetector = exports.AuthDetector = exports.ManifestGenerator = exports.ZodExtractor = exports.ContractParser = exports.PRISMA_PATTERNS = exports.PrismaParser = exports.OPENAPI_PATTERNS = exports.OpenAPIParser = exports.looksLikeWebSocketFile = exports.WebSocketParser = exports.looksLikeRemixRouteFile = exports.RemixParser = exports.looksLikeSSEFile = exports.SSEParser = exports.looksLikeTRPCFile = exports.TRPCParser = exports.looksLikeSocketIOFile = exports.SocketIOParser = exports.looksLikeRouteFile = exports.ExpressParser = exports.TSParser = exports.DiscoveryService = void 0;
|
|
18
18
|
__exportStar(require("./types"), exports);
|
|
19
19
|
var service_1 = require("./discovery/service");
|
|
20
20
|
Object.defineProperty(exports, "DiscoveryService", { enumerable: true, get: function () { return service_1.DiscoveryService; } });
|
|
@@ -23,6 +23,27 @@ Object.defineProperty(exports, "TSParser", { enumerable: true, get: function ()
|
|
|
23
23
|
var express_parser_1 = require("./parser/express-parser");
|
|
24
24
|
Object.defineProperty(exports, "ExpressParser", { enumerable: true, get: function () { return express_parser_1.ExpressParser; } });
|
|
25
25
|
Object.defineProperty(exports, "looksLikeRouteFile", { enumerable: true, get: function () { return express_parser_1.looksLikeRouteFile; } });
|
|
26
|
+
var socketio_parser_1 = require("./parser/socketio-parser");
|
|
27
|
+
Object.defineProperty(exports, "SocketIOParser", { enumerable: true, get: function () { return socketio_parser_1.SocketIOParser; } });
|
|
28
|
+
Object.defineProperty(exports, "looksLikeSocketIOFile", { enumerable: true, get: function () { return socketio_parser_1.looksLikeSocketIOFile; } });
|
|
29
|
+
var trpc_parser_1 = require("./parser/trpc-parser");
|
|
30
|
+
Object.defineProperty(exports, "TRPCParser", { enumerable: true, get: function () { return trpc_parser_1.TRPCParser; } });
|
|
31
|
+
Object.defineProperty(exports, "looksLikeTRPCFile", { enumerable: true, get: function () { return trpc_parser_1.looksLikeTRPCFile; } });
|
|
32
|
+
var sse_parser_1 = require("./parser/sse-parser");
|
|
33
|
+
Object.defineProperty(exports, "SSEParser", { enumerable: true, get: function () { return sse_parser_1.SSEParser; } });
|
|
34
|
+
Object.defineProperty(exports, "looksLikeSSEFile", { enumerable: true, get: function () { return sse_parser_1.looksLikeSSEFile; } });
|
|
35
|
+
var remix_parser_1 = require("./parser/remix-parser");
|
|
36
|
+
Object.defineProperty(exports, "RemixParser", { enumerable: true, get: function () { return remix_parser_1.RemixParser; } });
|
|
37
|
+
Object.defineProperty(exports, "looksLikeRemixRouteFile", { enumerable: true, get: function () { return remix_parser_1.looksLikeRemixRouteFile; } });
|
|
38
|
+
var websocket_parser_1 = require("./parser/websocket-parser");
|
|
39
|
+
Object.defineProperty(exports, "WebSocketParser", { enumerable: true, get: function () { return websocket_parser_1.WebSocketParser; } });
|
|
40
|
+
Object.defineProperty(exports, "looksLikeWebSocketFile", { enumerable: true, get: function () { return websocket_parser_1.looksLikeWebSocketFile; } });
|
|
41
|
+
var openapi_parser_1 = require("./parser/openapi-parser");
|
|
42
|
+
Object.defineProperty(exports, "OpenAPIParser", { enumerable: true, get: function () { return openapi_parser_1.OpenAPIParser; } });
|
|
43
|
+
Object.defineProperty(exports, "OPENAPI_PATTERNS", { enumerable: true, get: function () { return openapi_parser_1.OPENAPI_PATTERNS; } });
|
|
44
|
+
var prisma_parser_1 = require("./parser/prisma-parser");
|
|
45
|
+
Object.defineProperty(exports, "PrismaParser", { enumerable: true, get: function () { return prisma_parser_1.PrismaParser; } });
|
|
46
|
+
Object.defineProperty(exports, "PRISMA_PATTERNS", { enumerable: true, get: function () { return prisma_parser_1.PRISMA_PATTERNS; } });
|
|
26
47
|
var contract_parser_1 = require("./parser/contract-parser");
|
|
27
48
|
Object.defineProperty(exports, "ContractParser", { enumerable: true, get: function () { return contract_parser_1.ContractParser; } });
|
|
28
49
|
var zod_extractor_1 = require("./parser/zod-extractor");
|
|
@@ -33,7 +33,7 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
33
33
|
};
|
|
34
34
|
})();
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
-
exports.AuthDetector = void 0;
|
|
36
|
+
exports.AuthFlowInferrer = exports.AuthDetector = void 0;
|
|
37
37
|
const fs = __importStar(require("fs"));
|
|
38
38
|
const path = __importStar(require("path"));
|
|
39
39
|
const AUTH_SIGNALS = [
|
|
@@ -42,6 +42,15 @@ const AUTH_SIGNALS = [
|
|
|
42
42
|
{ pattern: '@farcaster/frame-node', type: 'farcaster-frame', priority: 100 },
|
|
43
43
|
{ pattern: 'validateFrameMessage', type: 'farcaster-frame', priority: 100 },
|
|
44
44
|
{ pattern: 'frameActionPayload', type: 'farcaster-frame', priority: 100 },
|
|
45
|
+
// Farcaster SIWF — must be before SIWE (higher priority)
|
|
46
|
+
{ pattern: '@farcaster/auth-kit', type: 'farcaster-siwf', priority: 98 },
|
|
47
|
+
{ pattern: 'verifySignInMessage', type: 'farcaster-siwf', priority: 98 },
|
|
48
|
+
{ pattern: 'useSignIn', type: 'farcaster-siwf', priority: 95 },
|
|
49
|
+
// SIWE
|
|
50
|
+
{ pattern: 'SiweMessage', type: 'siwe', priority: 95 },
|
|
51
|
+
{ pattern: "from 'siwe'", type: 'siwe', priority: 95 },
|
|
52
|
+
{ pattern: 'verifySiweMessage', type: 'siwe', priority: 95 },
|
|
53
|
+
{ pattern: 'generateNonce', type: 'siwe', priority: 90 },
|
|
45
54
|
// OAuth2 / NextAuth / Auth.js
|
|
46
55
|
{ pattern: 'NextAuth', type: 'oauth2', priority: 90 },
|
|
47
56
|
{ pattern: 'next-auth', type: 'oauth2', priority: 90 },
|
|
@@ -51,8 +60,14 @@ const AUTH_SIGNALS = [
|
|
|
51
60
|
{ pattern: 'oauth2', type: 'oauth2', priority: 80 },
|
|
52
61
|
{ pattern: 'access_token', type: 'oauth2', priority: 75 },
|
|
53
62
|
{ pattern: 'refresh_token', type: 'oauth2', priority: 75 },
|
|
54
|
-
|
|
55
|
-
{ pattern: '
|
|
63
|
+
// SAML
|
|
64
|
+
{ pattern: 'samlify', type: 'saml', priority: 92 },
|
|
65
|
+
{ pattern: 'passport-saml', type: 'saml', priority: 92 },
|
|
66
|
+
{ pattern: '@node-saml', type: 'saml', priority: 92 },
|
|
67
|
+
{ pattern: 'SAMLResponse', type: 'saml', priority: 88 },
|
|
68
|
+
// Clerk
|
|
69
|
+
{ pattern: 'clerkMiddleware', type: 'clerk', priority: 88 },
|
|
70
|
+
{ pattern: 'currentUser', type: 'clerk', priority: 70 },
|
|
56
71
|
// JWT / Bearer
|
|
57
72
|
{ pattern: 'jwt.verify', type: 'bearer', header: 'Authorization', scheme: 'Bearer', priority: 70 },
|
|
58
73
|
{ pattern: 'jsonwebtoken', type: 'bearer', header: 'Authorization', scheme: 'Bearer', priority: 70 },
|
|
@@ -61,6 +76,25 @@ const AUTH_SIGNALS = [
|
|
|
61
76
|
{ pattern: 'authorization.split', type: 'bearer', header: 'Authorization', scheme: 'Bearer', priority: 70 },
|
|
62
77
|
{ pattern: "headers.get('authorization')", type: 'bearer', header: 'Authorization', scheme: 'Bearer', priority: 65 },
|
|
63
78
|
{ pattern: "req.headers.authorization", type: 'bearer', header: 'Authorization', scheme: 'Bearer', priority: 65 },
|
|
79
|
+
// Privy
|
|
80
|
+
{ pattern: '@privy-io/react-auth', type: 'privy', priority: 85 },
|
|
81
|
+
{ pattern: 'usePrivy', type: 'privy', priority: 85 },
|
|
82
|
+
{ pattern: 'PrivyProvider', type: 'privy', priority: 85 },
|
|
83
|
+
// Dynamic
|
|
84
|
+
{ pattern: '@dynamic-labs/sdk-react-core', type: 'dynamic', priority: 85 },
|
|
85
|
+
{ pattern: 'DynamicContextProvider', type: 'dynamic', priority: 85 },
|
|
86
|
+
{ pattern: 'useDynamicContext', type: 'dynamic', priority: 80 },
|
|
87
|
+
// Magic
|
|
88
|
+
{ pattern: 'magic-sdk', type: 'magic', priority: 80 },
|
|
89
|
+
{ pattern: '@magic-sdk', type: 'magic', priority: 80 },
|
|
90
|
+
{ pattern: 'new Magic(', type: 'magic', priority: 80 },
|
|
91
|
+
// Passkey / WebAuthn
|
|
92
|
+
{ pattern: '@simplewebauthn/browser', type: 'passkey', priority: 78 },
|
|
93
|
+
{ pattern: 'startAuthentication', type: 'passkey', priority: 78 },
|
|
94
|
+
{ pattern: 'startRegistration', type: 'passkey', priority: 78 },
|
|
95
|
+
{ pattern: 'navigator.credentials.create', type: 'passkey', priority: 75 },
|
|
96
|
+
// Supabase
|
|
97
|
+
{ pattern: 'supabase.auth', type: 'supabase', priority: 75 },
|
|
64
98
|
// API key
|
|
65
99
|
{ pattern: "'x-api-key'", type: 'api-key', header: 'X-API-Key', priority: 60 },
|
|
66
100
|
{ pattern: '"x-api-key"', type: 'api-key', header: 'X-API-Key', priority: 60 },
|
|
@@ -80,6 +114,7 @@ const AUTH_SIGNALS = [
|
|
|
80
114
|
];
|
|
81
115
|
class AuthDetector {
|
|
82
116
|
best = null;
|
|
117
|
+
_flowInferrer = new AuthFlowInferrer();
|
|
83
118
|
scanContent(content) {
|
|
84
119
|
for (const signal of AUTH_SIGNALS) {
|
|
85
120
|
if (content.includes(signal.pattern)) {
|
|
@@ -88,6 +123,7 @@ class AuthDetector {
|
|
|
88
123
|
}
|
|
89
124
|
}
|
|
90
125
|
}
|
|
126
|
+
this._flowInferrer.addContent(content);
|
|
91
127
|
}
|
|
92
128
|
/** Read package.json to detect auth libraries in dependencies */
|
|
93
129
|
readPackageJson(projectPath) {
|
|
@@ -99,8 +135,24 @@ class AuthDetector {
|
|
|
99
135
|
const deps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
100
136
|
if (deps['next-auth'] || deps['@auth/core'])
|
|
101
137
|
this.applySignal({ pattern: 'next-auth', type: 'oauth2', priority: 90 });
|
|
138
|
+
if (deps['@farcaster/auth-kit'])
|
|
139
|
+
this.applySignal({ pattern: 'farcaster-siwf', type: 'farcaster-siwf', priority: 98 });
|
|
140
|
+
if (deps['siwe'])
|
|
141
|
+
this.applySignal({ pattern: 'siwe', type: 'siwe', priority: 95 });
|
|
142
|
+
if (deps['samlify'] || deps['passport-saml'])
|
|
143
|
+
this.applySignal({ pattern: 'saml', type: 'saml', priority: 92 });
|
|
102
144
|
if (deps['@clerk/nextjs'] || deps['@clerk/clerk-sdk-node'])
|
|
103
|
-
this.applySignal({ pattern: 'clerk', type: '
|
|
145
|
+
this.applySignal({ pattern: 'clerk', type: 'clerk', priority: 88 });
|
|
146
|
+
if (deps['@privy-io/react-auth'])
|
|
147
|
+
this.applySignal({ pattern: 'privy', type: 'privy', priority: 85 });
|
|
148
|
+
if (deps['@dynamic-labs/sdk-react-core'])
|
|
149
|
+
this.applySignal({ pattern: 'dynamic', type: 'dynamic', priority: 85 });
|
|
150
|
+
if (deps['magic-sdk'] || deps['@magic-sdk/admin'])
|
|
151
|
+
this.applySignal({ pattern: 'magic', type: 'magic', priority: 80 });
|
|
152
|
+
if (deps['@simplewebauthn/browser'])
|
|
153
|
+
this.applySignal({ pattern: 'passkey', type: 'passkey', priority: 78 });
|
|
154
|
+
if (deps['@supabase/supabase-js'])
|
|
155
|
+
this.applySignal({ pattern: 'supabase', type: 'supabase', priority: 75 });
|
|
104
156
|
if (deps['jsonwebtoken'] || deps['jose'])
|
|
105
157
|
this.applySignal({ pattern: 'jwt', type: 'bearer', header: 'Authorization', scheme: 'Bearer', priority: 70 });
|
|
106
158
|
if (deps['iron-session'])
|
|
@@ -118,14 +170,44 @@ class AuthDetector {
|
|
|
118
170
|
getAuth() {
|
|
119
171
|
if (!this.best)
|
|
120
172
|
return { type: 'none' };
|
|
121
|
-
const
|
|
173
|
+
const type = this.best.type;
|
|
174
|
+
const config = { type };
|
|
122
175
|
if (this.best.header)
|
|
123
176
|
config.header = this.best.header;
|
|
124
177
|
if (this.best.scheme)
|
|
125
178
|
config.scheme = this.best.scheme;
|
|
126
179
|
if (this.best.queryParam)
|
|
127
180
|
config.queryParam = this.best.queryParam;
|
|
128
|
-
|
|
181
|
+
const flowFields = this._flowInferrer.infer(type);
|
|
182
|
+
return { ...config, ...flowFields };
|
|
129
183
|
}
|
|
130
184
|
}
|
|
131
185
|
exports.AuthDetector = AuthDetector;
|
|
186
|
+
class AuthFlowInferrer {
|
|
187
|
+
scannedContents = [];
|
|
188
|
+
addContent(content) {
|
|
189
|
+
this.scannedContents.push(content);
|
|
190
|
+
}
|
|
191
|
+
infer(authType) {
|
|
192
|
+
const result = {};
|
|
193
|
+
for (const content of this.scannedContents) {
|
|
194
|
+
if (!result.nonceUrl && (authType === 'siwe' || authType === 'farcaster-siwf')) {
|
|
195
|
+
const m = content.match(/['"`](\/api\/auth\/nonce[^'"`\s]*)['"`]/);
|
|
196
|
+
if (m)
|
|
197
|
+
result.nonceUrl = m[1];
|
|
198
|
+
}
|
|
199
|
+
if (!result.callbackUrl && authType === 'oauth2') {
|
|
200
|
+
const m = content.match(/['"`](\/api\/auth\/callback[^'"`\s]*)['"`]/);
|
|
201
|
+
if (m)
|
|
202
|
+
result.callbackUrl = m[1];
|
|
203
|
+
}
|
|
204
|
+
if (!result.loginUrl) {
|
|
205
|
+
const m = content.match(/['"`](\/sign-in|\/login|\/auth\/login)['"`]/);
|
|
206
|
+
if (m)
|
|
207
|
+
result.loginUrl = m[1];
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
return result;
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
exports.AuthFlowInferrer = AuthFlowInferrer;
|
|
@@ -93,10 +93,10 @@ class ContractParser {
|
|
|
93
93
|
abiFunction: item.name,
|
|
94
94
|
isReadOnly,
|
|
95
95
|
safety,
|
|
96
|
-
agentSafe: (0, intent_classifier_1.deriveAgentSafe)(safety),
|
|
96
|
+
agentSafe: (0, intent_classifier_1.deriveAgentSafe)(safety, item.name),
|
|
97
97
|
requiredAuth: (0, intent_classifier_1.inferActionAuth)({ safety, isReadOnly, type: 'contract' }),
|
|
98
|
-
|
|
99
|
-
|
|
98
|
+
parameters: { properties: this.mapAbiInputs(item.inputs ?? []) },
|
|
99
|
+
returns: {
|
|
100
100
|
type: this.mapAbiOutputs(item.outputs ?? []),
|
|
101
101
|
description: '',
|
|
102
102
|
},
|
|
@@ -172,10 +172,10 @@ class ContractParser {
|
|
|
172
172
|
...(chainId !== undefined ? { chainId } : {}),
|
|
173
173
|
...(contractAddress !== undefined ? { contractAddress } : {}),
|
|
174
174
|
safety,
|
|
175
|
-
agentSafe: (0, intent_classifier_1.deriveAgentSafe)(safety),
|
|
175
|
+
agentSafe: (0, intent_classifier_1.deriveAgentSafe)(safety, functionName),
|
|
176
176
|
requiredAuth: (0, intent_classifier_1.inferActionAuth)({ safety, isReadOnly: false, type: 'contract' }),
|
|
177
|
-
|
|
178
|
-
|
|
177
|
+
parameters: { properties: parameters },
|
|
178
|
+
returns: { type: 'any' },
|
|
179
179
|
});
|
|
180
180
|
});
|
|
181
181
|
return actions;
|
|
@@ -113,10 +113,10 @@ class ExpressParser {
|
|
|
113
113
|
location: routePath,
|
|
114
114
|
method: httpMethod,
|
|
115
115
|
safety,
|
|
116
|
-
agentSafe: (0, intent_classifier_1.deriveAgentSafe)(safety),
|
|
116
|
+
agentSafe: (0, intent_classifier_1.deriveAgentSafe)(safety, actionName),
|
|
117
117
|
requiredAuth: (0, intent_classifier_1.inferActionAuth)({ safety, httpMethod, type: 'api' }),
|
|
118
|
-
|
|
119
|
-
|
|
118
|
+
parameters: { properties: this.extractRouteParams(routePath) },
|
|
119
|
+
returns: { type: 'any' },
|
|
120
120
|
});
|
|
121
121
|
});
|
|
122
122
|
return actions;
|
|
@@ -9,7 +9,7 @@ const INTENT_RULES = [
|
|
|
9
9
|
// getUsers don't have a word boundary after the verb segment. Leading \b is kept
|
|
10
10
|
// to avoid matching mid-word (e.g. "undo" matching "do").
|
|
11
11
|
// Game
|
|
12
|
-
{ pattern: /\b(play|flip|roll|spin|bet|guess|draw|move|attack|defend|claim(?:Prize|Reward|Win))/i, intent: 'game.play' },
|
|
12
|
+
{ pattern: /\b(play|flip|roll|spin|bet|guess|draw|move|make(?:Move)?|drop(?:Piece)?|attack|defend|claim(?:Prize|Reward|Win))/i, intent: 'game.play' },
|
|
13
13
|
{ pattern: /\b(score|leaderboard|rank|highscore)/i, intent: 'game.score' },
|
|
14
14
|
{ pattern: /\b(join(?:Game|Room|Lobby)|create(?:Game|Room)|start(?:Game|Round))/i, intent: 'game.join' },
|
|
15
15
|
// Finance / DeFi
|
|
@@ -25,16 +25,16 @@ const INTENT_RULES = [
|
|
|
25
25
|
{ pattern: /\b(listNFT|sellNFT|listFor(?:Sale)?)/i, intent: 'nft.list' },
|
|
26
26
|
{ pattern: /\b(buyNFT|purchaseNFT)/i, intent: 'nft.buy' },
|
|
27
27
|
{ pattern: /\b(burnNFT|burn)/i, intent: 'nft.burn' },
|
|
28
|
+
// Governance (must come BEFORE Social so castVote → governance.vote, not social.cast)
|
|
29
|
+
{ pattern: /\b(vote|castVote|submitVote)/i, intent: 'governance.vote' },
|
|
30
|
+
{ pattern: /\b(propose|createProposal|submitProposal)/i, intent: 'governance.propose' },
|
|
31
|
+
{ pattern: /\b(delegate|undelegate)/i, intent: 'governance.delegate' },
|
|
28
32
|
// Social (Farcaster-native)
|
|
29
|
-
{ pattern: /\b(cast|compose(?:Cast)?|post(?:Cast)?)/i, intent: 'social.cast' },
|
|
33
|
+
{ pattern: /\b(cast(?!Vote)|compose(?:Cast)?|post(?:Cast)?)/i, intent: 'social.cast' },
|
|
30
34
|
{ pattern: /\b(follow|unfollow|subscribe)/i, intent: 'social.follow' },
|
|
31
35
|
{ pattern: /\b(like|react|upvote|downvote)/i, intent: 'social.react' },
|
|
32
36
|
{ pattern: /\b(comment|reply)/i, intent: 'social.reply' },
|
|
33
37
|
{ pattern: /\b(share|recast|repost)/i, intent: 'social.share' },
|
|
34
|
-
// Governance
|
|
35
|
-
{ pattern: /\b(vote|castVote|submitVote)/i, intent: 'governance.vote' },
|
|
36
|
-
{ pattern: /\b(propose|createProposal|submitProposal)/i, intent: 'governance.propose' },
|
|
37
|
-
{ pattern: /\b(delegate|undelegate)/i, intent: 'governance.delegate' },
|
|
38
38
|
// Auth
|
|
39
39
|
{ pattern: /\b(login|logout|signIn|signOut|connect|disconnect)/i, intent: 'auth.session' },
|
|
40
40
|
{ pattern: /\b(register|signup|createAccount)/i, intent: 'auth.register' },
|
|
@@ -73,7 +73,9 @@ const DESTRUCTIVE_VERBS = /\b(delete|remove|destroy|burn|archive|purge|wipe|clea
|
|
|
73
73
|
* updateSsn, exportPrivateKey, getMedicalRecord.
|
|
74
74
|
*/
|
|
75
75
|
// No \b — these nouns appear anywhere in camelCase (resetPassword, submitKyc, getSsn)
|
|
76
|
-
const CONFIDENTIAL_NOUNS = /(password|credential|privateKey|secretKey|biometric|ssn|taxId|pii|kyc|
|
|
76
|
+
const CONFIDENTIAL_NOUNS = /(password|credential|privateKey|secretKey|biometric|ssn|taxId|tax_id|pii|kyc|medical|health|passport|license|creditCard|cvv|encrypted|identity|dob|birthDate|address|phone|mobile|email)/i;
|
|
77
|
+
/** Verbs that imply high-privilege administrative or sensitive state changes. */
|
|
78
|
+
const SENSITIVE_WRITE_VERBS = /\b(admin|role|permission|password|owner|ban|block|lock|unlock|grant|revoke|delegate|sudo|root|secret)/i;
|
|
77
79
|
/**
|
|
78
80
|
* Classify the safety level of an action.
|
|
79
81
|
*
|
|
@@ -97,19 +99,40 @@ function classifySafety(opts) {
|
|
|
97
99
|
return 'confidential';
|
|
98
100
|
return 'write';
|
|
99
101
|
}
|
|
102
|
+
// Financial and confidential always win regardless of type
|
|
100
103
|
if (FINANCIAL_VERBS.test(name))
|
|
101
104
|
return 'financial';
|
|
102
105
|
if (CONFIDENTIAL_NOUNS.test(name))
|
|
103
106
|
return 'confidential';
|
|
107
|
+
if (type === 'function' || type === 'ui') {
|
|
108
|
+
if (DESTRUCTIVE_VERBS.test(name))
|
|
109
|
+
return 'destructive';
|
|
110
|
+
// Auth functions are confidential (handle credentials)
|
|
111
|
+
if (/\b(login|logout|signIn|signOut|register|signup|createAccount)/i.test(name))
|
|
112
|
+
return 'confidential';
|
|
113
|
+
// Social/game → write
|
|
114
|
+
return 'write';
|
|
115
|
+
}
|
|
104
116
|
if (httpMethod === 'GET')
|
|
105
117
|
return 'read';
|
|
118
|
+
if (isReadOnly)
|
|
119
|
+
return 'read';
|
|
106
120
|
if (DESTRUCTIVE_VERBS.test(name))
|
|
107
121
|
return 'destructive';
|
|
108
122
|
return 'write';
|
|
109
123
|
}
|
|
110
|
-
/**
|
|
111
|
-
|
|
112
|
-
|
|
124
|
+
/**
|
|
125
|
+
* Derive agentSafe from safety level.
|
|
126
|
+
* Only 'read' and non-sensitive 'write' actions are safe for autonomous execution.
|
|
127
|
+
*/
|
|
128
|
+
function deriveAgentSafe(safety, name) {
|
|
129
|
+
if (safety === 'read')
|
|
130
|
+
return true;
|
|
131
|
+
if (safety === 'write') {
|
|
132
|
+
// Narrowing: check if the write action name contains high-privilege keywords
|
|
133
|
+
return !SENSITIVE_WRITE_VERBS.test(name);
|
|
134
|
+
}
|
|
135
|
+
return false;
|
|
113
136
|
}
|
|
114
137
|
/**
|
|
115
138
|
* Infer per-action auth requirement.
|
|
@@ -149,8 +172,10 @@ function inferActionAuth(opts) {
|
|
|
149
172
|
if (safety === 'destructive') {
|
|
150
173
|
return { required: 'required' };
|
|
151
174
|
}
|
|
152
|
-
//
|
|
153
|
-
|
|
175
|
+
// No-auth app → read and write actions are public (non-contract only).
|
|
176
|
+
// Financial/confidential/destructive retain their required auth even on no-auth apps
|
|
177
|
+
// (those actions handle money or PII and should always require confirmation).
|
|
178
|
+
if (type !== 'contract' && (appAuthType === 'none' || !appAuthType) && (safety === 'read' || safety === 'write')) {
|
|
154
179
|
return { required: 'public' };
|
|
155
180
|
}
|
|
156
181
|
return { required: 'required' };
|