agent-manifest 3.2.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/LICENSE +21 -0
- package/README.md +204 -0
- package/dist/cli.js +174 -0
- package/dist/discovery/service.js +159 -0
- package/dist/generator/json.js +25 -0
- package/dist/index.js +36 -0
- package/dist/parser/auth-detector.js +131 -0
- package/dist/parser/capability-detector.js +115 -0
- package/dist/parser/contract-parser.js +347 -0
- package/dist/parser/express-parser.js +163 -0
- package/dist/parser/intent-classifier.js +157 -0
- package/dist/parser/ts-parser.js +419 -0
- package/dist/parser/zod-extractor.js +335 -0
- package/dist/types.js +2 -0
- package/package.json +61 -0
- package/schema/agent.schema.json +168 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 Ayobami Adefolalu
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
# agentjson
|
|
2
|
+
|
|
3
|
+
A universal CLI compiler that scans any web app codebase and generates a `public/agent.json` manifest — so AI agents can discover and interact with your app without custom integration work.
|
|
4
|
+
|
|
5
|
+
Works with Next.js (App Router + Pages Router), Express, Hono, Fastify, and any TypeScript/JavaScript project with smart contracts.
|
|
6
|
+
|
|
7
|
+
## Installation
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
# Run once, no install
|
|
11
|
+
npx agent-manifest
|
|
12
|
+
|
|
13
|
+
# Or install globally
|
|
14
|
+
npm install -g agent-manifest
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Usage
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
# Scan current directory, output to public/agent.json
|
|
21
|
+
agentjson
|
|
22
|
+
|
|
23
|
+
# Specify project path and output
|
|
24
|
+
agentjson -p ./my-app -o ./public/agent.json
|
|
25
|
+
|
|
26
|
+
# With auth metadata
|
|
27
|
+
agentjson --auth-type bearer --auth-header Authorization --auth-docs https://myapp.xyz/docs/auth
|
|
28
|
+
|
|
29
|
+
# Add author / URL to manifest
|
|
30
|
+
agentjson --author "0xDev" --url https://myapp.xyz
|
|
31
|
+
|
|
32
|
+
# Validate an existing agent.json
|
|
33
|
+
agentjson validate ./public/agent.json
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## What gets generated
|
|
37
|
+
|
|
38
|
+
```json
|
|
39
|
+
{
|
|
40
|
+
"name": "FlipIt",
|
|
41
|
+
"description": "On-chain coin flip game",
|
|
42
|
+
"version": "1.0.0",
|
|
43
|
+
"author": "0xDev",
|
|
44
|
+
"url": "https://flipit.xyz",
|
|
45
|
+
"auth": { "type": "farcaster-frame" },
|
|
46
|
+
"capabilities": ["wallet", "payments"],
|
|
47
|
+
"actions": [
|
|
48
|
+
{
|
|
49
|
+
"name": "flip",
|
|
50
|
+
"description": "Flip a coin and bet ETH",
|
|
51
|
+
"intent": "game.play",
|
|
52
|
+
"type": "contract",
|
|
53
|
+
"location": "./src/abis/FlipABI.json",
|
|
54
|
+
"abiFunction": "flip",
|
|
55
|
+
"chainId": 8453,
|
|
56
|
+
"contractAddress": { "$env": "NEXT_PUBLIC_FLIP_ADDRESS" },
|
|
57
|
+
"safety": "financial",
|
|
58
|
+
"agentSafe": false,
|
|
59
|
+
"requiredAuth": { "required": "farcaster-signed" },
|
|
60
|
+
"inputs": {
|
|
61
|
+
"choice": { "type": "string", "enum": ["heads", "tails"], "required": true },
|
|
62
|
+
"amount": { "type": "number", "required": true }
|
|
63
|
+
},
|
|
64
|
+
"outputs": { "type": "void" }
|
|
65
|
+
}
|
|
66
|
+
],
|
|
67
|
+
"metadata": {}
|
|
68
|
+
}
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
## Automatic detection
|
|
72
|
+
|
|
73
|
+
### API routes
|
|
74
|
+
- **Next.js App Router** — `app/api/**/route.ts` (GET, POST, PUT, DELETE, PATCH)
|
|
75
|
+
- **Next.js Pages Router** — `pages/api/**/*.ts`
|
|
76
|
+
- **Express / Hono / Fastify** — `app.get('/path', handler)` and `router.post(...)` patterns
|
|
77
|
+
- **Server Actions** — `'use server'` files
|
|
78
|
+
|
|
79
|
+
### Smart contracts
|
|
80
|
+
- ABI JSON files (`*ABI.json`, `abi/*.json`, `abis/*.json`)
|
|
81
|
+
- Wagmi hooks — `useWriteContract`, `useContractWrite`, `writeContract`
|
|
82
|
+
- Contract addresses extracted as `{ "$env": "VAR_NAME" }` — secrets never embedded
|
|
83
|
+
|
|
84
|
+
### Zod schemas
|
|
85
|
+
- Inline `z.object(...)` schemas in the same file as `.parse()` / `.safeParse()`
|
|
86
|
+
- **Cross-file** — schemas imported from centralised files (e.g. `src/lib/schemas.ts`) are resolved via the import graph
|
|
87
|
+
|
|
88
|
+
### Auth
|
|
89
|
+
- Detected from source code and `package.json` dependencies:
|
|
90
|
+
`bearer` · `api-key` · `oauth2` · `basic` · `farcaster-frame` · `cookie`
|
|
91
|
+
- Override with `--auth-type` if detection misses
|
|
92
|
+
|
|
93
|
+
### Capabilities
|
|
94
|
+
`wallet` · `payments` · `ai` · `database` · `realtime` · `storage` · `farcaster`
|
|
95
|
+
|
|
96
|
+
---
|
|
97
|
+
|
|
98
|
+
## Safety levels
|
|
99
|
+
|
|
100
|
+
Every action is classified automatically:
|
|
101
|
+
|
|
102
|
+
| Level | Meaning | `agentSafe` |
|
|
103
|
+
|---|---|---|
|
|
104
|
+
| `read` | Read-only, no side effects | `true` |
|
|
105
|
+
| `write` | Mutates state, no money movement | `true` |
|
|
106
|
+
| `financial` | Moves tokens or value | `false` |
|
|
107
|
+
| `destructive` | Deletes or burns irreversibly | `false` |
|
|
108
|
+
| `confidential` | Handles PII, passwords, credentials, KYC | `false` |
|
|
109
|
+
|
|
110
|
+
`agentSafe: false` means the agent must ask for human confirmation before executing.
|
|
111
|
+
|
|
112
|
+
---
|
|
113
|
+
|
|
114
|
+
## Intent taxonomy
|
|
115
|
+
|
|
116
|
+
Actions get a `domain.verb` intent so agents understand what they do:
|
|
117
|
+
|
|
118
|
+
| Domain | Examples |
|
|
119
|
+
|---|---|
|
|
120
|
+
| `game.*` | `game.play`, `game.join`, `game.score` |
|
|
121
|
+
| `finance.*` | `finance.transfer`, `finance.swap`, `finance.stake`, `finance.approve` |
|
|
122
|
+
| `nft.*` | `nft.mint`, `nft.burn`, `nft.list` |
|
|
123
|
+
| `social.*` | `social.cast`, `social.follow`, `social.react`, `social.share` |
|
|
124
|
+
| `governance.*` | `governance.vote`, `governance.propose`, `governance.delegate` |
|
|
125
|
+
| `auth.*` | `auth.session`, `auth.register`, `auth.verify` |
|
|
126
|
+
| `data.*` | `data.read`, `data.create`, `data.update`, `data.delete` |
|
|
127
|
+
| `media.*` | `media.upload` |
|
|
128
|
+
|
|
129
|
+
Override with a JSDoc tag: `@agent-action intent=custom.thing`
|
|
130
|
+
|
|
131
|
+
---
|
|
132
|
+
|
|
133
|
+
## Explicit annotation (optional)
|
|
134
|
+
|
|
135
|
+
The compiler discovers actions automatically, but you can annotate for precision:
|
|
136
|
+
|
|
137
|
+
```typescript
|
|
138
|
+
/**
|
|
139
|
+
* @agent-action intent=game.play
|
|
140
|
+
* @description Flip a coin and bet ETH on the outcome.
|
|
141
|
+
* @param choice The side to bet on — "heads" or "tails".
|
|
142
|
+
* @param amount The amount of ETH to wager in wei.
|
|
143
|
+
*/
|
|
144
|
+
export async function flip(choice: string, amount: bigint) {
|
|
145
|
+
// ...
|
|
146
|
+
}
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
---
|
|
150
|
+
|
|
151
|
+
## Auth scopes
|
|
152
|
+
|
|
153
|
+
Per-action auth is inferred automatically:
|
|
154
|
+
|
|
155
|
+
| Condition | `requiredAuth` |
|
|
156
|
+
|---|---|
|
|
157
|
+
| Contract `view`/`pure` function | `public` |
|
|
158
|
+
| GET endpoint on public app | `public` |
|
|
159
|
+
| Farcaster frame app + write action | `farcaster-signed` |
|
|
160
|
+
| Financial action | `required` + scope `payments:write` |
|
|
161
|
+
| Confidential action (POST) | `required` + scope `pii:write` |
|
|
162
|
+
| Confidential action (GET) | `required` + scope `pii:read` |
|
|
163
|
+
| Everything else | `required` |
|
|
164
|
+
|
|
165
|
+
---
|
|
166
|
+
|
|
167
|
+
## CLI options
|
|
168
|
+
|
|
169
|
+
```
|
|
170
|
+
agentjson [options]
|
|
171
|
+
|
|
172
|
+
Options:
|
|
173
|
+
-p, --path <path> Project root (default: ".")
|
|
174
|
+
-o, --output <output> Output path (default: "./public/agent.json")
|
|
175
|
+
--author <author> Author name or organization
|
|
176
|
+
--url <url> App homepage URL
|
|
177
|
+
--auth-type <type> Override detected auth type
|
|
178
|
+
none | bearer | api-key | oauth2 | basic | farcaster-frame | cookie
|
|
179
|
+
--auth-header <header> Auth header name (default: Authorization)
|
|
180
|
+
--auth-docs <url> URL where agents can obtain credentials
|
|
181
|
+
-V, --version Show version
|
|
182
|
+
-h, --help Show help
|
|
183
|
+
|
|
184
|
+
Commands:
|
|
185
|
+
validate <file> Validate an existing agent.json against the schema
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
---
|
|
189
|
+
|
|
190
|
+
## JSON Schema
|
|
191
|
+
|
|
192
|
+
A full JSON Schema is bundled at `node_modules/@0xdeve/agentjson/schema/agent.schema.json` for editor autocomplete and CI validation.
|
|
193
|
+
|
|
194
|
+
---
|
|
195
|
+
|
|
196
|
+
## Performance
|
|
197
|
+
|
|
198
|
+
On large repos the compiler skips unchanged files using SHA-1 content hashing. Cache is stored in `.agentjson-cache.json` at the project root (add to `.gitignore`).
|
|
199
|
+
|
|
200
|
+
---
|
|
201
|
+
|
|
202
|
+
## License
|
|
203
|
+
|
|
204
|
+
ISC
|
package/dist/cli.js
ADDED
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
4
|
+
if (k2 === undefined) k2 = k;
|
|
5
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
6
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
7
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
8
|
+
}
|
|
9
|
+
Object.defineProperty(o, k2, desc);
|
|
10
|
+
}) : (function(o, m, k, k2) {
|
|
11
|
+
if (k2 === undefined) k2 = k;
|
|
12
|
+
o[k2] = m[k];
|
|
13
|
+
}));
|
|
14
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
15
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
16
|
+
}) : function(o, v) {
|
|
17
|
+
o["default"] = v;
|
|
18
|
+
});
|
|
19
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
20
|
+
var ownKeys = function(o) {
|
|
21
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
22
|
+
var ar = [];
|
|
23
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
24
|
+
return ar;
|
|
25
|
+
};
|
|
26
|
+
return ownKeys(o);
|
|
27
|
+
};
|
|
28
|
+
return function (mod) {
|
|
29
|
+
if (mod && mod.__esModule) return mod;
|
|
30
|
+
var result = {};
|
|
31
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
32
|
+
__setModuleDefault(result, mod);
|
|
33
|
+
return result;
|
|
34
|
+
};
|
|
35
|
+
})();
|
|
36
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
37
|
+
const commander_1 = require("commander");
|
|
38
|
+
const fs = __importStar(require("fs"));
|
|
39
|
+
const path = __importStar(require("path"));
|
|
40
|
+
const index_1 = require("./index");
|
|
41
|
+
const program = new commander_1.Command();
|
|
42
|
+
// ─── compile ─────────────────────────────────────────────────────────────────
|
|
43
|
+
program
|
|
44
|
+
.name('agentjson')
|
|
45
|
+
.description('Universal agent manifest compiler — generates agent.json for any web app')
|
|
46
|
+
.version('3.1.0')
|
|
47
|
+
.option('-p, --path <path>', 'path to the project root', '.')
|
|
48
|
+
.option('-o, --output <output>', 'output path for agent.json', './public/agent.json')
|
|
49
|
+
.option('--author <author>', 'author name or organization')
|
|
50
|
+
.option('--url <url>', 'app homepage URL')
|
|
51
|
+
.option('--auth-type <type>', 'override detected auth type: none | bearer | api-key | oauth2 | basic | farcaster-frame | cookie')
|
|
52
|
+
.option('--auth-header <header>', 'auth header name (default: Authorization)')
|
|
53
|
+
.option('--auth-docs <url>', 'URL where agents can obtain credentials')
|
|
54
|
+
.action(async (options) => {
|
|
55
|
+
const projectPath = path.resolve(options.path);
|
|
56
|
+
const outputPath = path.resolve(options.output);
|
|
57
|
+
console.log(`🚀 Scanning project at: ${projectPath}`);
|
|
58
|
+
const discovery = new index_1.DiscoveryService(projectPath);
|
|
59
|
+
const files = await discovery.findRelevantFiles();
|
|
60
|
+
console.log(`🔍 Found ${files.length} relevant files.`);
|
|
61
|
+
const tsParser = new index_1.TSParser(projectPath);
|
|
62
|
+
const expressParser = new index_1.ExpressParser(tsParser.getProject());
|
|
63
|
+
const actions = [];
|
|
64
|
+
for (const file of files) {
|
|
65
|
+
console.log(`📄 Parsing: ${path.relative(projectPath, file)}`);
|
|
66
|
+
const fileActions = await tsParser.parseFile(file);
|
|
67
|
+
actions.push(...fileActions);
|
|
68
|
+
if (file.endsWith('.ts') || file.endsWith('.js')) {
|
|
69
|
+
try {
|
|
70
|
+
const content = fs.readFileSync(file, 'utf8');
|
|
71
|
+
if ((0, index_1.looksLikeRouteFile)(content)) {
|
|
72
|
+
const expressActions = await expressParser.parseFile(file, projectPath);
|
|
73
|
+
actions.push(...expressActions);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
catch { /* ignore */ }
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
console.log(`✨ Detected ${actions.length} agent actions.`);
|
|
80
|
+
const uniqueActions = new Map();
|
|
81
|
+
for (const action of actions) {
|
|
82
|
+
const existing = uniqueActions.get(action.name);
|
|
83
|
+
if (!existing || Object.keys(action.inputs).length > Object.keys(existing.inputs).length) {
|
|
84
|
+
uniqueActions.set(action.name, action);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
const appMetadata = tsParser.getAppMetadata();
|
|
88
|
+
if (options.author)
|
|
89
|
+
appMetadata.author = options.author;
|
|
90
|
+
if (options.url)
|
|
91
|
+
appMetadata.url = options.url;
|
|
92
|
+
const detectedAuth = tsParser.getAuth();
|
|
93
|
+
const auth = {
|
|
94
|
+
...detectedAuth,
|
|
95
|
+
...(options.authType && { type: options.authType }),
|
|
96
|
+
...(options.authHeader && { header: options.authHeader }),
|
|
97
|
+
...(options.authDocs && { docsUrl: options.authDocs }),
|
|
98
|
+
};
|
|
99
|
+
const generator = new index_1.ManifestGenerator();
|
|
100
|
+
const manifest = generator.generate(Array.from(uniqueActions.values()), appMetadata, tsParser.getCapabilities(), auth);
|
|
101
|
+
fs.mkdirSync(path.dirname(outputPath), { recursive: true });
|
|
102
|
+
fs.writeFileSync(outputPath, JSON.stringify(manifest, null, 2));
|
|
103
|
+
console.log(`✅ agent.json generated at: ${outputPath}`);
|
|
104
|
+
console.log(` ${uniqueActions.size} actions · ${manifest.capabilities.length} capabilities · auth: ${auth.type}`);
|
|
105
|
+
});
|
|
106
|
+
// ─── validate ────────────────────────────────────────────────────────────────
|
|
107
|
+
program
|
|
108
|
+
.command('validate [file]')
|
|
109
|
+
.description('Validate an agent.json manifest against the schema')
|
|
110
|
+
.action((file = './public/agent.json') => {
|
|
111
|
+
const manifestPath = path.resolve(file);
|
|
112
|
+
if (!fs.existsSync(manifestPath)) {
|
|
113
|
+
console.error(`❌ File not found: ${manifestPath}`);
|
|
114
|
+
process.exit(1);
|
|
115
|
+
}
|
|
116
|
+
let manifest;
|
|
117
|
+
try {
|
|
118
|
+
manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf8'));
|
|
119
|
+
}
|
|
120
|
+
catch {
|
|
121
|
+
console.error(`❌ Invalid JSON in: ${manifestPath}`);
|
|
122
|
+
process.exit(1);
|
|
123
|
+
}
|
|
124
|
+
const errors = validateManifest(manifest);
|
|
125
|
+
if (errors.length === 0) {
|
|
126
|
+
console.log(`✅ ${manifestPath} is valid`);
|
|
127
|
+
console.log(` ${manifest.actions.length} actions · ${manifest.capabilities.length} capabilities · auth: ${manifest.auth.type}`);
|
|
128
|
+
}
|
|
129
|
+
else {
|
|
130
|
+
console.error(`❌ Validation failed (${errors.length} error${errors.length > 1 ? 's' : ''}):`);
|
|
131
|
+
for (const err of errors)
|
|
132
|
+
console.error(` • ${err}`);
|
|
133
|
+
process.exit(1);
|
|
134
|
+
}
|
|
135
|
+
});
|
|
136
|
+
// ─── Structural validator ────────────────────────────────────────────────────
|
|
137
|
+
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(['none', 'bearer', 'api-key', 'oauth2', 'basic', 'farcaster-frame', 'cookie']);
|
|
140
|
+
const INTENT_RE = /^[a-z][a-z0-9]*\.[a-z][a-z0-9]*$/;
|
|
141
|
+
function validateManifest(m) {
|
|
142
|
+
const errors = [];
|
|
143
|
+
if (typeof m.name !== 'string' || !m.name)
|
|
144
|
+
errors.push('`name` must be a non-empty string');
|
|
145
|
+
if (typeof m.version !== 'string' || !/^\d+\.\d+\.\d+$/.test(m.version))
|
|
146
|
+
errors.push('`version` must be a semver string');
|
|
147
|
+
if (!m.auth || !AUTH_TYPES.has(m.auth.type))
|
|
148
|
+
errors.push('`auth.type` must be one of: ' + [...AUTH_TYPES].join(' | '));
|
|
149
|
+
if (!Array.isArray(m.capabilities))
|
|
150
|
+
errors.push('`capabilities` must be an array');
|
|
151
|
+
if (!Array.isArray(m.actions))
|
|
152
|
+
errors.push('`actions` must be an array');
|
|
153
|
+
else {
|
|
154
|
+
m.actions.forEach((action, i) => {
|
|
155
|
+
const prefix = `actions[${i}] ("${action.name ?? '?'}")`;
|
|
156
|
+
if (!action.name)
|
|
157
|
+
errors.push(`${prefix}: missing \`name\``);
|
|
158
|
+
if (!action.intent)
|
|
159
|
+
errors.push(`${prefix}: missing \`intent\``);
|
|
160
|
+
else if (!INTENT_RE.test(action.intent))
|
|
161
|
+
errors.push(`${prefix}: \`intent\` must match domain.verb format`);
|
|
162
|
+
if (!ACTION_TYPES.has(action.type))
|
|
163
|
+
errors.push(`${prefix}: \`type\` must be one of api|contract|function`);
|
|
164
|
+
if (!SAFETY_LEVELS.has(action.safety))
|
|
165
|
+
errors.push(`${prefix}: \`safety\` must be one of read|write|financial|destructive|confidential`);
|
|
166
|
+
if (typeof action.agentSafe !== 'boolean')
|
|
167
|
+
errors.push(`${prefix}: \`agentSafe\` must be a boolean`);
|
|
168
|
+
if (!action.requiredAuth)
|
|
169
|
+
errors.push(`${prefix}: \`requiredAuth\` is missing`);
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
return errors;
|
|
173
|
+
}
|
|
174
|
+
program.parse();
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.DiscoveryService = void 0;
|
|
37
|
+
const fs = __importStar(require("fs"));
|
|
38
|
+
const path = __importStar(require("path"));
|
|
39
|
+
const crypto = __importStar(require("crypto"));
|
|
40
|
+
const tinyglobby_1 = require("tinyglobby");
|
|
41
|
+
const express_parser_1 = require("../parser/express-parser");
|
|
42
|
+
/** SHA-1 of a file's content — used for change detection caching. */
|
|
43
|
+
function fileHash(filePath) {
|
|
44
|
+
try {
|
|
45
|
+
return crypto.createHash('sha1').update(fs.readFileSync(filePath)).digest('hex');
|
|
46
|
+
}
|
|
47
|
+
catch {
|
|
48
|
+
return '';
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
const CACHE_FILE = '.agentjson-cache.json';
|
|
52
|
+
/** Glob negations applied to every pattern to keep node_modules and build artifacts out. */
|
|
53
|
+
const ALWAYS_EXCLUDE = [
|
|
54
|
+
'!**/node_modules/**',
|
|
55
|
+
'!**/.next/**',
|
|
56
|
+
'!**/dist/**',
|
|
57
|
+
'!**/.turbo/**',
|
|
58
|
+
'!**/.cache/**',
|
|
59
|
+
'!**/.git/**',
|
|
60
|
+
'!**/out/**',
|
|
61
|
+
'!**/build/**',
|
|
62
|
+
'!**/.vercel/**',
|
|
63
|
+
// Never scan env files — they may contain secrets
|
|
64
|
+
'!**/.env',
|
|
65
|
+
'!**/.env.*',
|
|
66
|
+
'!**/secrets/**',
|
|
67
|
+
'!**/credentials/**',
|
|
68
|
+
];
|
|
69
|
+
class DiscoveryService {
|
|
70
|
+
projectPath;
|
|
71
|
+
cache = {};
|
|
72
|
+
cachePath;
|
|
73
|
+
cacheModified = false;
|
|
74
|
+
constructor(projectPath) {
|
|
75
|
+
this.projectPath = projectPath;
|
|
76
|
+
this.cachePath = path.join(projectPath, CACHE_FILE);
|
|
77
|
+
this.loadCache();
|
|
78
|
+
}
|
|
79
|
+
loadCache() {
|
|
80
|
+
try {
|
|
81
|
+
this.cache = JSON.parse(fs.readFileSync(this.cachePath, 'utf8'));
|
|
82
|
+
}
|
|
83
|
+
catch {
|
|
84
|
+
this.cache = {};
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
saveCache() {
|
|
88
|
+
if (!this.cacheModified)
|
|
89
|
+
return;
|
|
90
|
+
try {
|
|
91
|
+
fs.writeFileSync(this.cachePath, JSON.stringify(this.cache, null, 2));
|
|
92
|
+
}
|
|
93
|
+
catch { /* ignore */ }
|
|
94
|
+
}
|
|
95
|
+
async findRelevantFiles() {
|
|
96
|
+
const relevantFiles = [];
|
|
97
|
+
// 0. Farcaster manifest (app identity / metadata)
|
|
98
|
+
const manifests = await (0, tinyglobby_1.glob)([
|
|
99
|
+
'.well-known/farcaster.json',
|
|
100
|
+
'public/.well-known/farcaster.json',
|
|
101
|
+
'**/public/.well-known/farcaster.json',
|
|
102
|
+
], { cwd: this.projectPath, absolute: true });
|
|
103
|
+
relevantFiles.push(...manifests);
|
|
104
|
+
// 0.5. ABI JSON files (smart contract definitions)
|
|
105
|
+
const abis = await (0, tinyglobby_1.glob)([
|
|
106
|
+
'**/*ABI.json',
|
|
107
|
+
'**/abi/*.json',
|
|
108
|
+
'**/abis/*.json',
|
|
109
|
+
'contracts/*.json',
|
|
110
|
+
'**/contracts/*.json',
|
|
111
|
+
...ALWAYS_EXCLUDE,
|
|
112
|
+
], { cwd: this.projectPath, absolute: true });
|
|
113
|
+
relevantFiles.push(...abis);
|
|
114
|
+
// 1. API routes — support monorepo layouts (apps/*/src/app/api, apps/*/pages/api, etc.)
|
|
115
|
+
const apiRoutes = await (0, tinyglobby_1.glob)([
|
|
116
|
+
// Next.js App Router
|
|
117
|
+
'**/app/api/**/*.{ts,js,tsx,jsx}',
|
|
118
|
+
// Next.js Pages Router
|
|
119
|
+
'**/pages/api/**/*.{ts,js,tsx,jsx}',
|
|
120
|
+
// Generic api/ folder
|
|
121
|
+
'**/api/**/*.{ts,js,tsx,jsx}',
|
|
122
|
+
...ALWAYS_EXCLUDE,
|
|
123
|
+
], { cwd: this.projectPath, absolute: true });
|
|
124
|
+
relevantFiles.push(...apiRoutes);
|
|
125
|
+
// 2. Scan all TS/TSX files for signal keywords
|
|
126
|
+
const allTsFiles = await (0, tinyglobby_1.glob)([
|
|
127
|
+
'**/*.{ts,tsx}',
|
|
128
|
+
...ALWAYS_EXCLUDE,
|
|
129
|
+
], { cwd: this.projectPath, absolute: true });
|
|
130
|
+
for (const file of allTsFiles) {
|
|
131
|
+
if (relevantFiles.includes(file))
|
|
132
|
+
continue;
|
|
133
|
+
const hash = fileHash(file);
|
|
134
|
+
const cached = this.cache[file];
|
|
135
|
+
// Cache hit: file unchanged, reuse previous relevance decision
|
|
136
|
+
if (cached && cached.hash === hash) {
|
|
137
|
+
if (cached.relevant)
|
|
138
|
+
relevantFiles.push(file);
|
|
139
|
+
continue;
|
|
140
|
+
}
|
|
141
|
+
// Cache miss: read and classify
|
|
142
|
+
const content = fs.readFileSync(file, 'utf8');
|
|
143
|
+
const relevant = content.includes('@agent-action') ||
|
|
144
|
+
content.includes('useWriteContract') ||
|
|
145
|
+
content.includes('useContractWrite') ||
|
|
146
|
+
content.includes('writeContract') ||
|
|
147
|
+
content.includes("'use server'") ||
|
|
148
|
+
content.includes('"use server"') ||
|
|
149
|
+
(0, express_parser_1.looksLikeRouteFile)(content);
|
|
150
|
+
this.cache[file] = { hash, relevant };
|
|
151
|
+
this.cacheModified = true;
|
|
152
|
+
if (relevant)
|
|
153
|
+
relevantFiles.push(file);
|
|
154
|
+
}
|
|
155
|
+
this.saveCache();
|
|
156
|
+
return Array.from(new Set(relevantFiles));
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
exports.DiscoveryService = DiscoveryService;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ManifestGenerator = void 0;
|
|
4
|
+
class ManifestGenerator {
|
|
5
|
+
generate(actions, metadata = {}, capabilities = [], auth = { type: 'none' }, version = '1.0.0') {
|
|
6
|
+
return {
|
|
7
|
+
name: metadata.name ?? 'Web App',
|
|
8
|
+
description: metadata.description ?? 'Auto-generated agent manifest',
|
|
9
|
+
version,
|
|
10
|
+
...(metadata.author && { author: metadata.author }),
|
|
11
|
+
...(metadata.url && { url: metadata.url }),
|
|
12
|
+
auth,
|
|
13
|
+
metadata: {
|
|
14
|
+
...(metadata.iconUrl && { iconUrl: metadata.iconUrl }),
|
|
15
|
+
...(metadata.homeUrl && { homeUrl: metadata.homeUrl }),
|
|
16
|
+
...(metadata.imageUrl && { imageUrl: metadata.imageUrl }),
|
|
17
|
+
...(metadata.splashImageUrl && { splashImageUrl: metadata.splashImageUrl }),
|
|
18
|
+
...(metadata.splashBackgroundColor && { splashBackgroundColor: metadata.splashBackgroundColor }),
|
|
19
|
+
},
|
|
20
|
+
capabilities,
|
|
21
|
+
actions,
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
exports.ManifestGenerator = ManifestGenerator;
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
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;
|
|
18
|
+
__exportStar(require("./types"), exports);
|
|
19
|
+
var service_1 = require("./discovery/service");
|
|
20
|
+
Object.defineProperty(exports, "DiscoveryService", { enumerable: true, get: function () { return service_1.DiscoveryService; } });
|
|
21
|
+
var ts_parser_1 = require("./parser/ts-parser");
|
|
22
|
+
Object.defineProperty(exports, "TSParser", { enumerable: true, get: function () { return ts_parser_1.TSParser; } });
|
|
23
|
+
var express_parser_1 = require("./parser/express-parser");
|
|
24
|
+
Object.defineProperty(exports, "ExpressParser", { enumerable: true, get: function () { return express_parser_1.ExpressParser; } });
|
|
25
|
+
Object.defineProperty(exports, "looksLikeRouteFile", { enumerable: true, get: function () { return express_parser_1.looksLikeRouteFile; } });
|
|
26
|
+
var contract_parser_1 = require("./parser/contract-parser");
|
|
27
|
+
Object.defineProperty(exports, "ContractParser", { enumerable: true, get: function () { return contract_parser_1.ContractParser; } });
|
|
28
|
+
var zod_extractor_1 = require("./parser/zod-extractor");
|
|
29
|
+
Object.defineProperty(exports, "ZodExtractor", { enumerable: true, get: function () { return zod_extractor_1.ZodExtractor; } });
|
|
30
|
+
var json_1 = require("./generator/json");
|
|
31
|
+
Object.defineProperty(exports, "ManifestGenerator", { enumerable: true, get: function () { return json_1.ManifestGenerator; } });
|
|
32
|
+
var auth_detector_1 = require("./parser/auth-detector");
|
|
33
|
+
Object.defineProperty(exports, "AuthDetector", { enumerable: true, get: function () { return auth_detector_1.AuthDetector; } });
|
|
34
|
+
var capability_detector_1 = require("./parser/capability-detector");
|
|
35
|
+
Object.defineProperty(exports, "CapabilityDetector", { enumerable: true, get: function () { return capability_detector_1.CapabilityDetector; } });
|
|
36
|
+
__exportStar(require("./parser/intent-classifier"), exports);
|