agent-permission 0.1.5 → 0.1.7
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 +28 -8
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +95 -3
- package/dist/install.d.ts +9 -1
- package/dist/install.d.ts.map +1 -1
- package/dist/install.js +117 -14
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -4,8 +4,20 @@ Give AI agents a permission gate before they touch external URLs.
|
|
|
4
4
|
|
|
5
5
|
## Quickstart
|
|
6
6
|
|
|
7
|
+
For one personal Codex setup that works across repositories:
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npx -y agent-permission@latest install --adapter codex
|
|
11
|
+
node "$HOME/.config/agent-permission/agent-permission.mjs" config set api-key <YOUR_API_KEY>
|
|
12
|
+
node "$HOME/.config/agent-permission/agent-permission.mjs" config doctor
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
`install --adapter codex` defaults to user scope, writes personal Codex instructions under your Agent Permission config directory, and installs a reusable runner there. The install output prints the exact runner command for your machine.
|
|
16
|
+
|
|
17
|
+
For a repository-owned instruction block:
|
|
18
|
+
|
|
7
19
|
```bash
|
|
8
|
-
npx agent-permission install --adapter auto
|
|
20
|
+
npx -y agent-permission@latest install --adapter auto --scope project
|
|
9
21
|
```
|
|
10
22
|
|
|
11
23
|
This adds an Agent Permission block to the local agent instruction file, such as `AGENTS.md`, `CLAUDE.md`, or `agent-permission.chatgpt.md`, depending on the selected adapter. It also writes a tiny project-local runner at `.agent-permission/agent-permission.mjs` so repeated checks do not need npm registry access.
|
|
@@ -13,14 +25,20 @@ This adds an Agent Permission block to the local agent instruction file, such as
|
|
|
13
25
|
To install instructions and configure live checks in one step:
|
|
14
26
|
|
|
15
27
|
```bash
|
|
16
|
-
npx agent-permission install --adapter
|
|
28
|
+
npx -y agent-permission@latest install --adapter codex --api-key <YOUR_API_KEY>
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
To force a project-local Codex install instead:
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
npx -y agent-permission@latest install --adapter codex --scope project
|
|
17
35
|
```
|
|
18
36
|
|
|
19
37
|
Add an API key when you want live checks from the command line, CI, or agent runs:
|
|
20
38
|
|
|
21
39
|
```bash
|
|
22
|
-
node
|
|
23
|
-
node
|
|
40
|
+
node "$HOME/.config/agent-permission/agent-permission.mjs" config set api-key <YOUR_API_KEY>
|
|
41
|
+
node "$HOME/.config/agent-permission/agent-permission.mjs" check https://example.com/blog-post --action summarize --exit-code
|
|
24
42
|
```
|
|
25
43
|
|
|
26
44
|
## What the Agent Runs
|
|
@@ -28,22 +46,24 @@ node .agent-permission/agent-permission.mjs check https://example.com/blog-post
|
|
|
28
46
|
After install, project instructions tell compatible agents to run:
|
|
29
47
|
|
|
30
48
|
```bash
|
|
31
|
-
node
|
|
49
|
+
node <installed-runner-path> check <url> --action <crawl|summarize|extract|train|transact|post> --exit-code
|
|
32
50
|
```
|
|
33
51
|
|
|
34
|
-
If the
|
|
52
|
+
If the runner is missing, the installed instructions tell the agent to stop and ask you to repair the installation once:
|
|
35
53
|
|
|
36
54
|
```bash
|
|
37
|
-
npx -y agent-permission@latest
|
|
55
|
+
npx -y agent-permission@latest install --adapter codex --scope user
|
|
38
56
|
```
|
|
39
57
|
|
|
58
|
+
If the API key is missing, the generated instructions tell the agent to stop and ask you to run the exact `config set api-key` command for that installed runner. The generated preflight block does not tell agents to run `npx ... check` during a URL request, so repeated checks do not need registry access or unpinned-code approval.
|
|
59
|
+
|
|
40
60
|
## Commands
|
|
41
61
|
|
|
42
62
|
```bash
|
|
43
63
|
agent-permission check <url> --action <crawl|summarize|extract|train|transact|post>
|
|
44
64
|
agent-permission batch --file checks.json
|
|
45
65
|
agent-permission verify-receipt --file receipt.json
|
|
46
|
-
agent-permission install --adapter auto|codex|claude|chatgpt|openclaw [--api-key <key>]
|
|
66
|
+
agent-permission install --adapter auto|codex|claude|chatgpt|openclaw [--scope project|user] [--api-key <key>]
|
|
47
67
|
agent-permission config set api-key <key>
|
|
48
68
|
agent-permission config doctor
|
|
49
69
|
```
|
package/dist/cli.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"AAKA,OAAO,EAAyD,KAAK,EAAuD,OAAO,EAAE,MAAM,YAAY,CAAC;AAExJ,UAAU,WAAW;IACnB,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,CAAC;IACxC,WAAW,EAAE,MAAM,EAAE,CAAC;CACvB;AAED,wBAAsB,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,OAAO,EAAE,OAAO,EAAE,EAAE,EAAE,KAAK,GAAG,OAAO,CAAC,MAAM,CAAC,CA6BzF;
|
|
1
|
+
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"AAKA,OAAO,EAAyD,KAAK,EAAuD,OAAO,EAAE,MAAM,YAAY,CAAC;AAExJ,UAAU,WAAW;IACnB,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,CAAC;IACxC,WAAW,EAAE,MAAM,EAAE,CAAC;CACvB;AAED,wBAAsB,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,OAAO,EAAE,OAAO,EAAE,EAAE,EAAE,KAAK,GAAG,OAAO,CAAC,MAAM,CAAC,CA6BzF;AA6ID,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,WAAW,CA2BtD"}
|
package/dist/cli.js
CHANGED
|
@@ -8,7 +8,7 @@ export async function runCli(argv, runtime, io) {
|
|
|
8
8
|
const invoked = argv[1] || 'agent-permission';
|
|
9
9
|
const args = argv.slice(2);
|
|
10
10
|
if (isSkillsInvocation(invoked, args)) {
|
|
11
|
-
return runCli([argv[0], argv[1], 'install', '--adapter', '
|
|
11
|
+
return runCli([argv[0], argv[1], 'install', '--adapter', 'codex', '--scope', 'user'], runtime, io);
|
|
12
12
|
}
|
|
13
13
|
const command = args[0];
|
|
14
14
|
const rest = args.slice(1);
|
|
@@ -42,7 +42,14 @@ async function runCheck(args, runtime, io) {
|
|
|
42
42
|
if (!url || !action) {
|
|
43
43
|
throw new Error('Usage: agent-permission check <url> --action <crawl|summarize|extract|train|transact|post>');
|
|
44
44
|
}
|
|
45
|
+
const localDecision = localUrlDecision(url, action);
|
|
46
|
+
if (localDecision) {
|
|
47
|
+
const format = outputFormat(parsed);
|
|
48
|
+
io.stdout(format === 'json' ? JSON.stringify(localDecision, null, 2) : formatCheckHuman(localDecision));
|
|
49
|
+
return parsed.flags['exit-code'] ? decisionExitCode(localDecision.decision) : 0;
|
|
50
|
+
}
|
|
45
51
|
const config = await resolveConfig(runtime);
|
|
52
|
+
ensureApiKeyConfigured(config.apiKey, invokedRunnerCommand(runtime));
|
|
46
53
|
const params = {
|
|
47
54
|
url,
|
|
48
55
|
action,
|
|
@@ -66,6 +73,7 @@ async function runBatch(args, runtime, io) {
|
|
|
66
73
|
throw new Error('Usage: agent-permission batch --file checks.json');
|
|
67
74
|
}
|
|
68
75
|
const config = await resolveConfig(runtime);
|
|
76
|
+
ensureApiKeyConfigured(config.apiKey, invokedRunnerCommand(runtime));
|
|
69
77
|
const batch = JSON.parse(await readFile(file, 'utf8'));
|
|
70
78
|
const result = await batchCheckPermissions(batch, {
|
|
71
79
|
apiUrl: stringFlag(parsed, 'api-url') || config.apiUrl,
|
|
@@ -98,11 +106,12 @@ async function runVerify(args, runtime, io) {
|
|
|
98
106
|
async function runInstall(args, runtime, io) {
|
|
99
107
|
const parsed = parseFlags(args);
|
|
100
108
|
const adapter = parseAdapter(stringFlag(parsed, 'adapter') || parsed.positionals[0] || 'auto');
|
|
101
|
-
const scope = parseScope(stringFlag(parsed, 'scope') ||
|
|
109
|
+
const scope = parseScope(stringFlag(parsed, 'scope') || defaultInstallScope(adapter));
|
|
102
110
|
const result = await installAgentInstructions(adapter, scope, runtime);
|
|
103
111
|
const messages = [
|
|
104
112
|
`Installed Agent Permission instructions for ${result.adapter} at ${result.path}`,
|
|
105
113
|
`Installed local Agent Permission runner at ${result.runnerPath}`,
|
|
114
|
+
`Agent preflight command: ${result.checkCommand}`,
|
|
106
115
|
];
|
|
107
116
|
const apiKey = stringFlag(parsed, 'api-key');
|
|
108
117
|
if (apiKey) {
|
|
@@ -113,7 +122,7 @@ async function runInstall(args, runtime, io) {
|
|
|
113
122
|
else {
|
|
114
123
|
const config = await resolveConfig(runtime);
|
|
115
124
|
if (!config.apiKey) {
|
|
116
|
-
messages.push(`API key is not configured. Live checks will fail until you run \`${result.
|
|
125
|
+
messages.push(`API key is not configured. Live checks will fail until you run \`${result.apiKeyCommand}\` or set AGENT_PERMISSION_API_KEY.`, `Verify setup with \`${result.doctorCommand}\`.`);
|
|
117
126
|
}
|
|
118
127
|
}
|
|
119
128
|
io.stdout(messages.join('\n'));
|
|
@@ -197,6 +206,89 @@ function parseScope(value) {
|
|
|
197
206
|
return value;
|
|
198
207
|
throw new Error('Scope must be project or user.');
|
|
199
208
|
}
|
|
209
|
+
function ensureApiKeyConfigured(apiKey, runnerCommand) {
|
|
210
|
+
if (!apiKey) {
|
|
211
|
+
throw new Error(`Missing API key. Set AGENT_PERMISSION_API_KEY or run \`${runnerCommand} config set api-key <key>\`.`);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
function invokedRunnerCommand(runtime) {
|
|
215
|
+
return runtime.env.AGENT_PERMISSION_RUNNER_COMMAND || 'agent-permission';
|
|
216
|
+
}
|
|
217
|
+
function defaultInstallScope(adapter) {
|
|
218
|
+
return adapter === 'codex' ? 'user' : 'project';
|
|
219
|
+
}
|
|
220
|
+
function localUrlDecision(url, action) {
|
|
221
|
+
const localKind = localUrlKind(url);
|
|
222
|
+
if (!localKind)
|
|
223
|
+
return undefined;
|
|
224
|
+
const issuedAt = new Date().toISOString();
|
|
225
|
+
return {
|
|
226
|
+
id: 'local_url_check',
|
|
227
|
+
decision: 'allow',
|
|
228
|
+
confidence: 'high',
|
|
229
|
+
risk: 'low',
|
|
230
|
+
reasons: [
|
|
231
|
+
`${localKind} URL was not sent to the Agent Permission API; local development and in-app browser URLs are allowed without external preflight.`,
|
|
232
|
+
],
|
|
233
|
+
sources: [
|
|
234
|
+
{
|
|
235
|
+
type: 'local_url',
|
|
236
|
+
url,
|
|
237
|
+
fetched_at: issuedAt,
|
|
238
|
+
evidence: localKind,
|
|
239
|
+
},
|
|
240
|
+
],
|
|
241
|
+
receipt: {
|
|
242
|
+
receipt_id: 'local_only_no_external_receipt',
|
|
243
|
+
check_id: 'local_url_check',
|
|
244
|
+
url_hash: 'local_only',
|
|
245
|
+
action,
|
|
246
|
+
decision: 'allow',
|
|
247
|
+
source_hashes: [],
|
|
248
|
+
issued_at: issuedAt,
|
|
249
|
+
account_id: 'local',
|
|
250
|
+
signature_alg: 'none',
|
|
251
|
+
signature: 'not_signed',
|
|
252
|
+
},
|
|
253
|
+
};
|
|
254
|
+
}
|
|
255
|
+
function localUrlKind(value) {
|
|
256
|
+
let parsed;
|
|
257
|
+
try {
|
|
258
|
+
parsed = new URL(value);
|
|
259
|
+
}
|
|
260
|
+
catch {
|
|
261
|
+
return undefined;
|
|
262
|
+
}
|
|
263
|
+
if (parsed.protocol !== 'http:' && parsed.protocol !== 'https:')
|
|
264
|
+
return undefined;
|
|
265
|
+
const hostname = parsed.hostname.toLowerCase().replace(/^\[|\]$/g, '');
|
|
266
|
+
if (hostname === 'localhost' || hostname.endsWith('.localhost'))
|
|
267
|
+
return 'localhost';
|
|
268
|
+
if (hostname === '::1')
|
|
269
|
+
return 'loopback';
|
|
270
|
+
if (hostname.startsWith('127.'))
|
|
271
|
+
return 'loopback';
|
|
272
|
+
if (hostname === '0.0.0.0')
|
|
273
|
+
return 'local bind address';
|
|
274
|
+
if (hostname.endsWith('.local'))
|
|
275
|
+
return 'local network';
|
|
276
|
+
const parts = hostname.split('.').map((part) => Number(part));
|
|
277
|
+
if (parts.length === 4 && parts.every((part) => Number.isInteger(part) && part >= 0 && part <= 255)) {
|
|
278
|
+
const [first, second] = parts;
|
|
279
|
+
if (first === 10)
|
|
280
|
+
return 'private network';
|
|
281
|
+
if (first === 172 && second >= 16 && second <= 31)
|
|
282
|
+
return 'private network';
|
|
283
|
+
if (first === 192 && second === 168)
|
|
284
|
+
return 'private network';
|
|
285
|
+
if (first === 169 && second === 254)
|
|
286
|
+
return 'link-local';
|
|
287
|
+
}
|
|
288
|
+
if (hostname.startsWith('fc') || hostname.startsWith('fd') || hostname.startsWith('fe80:'))
|
|
289
|
+
return 'private network';
|
|
290
|
+
return undefined;
|
|
291
|
+
}
|
|
200
292
|
function isSkillsInvocation(invoked, args) {
|
|
201
293
|
const name = invoked.split('/').pop() || invoked;
|
|
202
294
|
return name === 'skills' && args[0] === 'add' && args[1] === 'agent-permission';
|
package/dist/install.d.ts
CHANGED
|
@@ -4,8 +4,16 @@ export interface InstallResult {
|
|
|
4
4
|
path: string;
|
|
5
5
|
runnerPath: string;
|
|
6
6
|
runnerCommand: string;
|
|
7
|
+
checkCommand: string;
|
|
8
|
+
apiKeyCommand: string;
|
|
9
|
+
doctorCommand: string;
|
|
10
|
+
repairCommand: string;
|
|
7
11
|
}
|
|
8
12
|
export declare function installAgentInstructions(adapter: Adapter, scope: InstallScope, runtime: Runtime): Promise<InstallResult>;
|
|
9
13
|
export declare function upsertBlock(existing: string, blockBody: string): string;
|
|
10
|
-
export declare function buildInstructionBlock(adapter: Exclude<Adapter, 'auto'>,
|
|
14
|
+
export declare function buildInstructionBlock(adapter: Exclude<Adapter, 'auto'>, commands?: {
|
|
15
|
+
checkCommand?: string;
|
|
16
|
+
apiKeyCommand?: string;
|
|
17
|
+
repairCommand?: string;
|
|
18
|
+
}): string;
|
|
11
19
|
//# sourceMappingURL=install.d.ts.map
|
package/dist/install.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"install.d.ts","sourceRoot":"","sources":["../src/install.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,OAAO,EAAmB,YAAY,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AAM7E,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,OAAO,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;IAClC,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,MAAM,CAAC;IACnB,aAAa,EAAE,MAAM,CAAC;CACvB;AAED,wBAAsB,wBAAwB,CAAC,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,YAAY,EAAE,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,aAAa,CAAC,
|
|
1
|
+
{"version":3,"file":"install.d.ts","sourceRoot":"","sources":["../src/install.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,OAAO,EAAmB,YAAY,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AAM7E,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,OAAO,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;IAClC,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,MAAM,CAAC;IACnB,aAAa,EAAE,MAAM,CAAC;IACtB,YAAY,EAAE,MAAM,CAAC;IACrB,aAAa,EAAE,MAAM,CAAC;IACtB,aAAa,EAAE,MAAM,CAAC;IACtB,aAAa,EAAE,MAAM,CAAC;CACvB;AAED,wBAAsB,wBAAwB,CAAC,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,YAAY,EAAE,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,aAAa,CAAC,CAiC9H;AAED,wBAAgB,WAAW,CAAC,QAAQ,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,UAU9D;AAED,wBAAgB,qBAAqB,CACnC,OAAO,EAAE,OAAO,CAAC,OAAO,EAAE,MAAM,CAAC,EACjC,QAAQ,GAAE;IACR,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,aAAa,CAAC,EAAE,MAAM,CAAC;CACnB,UAqCP"}
|
package/dist/install.js
CHANGED
|
@@ -9,16 +9,33 @@ export async function installAgentInstructions(adapter, scope, runtime) {
|
|
|
9
9
|
const path = getInstructionPath(resolved, scope, runtime);
|
|
10
10
|
const runner = getRunnerPath(scope, runtime);
|
|
11
11
|
const runnerCommand = getRunnerCommand(scope, runner);
|
|
12
|
+
const repairCommand = getRepairCommand(resolved, scope);
|
|
13
|
+
const checkCommand = `${runnerCommand} check <url> --action <crawl|summarize|extract|train|transact|post> --exit-code`;
|
|
14
|
+
const apiKeyCommand = `${runnerCommand} config set api-key <YOUR_API_KEY>`;
|
|
15
|
+
const doctorCommand = `${runnerCommand} config doctor`;
|
|
12
16
|
const existing = await readFile(path, 'utf8').catch((err) => {
|
|
13
17
|
if (err && typeof err === 'object' && 'code' in err && err.code === 'ENOENT')
|
|
14
18
|
return '';
|
|
15
19
|
throw err;
|
|
16
20
|
});
|
|
17
|
-
const next = upsertBlock(existing, buildInstructionBlock(resolved,
|
|
21
|
+
const next = upsertBlock(existing, buildInstructionBlock(resolved, {
|
|
22
|
+
checkCommand,
|
|
23
|
+
apiKeyCommand,
|
|
24
|
+
repairCommand,
|
|
25
|
+
}));
|
|
18
26
|
await mkdir(dirname(path), { recursive: true });
|
|
19
|
-
await writeLocalRunner(runner);
|
|
27
|
+
await writeLocalRunner(runner, runnerCommand);
|
|
20
28
|
await writeFile(path, next);
|
|
21
|
-
return {
|
|
29
|
+
return {
|
|
30
|
+
adapter: resolved,
|
|
31
|
+
path,
|
|
32
|
+
runnerPath: runner,
|
|
33
|
+
runnerCommand,
|
|
34
|
+
checkCommand,
|
|
35
|
+
apiKeyCommand,
|
|
36
|
+
doctorCommand,
|
|
37
|
+
repairCommand,
|
|
38
|
+
};
|
|
22
39
|
}
|
|
23
40
|
export function upsertBlock(existing, blockBody) {
|
|
24
41
|
const block = `${BLOCK_START}\n${blockBody.trim()}\n${BLOCK_END}`;
|
|
@@ -29,13 +46,16 @@ export function upsertBlock(existing, blockBody) {
|
|
|
29
46
|
const prefix = existing.trimEnd();
|
|
30
47
|
return prefix ? `${prefix}\n\n${block}\n` : `${block}\n`;
|
|
31
48
|
}
|
|
32
|
-
export function buildInstructionBlock(adapter,
|
|
49
|
+
export function buildInstructionBlock(adapter, commands = {}) {
|
|
33
50
|
const agentName = {
|
|
34
51
|
codex: 'Codex',
|
|
35
52
|
claude: 'Claude',
|
|
36
53
|
chatgpt: 'ChatGPT',
|
|
37
54
|
openclaw: 'OpenClaw',
|
|
38
55
|
}[adapter];
|
|
56
|
+
const checkCommand = commands.checkCommand || `node ${PROJECT_RUNNER_PATH} check <url> --action <crawl|summarize|extract|train|transact|post> --exit-code`;
|
|
57
|
+
const apiKeyCommand = commands.apiKeyCommand || `node ${PROJECT_RUNNER_PATH} config set api-key <YOUR_API_KEY>`;
|
|
58
|
+
const repairCommand = commands.repairCommand || `npx -y agent-permission@latest install --adapter ${adapter} --scope project`;
|
|
39
59
|
return `# Agent Permission
|
|
40
60
|
|
|
41
61
|
These instructions equip ${agentName} with Agent Permission preflight checks.
|
|
@@ -43,13 +63,19 @@ These instructions equip ${agentName} with Agent Permission preflight checks.
|
|
|
43
63
|
Before crawling, summarizing, extracting, training on, transacting with, or posting to a URL, run:
|
|
44
64
|
|
|
45
65
|
\`\`\`bash
|
|
46
|
-
${
|
|
66
|
+
${checkCommand}
|
|
67
|
+
\`\`\`
|
|
68
|
+
|
|
69
|
+
Do not use npm while handling an individual URL request. If the runner command is missing or cannot start, stop and ask the user to repair the installation once with:
|
|
70
|
+
|
|
71
|
+
\`\`\`bash
|
|
72
|
+
${repairCommand}
|
|
47
73
|
\`\`\`
|
|
48
74
|
|
|
49
|
-
If the
|
|
75
|
+
If the runner reports that the API key is missing, stop and ask the user to configure it with:
|
|
50
76
|
|
|
51
77
|
\`\`\`bash
|
|
52
|
-
|
|
78
|
+
${apiKeyCommand}
|
|
53
79
|
\`\`\`
|
|
54
80
|
|
|
55
81
|
Use \`crawl\` for fetching or indexing pages, \`summarize\` for compacting page content, \`extract\` for structured data pulls, \`train\` for model training or dataset creation, \`transact\` for purchase/account-changing actions, and \`post\` for creating or modifying remote content.
|
|
@@ -97,9 +123,12 @@ function getRunnerCommand(scope, runnerPath) {
|
|
|
97
123
|
}
|
|
98
124
|
return `node ${PROJECT_RUNNER_PATH}`;
|
|
99
125
|
}
|
|
100
|
-
|
|
126
|
+
function getRepairCommand(adapter, scope) {
|
|
127
|
+
return `npx -y agent-permission@latest install --adapter ${adapter} --scope ${scope}`;
|
|
128
|
+
}
|
|
129
|
+
async function writeLocalRunner(path, runnerCommand) {
|
|
101
130
|
await mkdir(dirname(path), { recursive: true });
|
|
102
|
-
await writeFile(path, buildLocalRunner(), { mode: 0o644 });
|
|
131
|
+
await writeFile(path, buildLocalRunner(runnerCommand), { mode: 0o644 });
|
|
103
132
|
}
|
|
104
133
|
function shellQuote(value) {
|
|
105
134
|
return `'${value.replace(/'/g, `'\\''`)}'`;
|
|
@@ -123,13 +152,14 @@ function isOpenClawInstructions(content) {
|
|
|
123
152
|
function escapeRegExp(value) {
|
|
124
153
|
return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
125
154
|
}
|
|
126
|
-
function buildLocalRunner() {
|
|
155
|
+
function buildLocalRunner(runnerCommand = `node ${PROJECT_RUNNER_PATH}`) {
|
|
127
156
|
return `#!/usr/bin/env node
|
|
128
157
|
import { mkdir, readFile, writeFile } from 'node:fs/promises';
|
|
129
158
|
import { dirname, join } from 'node:path';
|
|
130
159
|
import { homedir } from 'node:os';
|
|
131
160
|
|
|
132
161
|
const DEFAULT_API_URL = '${DEFAULT_API_URL}';
|
|
162
|
+
const RUNNER_COMMAND = ${JSON.stringify(runnerCommand)};
|
|
133
163
|
const ACTIONS = ['crawl', 'summarize', 'extract', 'train', 'transact', 'post'];
|
|
134
164
|
|
|
135
165
|
const code = await main(process.argv.slice(2)).catch((err) => {
|
|
@@ -163,6 +193,12 @@ async function runCheck(args) {
|
|
|
163
193
|
throw new Error('Usage: agent-permission check <url> --action <crawl|summarize|extract|train|transact|post>');
|
|
164
194
|
}
|
|
165
195
|
|
|
196
|
+
const localDecision = localUrlDecision(url, action);
|
|
197
|
+
if (localDecision) {
|
|
198
|
+
console.log(parsed.flags.json ? JSON.stringify(localDecision, null, 2) : formatCheckHuman(localDecision));
|
|
199
|
+
return parsed.flags['exit-code'] ? decisionExitCode(localDecision.decision) : 0;
|
|
200
|
+
}
|
|
201
|
+
|
|
166
202
|
const config = await resolveConfig();
|
|
167
203
|
const result = await checkPermission({
|
|
168
204
|
url,
|
|
@@ -211,7 +247,7 @@ async function runConfig(args) {
|
|
|
211
247
|
|
|
212
248
|
async function checkPermission(params, options) {
|
|
213
249
|
if (!options.apiKey) {
|
|
214
|
-
throw new Error(
|
|
250
|
+
throw new Error(\`Missing API key. Set AGENT_PERMISSION_API_KEY or run \\\`\${RUNNER_COMMAND} config set api-key <key>\\\`.\`);
|
|
215
251
|
}
|
|
216
252
|
|
|
217
253
|
const apiUrl = trimSlash(options.apiUrl);
|
|
@@ -322,6 +358,73 @@ function decisionExitCode(decision) {
|
|
|
322
358
|
return 1;
|
|
323
359
|
}
|
|
324
360
|
|
|
361
|
+
function localUrlDecision(url, action) {
|
|
362
|
+
const localKind = localUrlKind(url);
|
|
363
|
+
if (!localKind) return undefined;
|
|
364
|
+
|
|
365
|
+
const issuedAt = new Date().toISOString();
|
|
366
|
+
return {
|
|
367
|
+
id: 'local_url_check',
|
|
368
|
+
decision: 'allow',
|
|
369
|
+
confidence: 'high',
|
|
370
|
+
risk: 'low',
|
|
371
|
+
reasons: [
|
|
372
|
+
\`\${localKind} URL was not sent to the Agent Permission API; local development and in-app browser URLs are allowed without external preflight.\`,
|
|
373
|
+
],
|
|
374
|
+
sources: [
|
|
375
|
+
{
|
|
376
|
+
type: 'local_url',
|
|
377
|
+
url,
|
|
378
|
+
fetched_at: issuedAt,
|
|
379
|
+
evidence: localKind,
|
|
380
|
+
},
|
|
381
|
+
],
|
|
382
|
+
receipt: {
|
|
383
|
+
receipt_id: 'local_only_no_external_receipt',
|
|
384
|
+
check_id: 'local_url_check',
|
|
385
|
+
url_hash: 'local_only',
|
|
386
|
+
action,
|
|
387
|
+
decision: 'allow',
|
|
388
|
+
source_hashes: [],
|
|
389
|
+
issued_at: issuedAt,
|
|
390
|
+
account_id: 'local',
|
|
391
|
+
signature_alg: 'none',
|
|
392
|
+
signature: 'not_signed',
|
|
393
|
+
},
|
|
394
|
+
};
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
function localUrlKind(value) {
|
|
398
|
+
let parsed;
|
|
399
|
+
try {
|
|
400
|
+
parsed = new URL(value);
|
|
401
|
+
} catch {
|
|
402
|
+
return undefined;
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
if (parsed.protocol !== 'http:' && parsed.protocol !== 'https:') return undefined;
|
|
406
|
+
|
|
407
|
+
const hostname = parsed.hostname.toLowerCase().replace(/^\\[|\\]$/g, '');
|
|
408
|
+
if (hostname === 'localhost' || hostname.endsWith('.localhost')) return 'localhost';
|
|
409
|
+
if (hostname === '::1') return 'loopback';
|
|
410
|
+
if (hostname.startsWith('127.')) return 'loopback';
|
|
411
|
+
if (hostname === '0.0.0.0') return 'local bind address';
|
|
412
|
+
if (hostname.endsWith('.local')) return 'local network';
|
|
413
|
+
|
|
414
|
+
const parts = hostname.split('.').map((part) => Number(part));
|
|
415
|
+
if (parts.length === 4 && parts.every((part) => Number.isInteger(part) && part >= 0 && part <= 255)) {
|
|
416
|
+
const [first, second] = parts;
|
|
417
|
+
if (first === 10) return 'private network';
|
|
418
|
+
if (first === 172 && second >= 16 && second <= 31) return 'private network';
|
|
419
|
+
if (first === 192 && second === 168) return 'private network';
|
|
420
|
+
if (first === 169 && second === 254) return 'link-local';
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
if (hostname.startsWith('fc') || hostname.startsWith('fd') || hostname.startsWith('fe80:')) return 'private network';
|
|
424
|
+
|
|
425
|
+
return undefined;
|
|
426
|
+
}
|
|
427
|
+
|
|
325
428
|
function getConfigPath() {
|
|
326
429
|
if (process.env.AGENT_PERMISSION_CONFIG) return process.env.AGENT_PERMISSION_CONFIG;
|
|
327
430
|
|
|
@@ -380,9 +483,9 @@ function helpText() {
|
|
|
380
483
|
return \`Agent Permission local runner
|
|
381
484
|
|
|
382
485
|
Usage:
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
486
|
+
\${RUNNER_COMMAND} check <url> --action <action> [--json] [--exit-code]
|
|
487
|
+
\${RUNNER_COMMAND} config set api-key <key>
|
|
488
|
+
\${RUNNER_COMMAND} config doctor
|
|
386
489
|
|
|
387
490
|
Actions:
|
|
388
491
|
crawl, summarize, extract, train, transact, post
|