aamp-acp-bridge 0.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +50 -0
- package/dist/acpx-client.d.ts +42 -0
- package/dist/acpx-client.js +97 -0
- package/dist/acpx-client.js.map +1 -0
- package/dist/agent-bridge.d.ts +46 -0
- package/dist/agent-bridge.js +278 -0
- package/dist/agent-bridge.js.map +1 -0
- package/dist/bridge.d.ts +21 -0
- package/dist/bridge.js +66 -0
- package/dist/bridge.js.map +1 -0
- package/dist/cli/init.d.ts +1 -0
- package/dist/cli/init.js +161 -0
- package/dist/cli/init.js.map +1 -0
- package/dist/config.d.ts +75 -0
- package/dist/config.js +23 -0
- package/dist/config.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +96 -0
- package/dist/index.js.map +1 -0
- package/dist/prompt-builder.d.ts +15 -0
- package/dist/prompt-builder.js +51 -0
- package/dist/prompt-builder.js.map +1 -0
- package/package.json +34 -0
package/README.md
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# @byted-meego/aamp-acp-bridge
|
|
2
|
+
|
|
3
|
+
Config-driven bridge that connects ACP-compatible agents to the AAMP email network.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @byted-meego/aamp-acp-bridge
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Usage
|
|
12
|
+
|
|
13
|
+
Initialize a config file:
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
npx --registry=https://bnpm.byted.org @byted-meego/aamp-acp-bridge init
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
Start the bridge:
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
npx --registry=https://bnpm.byted.org @byted-meego/aamp-acp-bridge start --config bridge.json
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
By default, agent credentials are stored under `~/.acp-bridge/`.
|
|
26
|
+
|
|
27
|
+
## Config
|
|
28
|
+
|
|
29
|
+
Minimal example:
|
|
30
|
+
|
|
31
|
+
```json
|
|
32
|
+
{
|
|
33
|
+
"aampHost": "https://meshmail.ai",
|
|
34
|
+
"rejectUnauthorized": false,
|
|
35
|
+
"agents": [
|
|
36
|
+
{
|
|
37
|
+
"name": "claude",
|
|
38
|
+
"acpCommand": "claude",
|
|
39
|
+
"slug": "claude-bridge",
|
|
40
|
+
"credentialsFile": "~/.acp-bridge/.aamp-claude.json",
|
|
41
|
+
"senderWhitelist": [
|
|
42
|
+
"system@aamp.local"
|
|
43
|
+
]
|
|
44
|
+
}
|
|
45
|
+
]
|
|
46
|
+
}
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
`senderWhitelist` is optional. If configured, the bridge only accepts tasks from those email addresses.
|
|
50
|
+
`credentialsFile` is optional. If omitted, the bridge uses `~/.acp-bridge/.aamp-<agent>.json`.
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
export interface AcpEvent {
|
|
2
|
+
eventVersion?: number;
|
|
3
|
+
sessionId?: string;
|
|
4
|
+
requestId?: string;
|
|
5
|
+
seq?: number;
|
|
6
|
+
type?: string;
|
|
7
|
+
content?: string;
|
|
8
|
+
[key: string]: unknown;
|
|
9
|
+
}
|
|
10
|
+
export interface AcpResult {
|
|
11
|
+
output: string;
|
|
12
|
+
events: AcpEvent[];
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Wrapper around acpx CLI.
|
|
16
|
+
* Invokes acpx as a subprocess and parses NDJSON output.
|
|
17
|
+
*/
|
|
18
|
+
export declare class AcpxClient {
|
|
19
|
+
private cwd;
|
|
20
|
+
constructor(cwd?: string);
|
|
21
|
+
/**
|
|
22
|
+
* Ensure a named ACP session exists for the given agent.
|
|
23
|
+
*/
|
|
24
|
+
ensureSession(agent: string, sessionName: string): Promise<string>;
|
|
25
|
+
/**
|
|
26
|
+
* Send a prompt to an ACP agent and wait for completion.
|
|
27
|
+
* Collects all stdout + stderr output and extracts the agent's response.
|
|
28
|
+
*/
|
|
29
|
+
prompt(agent: string, sessionName: string, text: string): Promise<AcpResult>;
|
|
30
|
+
/**
|
|
31
|
+
* Cancel the current operation in a session.
|
|
32
|
+
*/
|
|
33
|
+
cancel(agent: string, sessionName: string): Promise<void>;
|
|
34
|
+
/**
|
|
35
|
+
* Close a session.
|
|
36
|
+
*/
|
|
37
|
+
close(agent: string, sessionName: string): Promise<void>;
|
|
38
|
+
/**
|
|
39
|
+
* Execute an acpx command and return stdout.
|
|
40
|
+
*/
|
|
41
|
+
private exec;
|
|
42
|
+
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { spawn } from 'node:child_process';
|
|
2
|
+
/**
|
|
3
|
+
* Wrapper around acpx CLI.
|
|
4
|
+
* Invokes acpx as a subprocess and parses NDJSON output.
|
|
5
|
+
*/
|
|
6
|
+
export class AcpxClient {
|
|
7
|
+
cwd;
|
|
8
|
+
constructor(cwd) {
|
|
9
|
+
this.cwd = cwd ?? process.cwd();
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Ensure a named ACP session exists for the given agent.
|
|
13
|
+
*/
|
|
14
|
+
async ensureSession(agent, sessionName) {
|
|
15
|
+
const result = await this.exec(agent, ['sessions', 'ensure', '--name', sessionName]);
|
|
16
|
+
// Try to extract sessionId from the JSON output
|
|
17
|
+
try {
|
|
18
|
+
const data = JSON.parse(result.trim().split('\n').pop() ?? '{}');
|
|
19
|
+
return data.sessionId ?? sessionName;
|
|
20
|
+
}
|
|
21
|
+
catch {
|
|
22
|
+
return sessionName;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Send a prompt to an ACP agent and wait for completion.
|
|
27
|
+
* Collects all stdout + stderr output and extracts the agent's response.
|
|
28
|
+
*/
|
|
29
|
+
async prompt(agent, sessionName, text) {
|
|
30
|
+
const events = [];
|
|
31
|
+
return new Promise((resolve, reject) => {
|
|
32
|
+
// Use text format (default) — most reliable across acpx versions.
|
|
33
|
+
// --approve-all prevents interactive permission prompts from blocking.
|
|
34
|
+
const proc = spawn('acpx', ['--approve-all', agent, 'prompt', '-s', sessionName, text], {
|
|
35
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
36
|
+
cwd: this.cwd,
|
|
37
|
+
env: { ...process.env },
|
|
38
|
+
});
|
|
39
|
+
let stdout = '';
|
|
40
|
+
let stderr = '';
|
|
41
|
+
proc.stdout.on('data', (chunk) => { stdout += chunk.toString(); });
|
|
42
|
+
proc.stderr.on('data', (chunk) => { stderr += chunk.toString(); });
|
|
43
|
+
proc.on('close', (code) => {
|
|
44
|
+
// acpx text mode writes the agent's response to stdout.
|
|
45
|
+
// Try to extract meaningful output from stdout, falling back to stderr.
|
|
46
|
+
const output = stdout.trim() || stderr.trim();
|
|
47
|
+
if (code !== 0 && !output) {
|
|
48
|
+
reject(new Error(`acpx exited with code ${code}: ${stderr.trim()}`));
|
|
49
|
+
}
|
|
50
|
+
else {
|
|
51
|
+
resolve({ output, events });
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
proc.on('error', (err) => {
|
|
55
|
+
reject(new Error(`Failed to spawn acpx: ${err.message}. Is acpx installed?`));
|
|
56
|
+
});
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Cancel the current operation in a session.
|
|
61
|
+
*/
|
|
62
|
+
async cancel(agent, sessionName) {
|
|
63
|
+
await this.exec(agent, ['cancel', '-s', sessionName]);
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Close a session.
|
|
67
|
+
*/
|
|
68
|
+
async close(agent, sessionName) {
|
|
69
|
+
await this.exec(agent, ['sessions', 'close', sessionName]);
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Execute an acpx command and return stdout.
|
|
73
|
+
*/
|
|
74
|
+
exec(agent, args) {
|
|
75
|
+
return new Promise((resolve, reject) => {
|
|
76
|
+
const proc = spawn('acpx', ['--approve-all', agent, ...args], {
|
|
77
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
78
|
+
cwd: this.cwd,
|
|
79
|
+
env: { ...process.env },
|
|
80
|
+
});
|
|
81
|
+
let stdout = '';
|
|
82
|
+
let stderr = '';
|
|
83
|
+
proc.stdout.on('data', (chunk) => { stdout += chunk.toString(); });
|
|
84
|
+
proc.stderr.on('data', (chunk) => { stderr += chunk.toString(); });
|
|
85
|
+
proc.on('close', (code) => {
|
|
86
|
+
if (code !== 0)
|
|
87
|
+
reject(new Error(`acpx ${agent} ${args.join(' ')} failed (${code}): ${stderr.trim()}`));
|
|
88
|
+
else
|
|
89
|
+
resolve(stdout);
|
|
90
|
+
});
|
|
91
|
+
proc.on('error', (err) => {
|
|
92
|
+
reject(new Error(`Failed to spawn acpx: ${err.message}`));
|
|
93
|
+
});
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
//# sourceMappingURL=acpx-client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"acpx-client.js","sourceRoot":"","sources":["../src/acpx-client.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,oBAAoB,CAAA;AAiB1C;;;GAGG;AACH,MAAM,OAAO,UAAU;IACb,GAAG,CAAQ;IAEnB,YAAY,GAAY;QACtB,IAAI,CAAC,GAAG,GAAG,GAAG,IAAI,OAAO,CAAC,GAAG,EAAE,CAAA;IACjC,CAAC;IAED;;OAEG;IACH,KAAK,CAAC,aAAa,CAAC,KAAa,EAAE,WAAmB;QACpD,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,IAAI,CAAC,KAAK,EAAE,CAAC,UAAU,EAAE,QAAQ,EAAE,QAAQ,EAAE,WAAW,CAAC,CAAC,CAAA;QACpF,gDAAgD;QAChD,IAAI,CAAC;YACH,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,GAAG,EAAE,IAAI,IAAI,CAAC,CAAA;YAChE,OAAO,IAAI,CAAC,SAAS,IAAI,WAAW,CAAA;QACtC,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,WAAW,CAAA;QACpB,CAAC;IACH,CAAC;IAED;;;OAGG;IACH,KAAK,CAAC,MAAM,CAAC,KAAa,EAAE,WAAmB,EAAE,IAAY;QAC3D,MAAM,MAAM,GAAe,EAAE,CAAA;QAE7B,OAAO,IAAI,OAAO,CAAY,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;YAChD,kEAAkE;YAClE,uEAAuE;YACvE,MAAM,IAAI,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC,eAAe,EAAE,KAAK,EAAE,QAAQ,EAAE,IAAI,EAAE,WAAW,EAAE,IAAI,CAAC,EAAE;gBACtF,KAAK,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC;gBAC/B,GAAG,EAAE,IAAI,CAAC,GAAG;gBACb,GAAG,EAAE,EAAE,GAAG,OAAO,CAAC,GAAG,EAAE;aACxB,CAAC,CAAA;YAEF,IAAI,MAAM,GAAG,EAAE,CAAA;YACf,IAAI,MAAM,GAAG,EAAE,CAAA;YAEf,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,KAAa,EAAE,EAAE,GAAG,MAAM,IAAI,KAAK,CAAC,QAAQ,EAAE,CAAA,CAAC,CAAC,CAAC,CAAA;YACzE,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,KAAa,EAAE,EAAE,GAAG,MAAM,IAAI,KAAK,CAAC,QAAQ,EAAE,CAAA,CAAC,CAAC,CAAC,CAAA;YAEzE,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,IAAI,EAAE,EAAE;gBACxB,wDAAwD;gBACxD,wEAAwE;gBACxE,MAAM,MAAM,GAAG,MAAM,CAAC,IAAI,EAAE,IAAI,MAAM,CAAC,IAAI,EAAE,CAAA;gBAE7C,IAAI,IAAI,KAAK,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC;oBAC1B,MAAM,CAAC,IAAI,KAAK,CAAC,yBAAyB,IAAI,KAAK,MAAM,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,CAAA;gBACtE,CAAC;qBAAM,CAAC;oBACN,OAAO,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC,CAAA;gBAC7B,CAAC;YACH,CAAC,CAAC,CAAA;YAEF,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,GAAG,EAAE,EAAE;gBACvB,MAAM,CAAC,IAAI,KAAK,CAAC,yBAAyB,GAAG,CAAC,OAAO,sBAAsB,CAAC,CAAC,CAAA;YAC/E,CAAC,CAAC,CAAA;QACJ,CAAC,CAAC,CAAA;IACJ,CAAC;IAED;;OAEG;IACH,KAAK,CAAC,MAAM,CAAC,KAAa,EAAE,WAAmB;QAC7C,MAAM,IAAI,CAAC,IAAI,CAAC,KAAK,EAAE,CAAC,QAAQ,EAAE,IAAI,EAAE,WAAW,CAAC,CAAC,CAAA;IACvD,CAAC;IAED;;OAEG;IACH,KAAK,CAAC,KAAK,CAAC,KAAa,EAAE,WAAmB;QAC5C,MAAM,IAAI,CAAC,IAAI,CAAC,KAAK,EAAE,CAAC,UAAU,EAAE,OAAO,EAAE,WAAW,CAAC,CAAC,CAAA;IAC5D,CAAC;IAED;;OAEG;IACK,IAAI,CAAC,KAAa,EAAE,IAAc;QACxC,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;YACrC,MAAM,IAAI,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC,eAAe,EAAE,KAAK,EAAE,GAAG,IAAI,CAAC,EAAE;gBAC5D,KAAK,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC;gBAC/B,GAAG,EAAE,IAAI,CAAC,GAAG;gBACb,GAAG,EAAE,EAAE,GAAG,OAAO,CAAC,GAAG,EAAE;aACxB,CAAC,CAAA;YAEF,IAAI,MAAM,GAAG,EAAE,CAAA;YACf,IAAI,MAAM,GAAG,EAAE,CAAA;YAEf,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,KAAa,EAAE,EAAE,GAAG,MAAM,IAAI,KAAK,CAAC,QAAQ,EAAE,CAAA,CAAC,CAAC,CAAC,CAAA;YACzE,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,KAAa,EAAE,EAAE,GAAG,MAAM,IAAI,KAAK,CAAC,QAAQ,EAAE,CAAA,CAAC,CAAC,CAAC,CAAA;YAEzE,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,IAAI,EAAE,EAAE;gBACxB,IAAI,IAAI,KAAK,CAAC;oBAAE,MAAM,CAAC,IAAI,KAAK,CAAC,QAAQ,KAAK,IAAI,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,YAAY,IAAI,MAAM,MAAM,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,CAAA;;oBAClG,OAAO,CAAC,MAAM,CAAC,CAAA;YACtB,CAAC,CAAC,CAAA;YAEF,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,GAAG,EAAE,EAAE;gBACvB,MAAM,CAAC,IAAI,KAAK,CAAC,yBAAyB,GAAG,CAAC,OAAO,EAAE,CAAC,CAAC,CAAA;YAC3D,CAAC,CAAC,CAAA;QACJ,CAAC,CAAC,CAAA;IACJ,CAAC;CACF"}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import type { AgentConfig } from './config.js';
|
|
2
|
+
export interface AgentIdentity {
|
|
3
|
+
email: string;
|
|
4
|
+
jmapToken: string;
|
|
5
|
+
smtpPassword: string;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Bridges a single ACP agent to the AAMP network.
|
|
9
|
+
* Manages AAMP identity, ACP session, and task routing.
|
|
10
|
+
*/
|
|
11
|
+
export declare class AgentBridge {
|
|
12
|
+
private readonly agentConfig;
|
|
13
|
+
private readonly aampHost;
|
|
14
|
+
private readonly rejectUnauthorized;
|
|
15
|
+
private client;
|
|
16
|
+
private acpx;
|
|
17
|
+
private identity;
|
|
18
|
+
private sessionName;
|
|
19
|
+
private processing;
|
|
20
|
+
private pollingFallback;
|
|
21
|
+
private transportMode;
|
|
22
|
+
constructor(agentConfig: AgentConfig, aampHost: string, rejectUnauthorized: boolean);
|
|
23
|
+
get name(): string;
|
|
24
|
+
get email(): string;
|
|
25
|
+
get isConnected(): boolean;
|
|
26
|
+
get isUsingPollingFallback(): boolean;
|
|
27
|
+
get isBusy(): boolean;
|
|
28
|
+
/**
|
|
29
|
+
* Start the bridge: resolve identity → connect AAMP → ensure ACP session.
|
|
30
|
+
*/
|
|
31
|
+
start(): Promise<void>;
|
|
32
|
+
/**
|
|
33
|
+
* Stop the bridge.
|
|
34
|
+
*/
|
|
35
|
+
stop(): void;
|
|
36
|
+
private normalizeEmail;
|
|
37
|
+
private isSenderAllowed;
|
|
38
|
+
/**
|
|
39
|
+
* Handle an incoming AAMP task by forwarding to the ACP agent.
|
|
40
|
+
*/
|
|
41
|
+
private handleTask;
|
|
42
|
+
/**
|
|
43
|
+
* Resolve AAMP identity: load from credentials file or register new.
|
|
44
|
+
*/
|
|
45
|
+
private resolveIdentity;
|
|
46
|
+
}
|
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
import { AampClient } from 'aamp-sdk';
|
|
2
|
+
import { AcpxClient } from './acpx-client.js';
|
|
3
|
+
import { buildPrompt, parseResponse } from './prompt-builder.js';
|
|
4
|
+
import { readFileSync, writeFileSync, existsSync, mkdirSync } from 'node:fs';
|
|
5
|
+
import { homedir } from 'node:os';
|
|
6
|
+
import { basename, dirname, join } from 'node:path';
|
|
7
|
+
function defaultCredentialsFile(name) {
|
|
8
|
+
return join(homedir(), '.acp-bridge', `.aamp-${name}.json`);
|
|
9
|
+
}
|
|
10
|
+
function resolveCredentialsFile(pathValue, name) {
|
|
11
|
+
const raw = pathValue?.trim();
|
|
12
|
+
if (!raw)
|
|
13
|
+
return defaultCredentialsFile(name);
|
|
14
|
+
if (raw === '~')
|
|
15
|
+
return homedir();
|
|
16
|
+
if (raw.startsWith('~/'))
|
|
17
|
+
return join(homedir(), raw.slice(2));
|
|
18
|
+
return raw;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Bridges a single ACP agent to the AAMP network.
|
|
22
|
+
* Manages AAMP identity, ACP session, and task routing.
|
|
23
|
+
*/
|
|
24
|
+
export class AgentBridge {
|
|
25
|
+
agentConfig;
|
|
26
|
+
aampHost;
|
|
27
|
+
rejectUnauthorized;
|
|
28
|
+
client = null;
|
|
29
|
+
acpx;
|
|
30
|
+
identity = null;
|
|
31
|
+
sessionName;
|
|
32
|
+
processing = false;
|
|
33
|
+
pollingFallback = false;
|
|
34
|
+
transportMode = 'connecting';
|
|
35
|
+
constructor(agentConfig, aampHost, rejectUnauthorized) {
|
|
36
|
+
this.agentConfig = agentConfig;
|
|
37
|
+
this.aampHost = aampHost;
|
|
38
|
+
this.rejectUnauthorized = rejectUnauthorized;
|
|
39
|
+
this.acpx = new AcpxClient();
|
|
40
|
+
this.sessionName = `aamp-${agentConfig.name}`;
|
|
41
|
+
}
|
|
42
|
+
get name() { return this.agentConfig.name; }
|
|
43
|
+
get email() { return this.identity?.email ?? '(not registered)'; }
|
|
44
|
+
get isConnected() { return this.client?.isConnected() ?? false; }
|
|
45
|
+
get isUsingPollingFallback() { return this.pollingFallback || (this.client?.isUsingPollingFallback() ?? false); }
|
|
46
|
+
get isBusy() { return this.processing; }
|
|
47
|
+
/**
|
|
48
|
+
* Start the bridge: resolve identity → connect AAMP → ensure ACP session.
|
|
49
|
+
*/
|
|
50
|
+
async start() {
|
|
51
|
+
// 1. Resolve AAMP identity
|
|
52
|
+
this.identity = await this.resolveIdentity();
|
|
53
|
+
console.log(`[${this.name}] AAMP identity: ${this.identity.email}`);
|
|
54
|
+
// 2. Create AAMP client
|
|
55
|
+
const smtpUrl = new URL(this.aampHost);
|
|
56
|
+
this.client = new AampClient({
|
|
57
|
+
email: this.identity.email,
|
|
58
|
+
jmapToken: this.identity.jmapToken,
|
|
59
|
+
jmapUrl: this.aampHost,
|
|
60
|
+
smtpHost: smtpUrl.hostname,
|
|
61
|
+
smtpPort: 587,
|
|
62
|
+
smtpPassword: this.identity.smtpPassword,
|
|
63
|
+
rejectUnauthorized: this.rejectUnauthorized,
|
|
64
|
+
});
|
|
65
|
+
// 3. Wire up task handler
|
|
66
|
+
this.client.on('task.dispatch', (task) => {
|
|
67
|
+
this.handleTask(task).catch((err) => {
|
|
68
|
+
console.error(`[${this.name}] Task ${task.taskId} failed: ${err.message}`);
|
|
69
|
+
});
|
|
70
|
+
});
|
|
71
|
+
this.client.on('connected', () => {
|
|
72
|
+
const usingPollingFallback = this.client?.isUsingPollingFallback() ?? false;
|
|
73
|
+
this.pollingFallback = usingPollingFallback;
|
|
74
|
+
if (usingPollingFallback) {
|
|
75
|
+
if (this.transportMode !== 'polling') {
|
|
76
|
+
console.warn(`[${this.name}] AAMP connected (polling fallback active)`);
|
|
77
|
+
}
|
|
78
|
+
this.transportMode = 'polling';
|
|
79
|
+
}
|
|
80
|
+
else {
|
|
81
|
+
const previousMode = this.transportMode;
|
|
82
|
+
this.transportMode = 'websocket';
|
|
83
|
+
if (previousMode === 'polling') {
|
|
84
|
+
console.log(`[${this.name}] AAMP WebSocket restored`);
|
|
85
|
+
}
|
|
86
|
+
else {
|
|
87
|
+
console.log(`[${this.name}] AAMP connected`);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
});
|
|
91
|
+
this.client.on('disconnected', (reason) => {
|
|
92
|
+
const usingPollingFallback = this.client?.isUsingPollingFallback() ?? false;
|
|
93
|
+
this.pollingFallback = usingPollingFallback;
|
|
94
|
+
if (usingPollingFallback) {
|
|
95
|
+
if (this.transportMode !== 'polling') {
|
|
96
|
+
console.warn(`[${this.name}] AAMP WebSocket unavailable, using polling fallback: ${reason}`);
|
|
97
|
+
}
|
|
98
|
+
this.transportMode = 'polling';
|
|
99
|
+
}
|
|
100
|
+
else {
|
|
101
|
+
this.transportMode = 'disconnected';
|
|
102
|
+
console.warn(`[${this.name}] AAMP disconnected: ${reason}`);
|
|
103
|
+
}
|
|
104
|
+
});
|
|
105
|
+
this.client.on('error', (err) => {
|
|
106
|
+
if (err.message.includes('falling back to polling')) {
|
|
107
|
+
this.pollingFallback = true;
|
|
108
|
+
if (this.transportMode !== 'polling') {
|
|
109
|
+
console.warn(`[${this.name}] ${err.message}`);
|
|
110
|
+
this.transportMode = 'polling';
|
|
111
|
+
}
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
if (this.transportMode === 'polling' && (err.message.includes('JMAP WebSocket handshake failed')
|
|
115
|
+
|| err.message.includes('Failed to get JMAP session')
|
|
116
|
+
|| err.message.includes('Polling fallback failed'))) {
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
console.error(`[${this.name}] AAMP error: ${err.message}`);
|
|
120
|
+
});
|
|
121
|
+
// 4. Connect to AAMP
|
|
122
|
+
await this.client.connect();
|
|
123
|
+
// 5. Ensure ACP session
|
|
124
|
+
try {
|
|
125
|
+
await this.acpx.ensureSession(this.agentConfig.acpCommand, this.sessionName);
|
|
126
|
+
console.log(`[${this.name}] ACP session ready: ${this.sessionName}`);
|
|
127
|
+
}
|
|
128
|
+
catch (err) {
|
|
129
|
+
console.warn(`[${this.name}] ACP session setup deferred: ${err.message}`);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Stop the bridge.
|
|
134
|
+
*/
|
|
135
|
+
stop() {
|
|
136
|
+
this.client?.disconnect();
|
|
137
|
+
this.client = null;
|
|
138
|
+
}
|
|
139
|
+
normalizeEmail(email) {
|
|
140
|
+
return email.trim().toLowerCase();
|
|
141
|
+
}
|
|
142
|
+
isSenderAllowed(sender) {
|
|
143
|
+
const whitelist = this.agentConfig.senderWhitelist;
|
|
144
|
+
if (!whitelist)
|
|
145
|
+
return true;
|
|
146
|
+
const normalizedSender = this.normalizeEmail(sender);
|
|
147
|
+
return whitelist.some((allowed) => this.normalizeEmail(allowed) === normalizedSender);
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* Handle an incoming AAMP task by forwarding to the ACP agent.
|
|
151
|
+
*/
|
|
152
|
+
async handleTask(task) {
|
|
153
|
+
if (!this.client)
|
|
154
|
+
return;
|
|
155
|
+
console.log(`[${this.name}] <- task.dispatch ${task.taskId} "${task.title}" from=${task.from}`);
|
|
156
|
+
if (!this.isSenderAllowed(task.from)) {
|
|
157
|
+
console.warn(`[${this.name}] Rejecting task ${task.taskId} from non-whitelisted sender: ${task.from}`);
|
|
158
|
+
await this.client.sendResult({
|
|
159
|
+
to: task.from,
|
|
160
|
+
taskId: task.taskId,
|
|
161
|
+
status: 'rejected',
|
|
162
|
+
output: '',
|
|
163
|
+
errorMsg: 'Unauthorized sender: this bridge only accepts tasks from whitelisted email addresses.',
|
|
164
|
+
inReplyTo: task.messageId,
|
|
165
|
+
});
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
this.processing = true;
|
|
169
|
+
try {
|
|
170
|
+
const prompt = buildPrompt(task);
|
|
171
|
+
const result = await this.acpx.prompt(this.agentConfig.acpCommand, this.sessionName, prompt);
|
|
172
|
+
const parsed = parseResponse(result.output);
|
|
173
|
+
if (parsed.isHelp) {
|
|
174
|
+
// Agent needs help
|
|
175
|
+
await this.client.sendHelp({
|
|
176
|
+
to: task.from,
|
|
177
|
+
taskId: task.taskId,
|
|
178
|
+
question: parsed.question ?? 'Agent needs more information',
|
|
179
|
+
blockedReason: 'ACP agent requested clarification',
|
|
180
|
+
suggestedOptions: [],
|
|
181
|
+
inReplyTo: task.messageId,
|
|
182
|
+
});
|
|
183
|
+
console.log(`[${this.name}] -> task.help ${task.taskId}`);
|
|
184
|
+
}
|
|
185
|
+
else {
|
|
186
|
+
// Collect file attachments referenced by the agent
|
|
187
|
+
const attachments = [];
|
|
188
|
+
for (const filepath of parsed.files) {
|
|
189
|
+
if (existsSync(filepath)) {
|
|
190
|
+
try {
|
|
191
|
+
attachments.push({
|
|
192
|
+
filename: basename(filepath),
|
|
193
|
+
contentType: 'application/octet-stream',
|
|
194
|
+
content: readFileSync(filepath),
|
|
195
|
+
});
|
|
196
|
+
console.log(`[${this.name}] Attaching file: ${filepath}`);
|
|
197
|
+
}
|
|
198
|
+
catch (err) {
|
|
199
|
+
console.warn(`[${this.name}] Failed to read file ${filepath}: ${err.message}`);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
// Task completed
|
|
204
|
+
await this.client.sendResult({
|
|
205
|
+
to: task.from,
|
|
206
|
+
taskId: task.taskId,
|
|
207
|
+
status: 'completed',
|
|
208
|
+
output: parsed.output,
|
|
209
|
+
inReplyTo: task.messageId,
|
|
210
|
+
attachments: attachments.length > 0 ? attachments : undefined,
|
|
211
|
+
});
|
|
212
|
+
console.log(`[${this.name}] -> task.result ${task.taskId} completed${attachments.length ? ` (${attachments.length} attachment(s))` : ''}`);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
catch (err) {
|
|
216
|
+
const errorMsg = err.message;
|
|
217
|
+
console.error(`[${this.name}] Task ${task.taskId} error: ${errorMsg}`);
|
|
218
|
+
try {
|
|
219
|
+
await this.client.sendResult({
|
|
220
|
+
to: task.from,
|
|
221
|
+
taskId: task.taskId,
|
|
222
|
+
status: 'rejected',
|
|
223
|
+
output: '',
|
|
224
|
+
errorMsg: `ACP agent error: ${errorMsg}`,
|
|
225
|
+
inReplyTo: task.messageId,
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
catch { /* best effort */ }
|
|
229
|
+
}
|
|
230
|
+
finally {
|
|
231
|
+
this.processing = false;
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
/**
|
|
235
|
+
* Resolve AAMP identity: load from credentials file or register new.
|
|
236
|
+
*/
|
|
237
|
+
async resolveIdentity() {
|
|
238
|
+
const credFile = resolveCredentialsFile(this.agentConfig.credentialsFile, this.agentConfig.name);
|
|
239
|
+
// Try loading existing credentials
|
|
240
|
+
if (existsSync(credFile)) {
|
|
241
|
+
try {
|
|
242
|
+
const data = JSON.parse(readFileSync(credFile, 'utf-8'));
|
|
243
|
+
if (data.email && data.jmapToken && data.smtpPassword) {
|
|
244
|
+
return data;
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
catch { /* re-register */ }
|
|
248
|
+
}
|
|
249
|
+
// Self-register
|
|
250
|
+
const slug = this.agentConfig.slug ?? `${this.agentConfig.name}-bridge`;
|
|
251
|
+
const description = this.agentConfig.description ?? `${this.agentConfig.name} via ACP bridge`;
|
|
252
|
+
// Step 1: Register
|
|
253
|
+
const regRes = await fetch(`${this.aampHost}/api/nodes/self-register`, {
|
|
254
|
+
method: 'POST',
|
|
255
|
+
headers: { 'Content-Type': 'application/json' },
|
|
256
|
+
body: JSON.stringify({ slug, description }),
|
|
257
|
+
});
|
|
258
|
+
if (!regRes.ok)
|
|
259
|
+
throw new Error(`Registration failed: ${regRes.status} ${await regRes.text()}`);
|
|
260
|
+
const regData = await regRes.json();
|
|
261
|
+
// Step 2: Exchange code for credentials
|
|
262
|
+
const credRes = await fetch(`${this.aampHost}/api/nodes/credentials?code=${regData.registrationCode}`);
|
|
263
|
+
if (!credRes.ok)
|
|
264
|
+
throw new Error(`Credential exchange failed: ${credRes.status}`);
|
|
265
|
+
const creds = await credRes.json();
|
|
266
|
+
const identity = {
|
|
267
|
+
email: creds.email,
|
|
268
|
+
jmapToken: creds.jmap.token,
|
|
269
|
+
smtpPassword: creds.smtp.password,
|
|
270
|
+
};
|
|
271
|
+
// Persist credentials
|
|
272
|
+
mkdirSync(dirname(credFile), { recursive: true });
|
|
273
|
+
writeFileSync(credFile, JSON.stringify(identity, null, 2));
|
|
274
|
+
console.log(`[${this.name}] Registered: ${identity.email} (credentials saved to ${credFile})`);
|
|
275
|
+
return identity;
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
//# sourceMappingURL=agent-bridge.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"agent-bridge.js","sourceRoot":"","sources":["../src/agent-bridge.ts"],"names":[],"mappings":"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"}
|
package/dist/bridge.d.ts
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { BridgeConfig } from './config.js';
|
|
2
|
+
/**
|
|
3
|
+
* Manages multiple ACP agent bridges, each with its own AAMP identity.
|
|
4
|
+
*/
|
|
5
|
+
export declare class AampAcpBridge {
|
|
6
|
+
private agents;
|
|
7
|
+
private config;
|
|
8
|
+
constructor(config: BridgeConfig);
|
|
9
|
+
/**
|
|
10
|
+
* Start all configured agents.
|
|
11
|
+
*/
|
|
12
|
+
start(): Promise<void>;
|
|
13
|
+
/**
|
|
14
|
+
* Stop all agents.
|
|
15
|
+
*/
|
|
16
|
+
stop(): void;
|
|
17
|
+
/**
|
|
18
|
+
* List all agents and their status.
|
|
19
|
+
*/
|
|
20
|
+
list(): void;
|
|
21
|
+
}
|
package/dist/bridge.js
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { AgentBridge } from './agent-bridge.js';
|
|
2
|
+
/**
|
|
3
|
+
* Manages multiple ACP agent bridges, each with its own AAMP identity.
|
|
4
|
+
*/
|
|
5
|
+
export class AampAcpBridge {
|
|
6
|
+
agents = new Map();
|
|
7
|
+
config;
|
|
8
|
+
constructor(config) {
|
|
9
|
+
this.config = config;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Start all configured agents.
|
|
13
|
+
*/
|
|
14
|
+
async start() {
|
|
15
|
+
console.log(`\nAAMP ACP Bridge`);
|
|
16
|
+
console.log(` Host: ${this.config.aampHost}`);
|
|
17
|
+
console.log(` Agents: ${this.config.agents.length}\n`);
|
|
18
|
+
for (const agentConfig of this.config.agents) {
|
|
19
|
+
const bridge = new AgentBridge(agentConfig, this.config.aampHost, this.config.rejectUnauthorized);
|
|
20
|
+
try {
|
|
21
|
+
await bridge.start();
|
|
22
|
+
this.agents.set(agentConfig.name, bridge);
|
|
23
|
+
}
|
|
24
|
+
catch (err) {
|
|
25
|
+
console.error(`[${agentConfig.name}] Failed to start: ${err.message}`);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
if (this.agents.size === 0) {
|
|
29
|
+
throw new Error('No agents started successfully');
|
|
30
|
+
}
|
|
31
|
+
console.log(`\nBridge running with ${this.agents.size} agent(s):`);
|
|
32
|
+
for (const [name, bridge] of this.agents) {
|
|
33
|
+
console.log(` ${name}: ${bridge.email}`);
|
|
34
|
+
}
|
|
35
|
+
console.log(`\nPress Ctrl+C to stop.\n`);
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Stop all agents.
|
|
39
|
+
*/
|
|
40
|
+
stop() {
|
|
41
|
+
for (const [name, bridge] of this.agents) {
|
|
42
|
+
console.log(`[${name}] Stopping...`);
|
|
43
|
+
bridge.stop();
|
|
44
|
+
}
|
|
45
|
+
this.agents.clear();
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* List all agents and their status.
|
|
49
|
+
*/
|
|
50
|
+
list() {
|
|
51
|
+
if (this.agents.size === 0) {
|
|
52
|
+
console.log('No agents running.');
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
console.log(`\nAgents (${this.agents.size}):`);
|
|
56
|
+
for (const [name, bridge] of this.agents) {
|
|
57
|
+
const status = bridge.isConnected
|
|
58
|
+
? (bridge.isUsingPollingFallback ? 'connected (polling fallback)' : 'connected')
|
|
59
|
+
: 'disconnected';
|
|
60
|
+
const busy = bridge.isBusy ? ' (processing)' : '';
|
|
61
|
+
console.log(` ${name}: ${bridge.email} -- ${status}${busy}`);
|
|
62
|
+
}
|
|
63
|
+
console.log();
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
//# sourceMappingURL=bridge.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bridge.js","sourceRoot":"","sources":["../src/bridge.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function runInit(configPath: string): Promise<void>;
|
package/dist/cli/init.js
ADDED
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
import { createInterface } from 'node:readline';
|
|
2
|
+
import { writeFileSync, existsSync, mkdirSync } from 'node:fs';
|
|
3
|
+
import { execSync } from 'node:child_process';
|
|
4
|
+
import { homedir } from 'node:os';
|
|
5
|
+
import { dirname, join } from 'node:path';
|
|
6
|
+
const KNOWN_AGENTS = [
|
|
7
|
+
'claude', 'codex', 'gemini', 'goose', 'openclaw',
|
|
8
|
+
'opencode', 'cursor', 'copilot', 'kimi', 'kiro',
|
|
9
|
+
];
|
|
10
|
+
function ask(rl, question) {
|
|
11
|
+
return new Promise((resolve) => rl.question(question, resolve));
|
|
12
|
+
}
|
|
13
|
+
function parseWhitelist(input) {
|
|
14
|
+
return input
|
|
15
|
+
.split(',')
|
|
16
|
+
.map((item) => item.trim().toLowerCase())
|
|
17
|
+
.filter(Boolean);
|
|
18
|
+
}
|
|
19
|
+
function detectAgent(name) {
|
|
20
|
+
try {
|
|
21
|
+
execSync(`which ${name}`, { stdio: 'pipe' });
|
|
22
|
+
try {
|
|
23
|
+
const version = execSync(`${name} --version 2>/dev/null || echo unknown`, { stdio: 'pipe' }).toString().trim().split('\n')[0];
|
|
24
|
+
return version;
|
|
25
|
+
}
|
|
26
|
+
catch {
|
|
27
|
+
return 'installed';
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
catch {
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
function defaultCredentialsFile(name) {
|
|
35
|
+
return join(homedir(), '.acp-bridge', `.aamp-${name}.json`);
|
|
36
|
+
}
|
|
37
|
+
export async function runInit(configPath) {
|
|
38
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
39
|
+
console.log('\nAAMP ACP Bridge Setup\n');
|
|
40
|
+
// 1. AAMP host
|
|
41
|
+
const aampHostInput = (await ask(rl, '? AAMP Service URL (default: https://meshmail.ai): ')).trim();
|
|
42
|
+
const aampHost = aampHostInput || 'https://meshmail.ai';
|
|
43
|
+
if (!aampHost) {
|
|
44
|
+
rl.close();
|
|
45
|
+
throw new Error('AAMP host is required');
|
|
46
|
+
}
|
|
47
|
+
// Verify connectivity
|
|
48
|
+
try {
|
|
49
|
+
const res = await fetch(`${aampHost}/health`);
|
|
50
|
+
if (res.ok) {
|
|
51
|
+
const data = await res.json();
|
|
52
|
+
console.log(` Connected (${data.service})\n`);
|
|
53
|
+
}
|
|
54
|
+
else {
|
|
55
|
+
console.log(` Warning: Server responded with ${res.status}\n`);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
catch {
|
|
59
|
+
console.log(` Warning: Could not reach ${aampHost} -- continuing anyway\n`);
|
|
60
|
+
}
|
|
61
|
+
// 2. Scan for ACP agents
|
|
62
|
+
console.log('? Scanning for ACP agents...');
|
|
63
|
+
const detected = [];
|
|
64
|
+
for (const name of KNOWN_AGENTS) {
|
|
65
|
+
const version = detectAgent(name);
|
|
66
|
+
if (version) {
|
|
67
|
+
console.log(` + ${name.padEnd(12)} (${version})`);
|
|
68
|
+
detected.push({ name, version });
|
|
69
|
+
}
|
|
70
|
+
else {
|
|
71
|
+
console.log(` - ${name.padEnd(12)} (not installed)`);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
console.log();
|
|
75
|
+
if (detected.length === 0) {
|
|
76
|
+
console.log('No ACP agents found. Install an agent first (e.g. npm i -g @anthropic-ai/claude-code).');
|
|
77
|
+
rl.close();
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
// 3. Select agents
|
|
81
|
+
const selected = [];
|
|
82
|
+
for (const { name } of detected) {
|
|
83
|
+
const answer = await ask(rl, `? Bridge ${name}? (y/N): `);
|
|
84
|
+
if (answer.trim().toLowerCase() === 'y') {
|
|
85
|
+
selected.push(name);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
if (selected.length === 0) {
|
|
89
|
+
console.log('No agents selected. Exiting.');
|
|
90
|
+
rl.close();
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
// 4. Register AAMP identities
|
|
94
|
+
console.log('\n? Registering AAMP identities...');
|
|
95
|
+
const agents = [];
|
|
96
|
+
for (const name of selected) {
|
|
97
|
+
const slug = `${name}-bridge`;
|
|
98
|
+
const credFile = defaultCredentialsFile(name);
|
|
99
|
+
const whitelistAnswer = await ask(rl, `? Restrict ${name} to a sender whitelist? (y/N): `);
|
|
100
|
+
let senderWhitelist;
|
|
101
|
+
if (whitelistAnswer.trim().toLowerCase() === 'y') {
|
|
102
|
+
const whitelistInput = await ask(rl, `? Allowed sender emails for ${name} (comma-separated): `);
|
|
103
|
+
const parsed = parseWhitelist(whitelistInput);
|
|
104
|
+
if (parsed.length === 0) {
|
|
105
|
+
console.log(` Warning: no valid whitelist entries provided; ${name} will accept all senders.`);
|
|
106
|
+
}
|
|
107
|
+
else {
|
|
108
|
+
senderWhitelist = parsed;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
if (existsSync(credFile)) {
|
|
112
|
+
console.log(` + ${name} -> using existing credentials (${credFile})`);
|
|
113
|
+
agents.push({ name, acpCommand: name, slug, credentialsFile: credFile, senderWhitelist });
|
|
114
|
+
continue;
|
|
115
|
+
}
|
|
116
|
+
try {
|
|
117
|
+
const regRes = await fetch(`${aampHost}/api/nodes/self-register`, {
|
|
118
|
+
method: 'POST',
|
|
119
|
+
headers: { 'Content-Type': 'application/json' },
|
|
120
|
+
body: JSON.stringify({ slug, description: `${name} via ACP bridge` }),
|
|
121
|
+
});
|
|
122
|
+
if (!regRes.ok)
|
|
123
|
+
throw new Error(`${regRes.status}`);
|
|
124
|
+
const regData = await regRes.json();
|
|
125
|
+
const credRes = await fetch(`${aampHost}/api/nodes/credentials?code=${regData.registrationCode}`);
|
|
126
|
+
if (!credRes.ok)
|
|
127
|
+
throw new Error(`Credential exchange: ${credRes.status}`);
|
|
128
|
+
const creds = await credRes.json();
|
|
129
|
+
mkdirSync(dirname(credFile), { recursive: true });
|
|
130
|
+
writeFileSync(credFile, JSON.stringify({
|
|
131
|
+
email: creds.email,
|
|
132
|
+
jmapToken: creds.jmap.token,
|
|
133
|
+
smtpPassword: creds.smtp.password,
|
|
134
|
+
}, null, 2));
|
|
135
|
+
console.log(` + ${name} -> ${creds.email}`);
|
|
136
|
+
agents.push({
|
|
137
|
+
name,
|
|
138
|
+
acpCommand: name,
|
|
139
|
+
slug,
|
|
140
|
+
credentialsFile: credFile,
|
|
141
|
+
description: `${name} via ACP bridge`,
|
|
142
|
+
senderWhitelist,
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
catch (err) {
|
|
146
|
+
console.log(` x ${name} -> registration failed: ${err.message}`);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
if (agents.length === 0) {
|
|
150
|
+
console.log('No agents registered. Exiting.');
|
|
151
|
+
rl.close();
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
// 5. Write config
|
|
155
|
+
const config = { aampHost, rejectUnauthorized: false, agents };
|
|
156
|
+
writeFileSync(configPath, JSON.stringify(config, null, 2));
|
|
157
|
+
console.log(`\nConfig written to ${configPath}`);
|
|
158
|
+
console.log(` Run: npx --registry=https://bnpm.byted.org @byted-meego/aamp-acp-bridge start\n`);
|
|
159
|
+
rl.close();
|
|
160
|
+
}
|
|
161
|
+
//# sourceMappingURL=init.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"init.js","sourceRoot":"","sources":["../../src/cli/init.ts"],"names":[],"mappings":"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"}
|
package/dist/config.d.ts
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
declare const agentConfigSchema: z.ZodObject<{
|
|
3
|
+
name: z.ZodString;
|
|
4
|
+
acpCommand: z.ZodString;
|
|
5
|
+
slug: z.ZodOptional<z.ZodString>;
|
|
6
|
+
description: z.ZodOptional<z.ZodString>;
|
|
7
|
+
credentialsFile: z.ZodOptional<z.ZodString>;
|
|
8
|
+
senderWhitelist: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
9
|
+
}, "strip", z.ZodTypeAny, {
|
|
10
|
+
name: string;
|
|
11
|
+
acpCommand: string;
|
|
12
|
+
slug?: string | undefined;
|
|
13
|
+
description?: string | undefined;
|
|
14
|
+
credentialsFile?: string | undefined;
|
|
15
|
+
senderWhitelist?: string[] | undefined;
|
|
16
|
+
}, {
|
|
17
|
+
name: string;
|
|
18
|
+
acpCommand: string;
|
|
19
|
+
slug?: string | undefined;
|
|
20
|
+
description?: string | undefined;
|
|
21
|
+
credentialsFile?: string | undefined;
|
|
22
|
+
senderWhitelist?: string[] | undefined;
|
|
23
|
+
}>;
|
|
24
|
+
declare const bridgeConfigSchema: z.ZodObject<{
|
|
25
|
+
aampHost: z.ZodString;
|
|
26
|
+
rejectUnauthorized: z.ZodDefault<z.ZodBoolean>;
|
|
27
|
+
agents: z.ZodArray<z.ZodObject<{
|
|
28
|
+
name: z.ZodString;
|
|
29
|
+
acpCommand: z.ZodString;
|
|
30
|
+
slug: z.ZodOptional<z.ZodString>;
|
|
31
|
+
description: z.ZodOptional<z.ZodString>;
|
|
32
|
+
credentialsFile: z.ZodOptional<z.ZodString>;
|
|
33
|
+
senderWhitelist: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
34
|
+
}, "strip", z.ZodTypeAny, {
|
|
35
|
+
name: string;
|
|
36
|
+
acpCommand: string;
|
|
37
|
+
slug?: string | undefined;
|
|
38
|
+
description?: string | undefined;
|
|
39
|
+
credentialsFile?: string | undefined;
|
|
40
|
+
senderWhitelist?: string[] | undefined;
|
|
41
|
+
}, {
|
|
42
|
+
name: string;
|
|
43
|
+
acpCommand: string;
|
|
44
|
+
slug?: string | undefined;
|
|
45
|
+
description?: string | undefined;
|
|
46
|
+
credentialsFile?: string | undefined;
|
|
47
|
+
senderWhitelist?: string[] | undefined;
|
|
48
|
+
}>, "many">;
|
|
49
|
+
}, "strip", z.ZodTypeAny, {
|
|
50
|
+
aampHost: string;
|
|
51
|
+
rejectUnauthorized: boolean;
|
|
52
|
+
agents: {
|
|
53
|
+
name: string;
|
|
54
|
+
acpCommand: string;
|
|
55
|
+
slug?: string | undefined;
|
|
56
|
+
description?: string | undefined;
|
|
57
|
+
credentialsFile?: string | undefined;
|
|
58
|
+
senderWhitelist?: string[] | undefined;
|
|
59
|
+
}[];
|
|
60
|
+
}, {
|
|
61
|
+
aampHost: string;
|
|
62
|
+
agents: {
|
|
63
|
+
name: string;
|
|
64
|
+
acpCommand: string;
|
|
65
|
+
slug?: string | undefined;
|
|
66
|
+
description?: string | undefined;
|
|
67
|
+
credentialsFile?: string | undefined;
|
|
68
|
+
senderWhitelist?: string[] | undefined;
|
|
69
|
+
}[];
|
|
70
|
+
rejectUnauthorized?: boolean | undefined;
|
|
71
|
+
}>;
|
|
72
|
+
export type AgentConfig = z.infer<typeof agentConfigSchema>;
|
|
73
|
+
export type BridgeConfig = z.infer<typeof bridgeConfigSchema>;
|
|
74
|
+
export declare function loadConfig(path: string): BridgeConfig;
|
|
75
|
+
export {};
|
package/dist/config.js
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { readFileSync, existsSync } from 'node:fs';
|
|
3
|
+
const agentConfigSchema = z.object({
|
|
4
|
+
name: z.string().min(1),
|
|
5
|
+
acpCommand: z.string().min(1),
|
|
6
|
+
slug: z.string().regex(/^[a-z0-9-]+$/).optional(),
|
|
7
|
+
description: z.string().optional(),
|
|
8
|
+
credentialsFile: z.string().optional(),
|
|
9
|
+
senderWhitelist: z.array(z.string().email()).optional(),
|
|
10
|
+
});
|
|
11
|
+
const bridgeConfigSchema = z.object({
|
|
12
|
+
aampHost: z.string().url(),
|
|
13
|
+
rejectUnauthorized: z.boolean().default(false),
|
|
14
|
+
agents: z.array(agentConfigSchema).min(1),
|
|
15
|
+
});
|
|
16
|
+
export function loadConfig(path) {
|
|
17
|
+
if (!existsSync(path)) {
|
|
18
|
+
throw new Error(`Config file not found: ${path}. Run 'aamp-acp-bridge init' first.`);
|
|
19
|
+
}
|
|
20
|
+
const raw = JSON.parse(readFileSync(path, 'utf-8'));
|
|
21
|
+
return bridgeConfigSchema.parse(raw);
|
|
22
|
+
}
|
|
23
|
+
//# sourceMappingURL=config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AACvB,OAAO,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,SAAS,CAAA;AAElD,MAAM,iBAAiB,GAAG,CAAC,CAAC,MAAM,CAAC;IACjC,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;IACvB,UAAU,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;IAC7B,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,KAAK,CAAC,cAAc,CAAC,CAAC,QAAQ,EAAE;IACjD,WAAW,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IAClC,eAAe,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IACtC,eAAe,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,KAAK,EAAE,CAAC,CAAC,QAAQ,EAAE;CACxD,CAAC,CAAA;AAEF,MAAM,kBAAkB,GAAG,CAAC,CAAC,MAAM,CAAC;IAClC,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE;IAC1B,kBAAkB,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC;IAC9C,MAAM,EAAE,CAAC,CAAC,KAAK,CAAC,iBAAiB,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;CAC1C,CAAC,CAAA;AAKF,MAAM,UAAU,UAAU,CAAC,IAAY;IACrC,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC;QACtB,MAAM,IAAI,KAAK,CAAC,0BAA0B,IAAI,qCAAqC,CAAC,CAAA;IACtF,CAAC;IACD,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC,CAAA;IACnD,OAAO,kBAAkB,CAAC,KAAK,CAAC,GAAG,CAAC,CAAA;AACtC,CAAC"}
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { readFileSync } from 'node:fs';
|
|
3
|
+
import { homedir } from 'node:os';
|
|
4
|
+
import { join } from 'node:path';
|
|
5
|
+
import { loadConfig } from './config.js';
|
|
6
|
+
import { AampAcpBridge } from './bridge.js';
|
|
7
|
+
import { runInit } from './cli/init.js';
|
|
8
|
+
const args = process.argv.slice(2);
|
|
9
|
+
const command = args[0] ?? 'start';
|
|
10
|
+
const configPath = args.includes('--config')
|
|
11
|
+
? (args[args.indexOf('--config') + 1] ?? 'bridge.json')
|
|
12
|
+
: 'bridge.json';
|
|
13
|
+
function defaultCredentialsFile(name) {
|
|
14
|
+
return join(homedir(), '.acp-bridge', `.aamp-${name}.json`);
|
|
15
|
+
}
|
|
16
|
+
function resolveCredentialsFile(pathValue, name) {
|
|
17
|
+
const raw = pathValue?.trim();
|
|
18
|
+
if (!raw)
|
|
19
|
+
return defaultCredentialsFile(name);
|
|
20
|
+
if (raw === '~')
|
|
21
|
+
return homedir();
|
|
22
|
+
if (raw.startsWith('~/'))
|
|
23
|
+
return join(homedir(), raw.slice(2));
|
|
24
|
+
return raw;
|
|
25
|
+
}
|
|
26
|
+
async function main() {
|
|
27
|
+
switch (command) {
|
|
28
|
+
case 'init': {
|
|
29
|
+
await runInit(configPath);
|
|
30
|
+
break;
|
|
31
|
+
}
|
|
32
|
+
case 'start': {
|
|
33
|
+
const config = loadConfig(configPath);
|
|
34
|
+
const bridge = new AampAcpBridge(config);
|
|
35
|
+
// Graceful shutdown
|
|
36
|
+
const shutdown = () => {
|
|
37
|
+
console.log('\nShutting down...');
|
|
38
|
+
bridge.stop();
|
|
39
|
+
process.exit(0);
|
|
40
|
+
};
|
|
41
|
+
process.on('SIGTERM', shutdown);
|
|
42
|
+
process.on('SIGINT', shutdown);
|
|
43
|
+
await bridge.start();
|
|
44
|
+
// Keep alive
|
|
45
|
+
setInterval(() => { }, 60_000);
|
|
46
|
+
break;
|
|
47
|
+
}
|
|
48
|
+
case 'list': {
|
|
49
|
+
const config = loadConfig(configPath);
|
|
50
|
+
console.log(`\nConfigured agents (${config.agents.length}):`);
|
|
51
|
+
for (const a of config.agents) {
|
|
52
|
+
const credFile = resolveCredentialsFile(a.credentialsFile, a.name);
|
|
53
|
+
let email = '(not registered)';
|
|
54
|
+
try {
|
|
55
|
+
const creds = JSON.parse(readFileSync(credFile, 'utf-8'));
|
|
56
|
+
email = creds.email ?? email;
|
|
57
|
+
}
|
|
58
|
+
catch { /* no credentials yet */ }
|
|
59
|
+
console.log(` ${a.name}: ${email} (${a.acpCommand})`);
|
|
60
|
+
}
|
|
61
|
+
console.log();
|
|
62
|
+
break;
|
|
63
|
+
}
|
|
64
|
+
case 'status': {
|
|
65
|
+
const config = loadConfig(configPath);
|
|
66
|
+
const bridge = new AampAcpBridge(config);
|
|
67
|
+
await bridge.start();
|
|
68
|
+
bridge.list();
|
|
69
|
+
bridge.stop();
|
|
70
|
+
break;
|
|
71
|
+
}
|
|
72
|
+
case 'help':
|
|
73
|
+
default:
|
|
74
|
+
console.log(`
|
|
75
|
+
AAMP ACP Bridge -- Connect ACP agents to the AAMP email network
|
|
76
|
+
|
|
77
|
+
Usage:
|
|
78
|
+
aamp-acp-bridge init Interactive setup wizard
|
|
79
|
+
aamp-acp-bridge start [--config X] Start the bridge (default: bridge.json)
|
|
80
|
+
aamp-acp-bridge list [--config X] List configured agents
|
|
81
|
+
aamp-acp-bridge status Show live connection status
|
|
82
|
+
aamp-acp-bridge help Show this help
|
|
83
|
+
|
|
84
|
+
Examples:
|
|
85
|
+
npx --registry=https://bnpm.byted.org @byted-meego/aamp-acp-bridge init
|
|
86
|
+
npx --registry=https://bnpm.byted.org @byted-meego/aamp-acp-bridge start
|
|
87
|
+
npx --registry=https://bnpm.byted.org @byted-meego/aamp-acp-bridge start --config production.json
|
|
88
|
+
`);
|
|
89
|
+
break;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
main().catch((err) => {
|
|
93
|
+
console.error(`Error: ${err.message}`);
|
|
94
|
+
process.exit(1);
|
|
95
|
+
});
|
|
96
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { TaskDispatch } from 'aamp-sdk';
|
|
2
|
+
/**
|
|
3
|
+
* Convert an AAMP TaskDispatch into a prompt string for an ACP agent.
|
|
4
|
+
*/
|
|
5
|
+
export declare function buildPrompt(task: TaskDispatch): string;
|
|
6
|
+
/**
|
|
7
|
+
* Parse an ACP agent response to detect HELP requests and extract file paths.
|
|
8
|
+
* Returns { isHelp, question, output, files }.
|
|
9
|
+
*/
|
|
10
|
+
export declare function parseResponse(output: string): {
|
|
11
|
+
isHelp: boolean;
|
|
12
|
+
question?: string;
|
|
13
|
+
output: string;
|
|
14
|
+
files: string[];
|
|
15
|
+
};
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Convert an AAMP TaskDispatch into a prompt string for an ACP agent.
|
|
3
|
+
*/
|
|
4
|
+
export function buildPrompt(task) {
|
|
5
|
+
const parts = [
|
|
6
|
+
`## AAMP Task`,
|
|
7
|
+
``,
|
|
8
|
+
`Task ID: ${task.taskId}`,
|
|
9
|
+
`From: ${task.from}`,
|
|
10
|
+
`Title: ${task.title}`,
|
|
11
|
+
];
|
|
12
|
+
if (task.bodyText) {
|
|
13
|
+
parts.push(``, `Description:`, task.bodyText);
|
|
14
|
+
}
|
|
15
|
+
if (task.timeoutSecs) {
|
|
16
|
+
parts.push(``, `Deadline: ${task.timeoutSecs}s`);
|
|
17
|
+
}
|
|
18
|
+
parts.push(``, `Please complete this task and output your result directly.`, `If you cannot complete the task and need more information, start your response with "HELP:" followed by your question.`, ``, `If you create any files as part of this task, list each file path at the end of your response in this exact format:`, `FILE:/absolute/path/to/file`);
|
|
19
|
+
return parts.join('\n');
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Parse an ACP agent response to detect HELP requests and extract file paths.
|
|
23
|
+
* Returns { isHelp, question, output, files }.
|
|
24
|
+
*/
|
|
25
|
+
export function parseResponse(output) {
|
|
26
|
+
const trimmed = output.trim();
|
|
27
|
+
if (trimmed.startsWith('HELP:')) {
|
|
28
|
+
return {
|
|
29
|
+
isHelp: true,
|
|
30
|
+
question: trimmed.slice(5).trim(),
|
|
31
|
+
output: '',
|
|
32
|
+
files: [],
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
// Extract FILE: lines
|
|
36
|
+
const files = [];
|
|
37
|
+
const lines = trimmed.split('\n');
|
|
38
|
+
const outputLines = [];
|
|
39
|
+
for (const line of lines) {
|
|
40
|
+
if (line.startsWith('FILE:')) {
|
|
41
|
+
const path = line.slice(5).trim();
|
|
42
|
+
if (path)
|
|
43
|
+
files.push(path);
|
|
44
|
+
}
|
|
45
|
+
else {
|
|
46
|
+
outputLines.push(line);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
return { isHelp: false, output: outputLines.join('\n').trim(), files };
|
|
50
|
+
}
|
|
51
|
+
//# sourceMappingURL=prompt-builder.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"prompt-builder.js","sourceRoot":"","sources":["../src/prompt-builder.ts"],"names":[],"mappings":"AAEA;;GAEG;AACH,MAAM,UAAU,WAAW,CAAC,IAAkB;IAC5C,MAAM,KAAK,GAAG;QACZ,cAAc;QACd,EAAE;QACF,YAAY,IAAI,CAAC,MAAM,EAAE;QACzB,SAAS,IAAI,CAAC,IAAI,EAAE;QACpB,UAAU,IAAI,CAAC,KAAK,EAAE;KACvB,CAAA;IAED,IAAI,IAAI,CAAC,QAAQ,EAAE,CAAC;QAClB,KAAK,CAAC,IAAI,CAAC,EAAE,EAAE,cAAc,EAAE,IAAI,CAAC,QAAQ,CAAC,CAAA;IAC/C,CAAC;IAED,IAAI,IAAI,CAAC,WAAW,EAAE,CAAC;QACrB,KAAK,CAAC,IAAI,CAAC,EAAE,EAAE,aAAa,IAAI,CAAC,WAAW,GAAG,CAAC,CAAA;IAClD,CAAC;IAED,KAAK,CAAC,IAAI,CACR,EAAE,EACF,4DAA4D,EAC5D,wHAAwH,EACxH,EAAE,EACF,qHAAqH,EACrH,6BAA6B,CAC9B,CAAA;IAED,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;AACzB,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,aAAa,CAAC,MAAc;IAM1C,MAAM,OAAO,GAAG,MAAM,CAAC,IAAI,EAAE,CAAA;IAC7B,IAAI,OAAO,CAAC,UAAU,CAAC,OAAO,CAAC,EAAE,CAAC;QAChC,OAAO;YACL,MAAM,EAAE,IAAI;YACZ,QAAQ,EAAE,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE;YACjC,MAAM,EAAE,EAAE;YACV,KAAK,EAAE,EAAE;SACV,CAAA;IACH,CAAC;IAED,sBAAsB;IACtB,MAAM,KAAK,GAAa,EAAE,CAAA;IAC1B,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,CAAA;IACjC,MAAM,WAAW,GAAa,EAAE,CAAA;IAChC,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,IAAI,IAAI,CAAC,UAAU,CAAC,OAAO,CAAC,EAAE,CAAC;YAC7B,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAA;YACjC,IAAI,IAAI;gBAAE,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;QAC5B,CAAC;aAAM,CAAC;YACN,WAAW,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;QACxB,CAAC;IACH,CAAC;IAED,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,WAAW,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,KAAK,EAAE,CAAA;AACxE,CAAC"}
|
package/package.json
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "aamp-acp-bridge",
|
|
3
|
+
"files": [
|
|
4
|
+
"dist"
|
|
5
|
+
],
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"version": "0.1.4",
|
|
8
|
+
"description": "Bridge ACP-compatible agents (Claude, Codex, Gemini, etc.) into the AAMP email network — zero code, config-driven",
|
|
9
|
+
"type": "module",
|
|
10
|
+
"main": "dist/index.js",
|
|
11
|
+
"bin": {
|
|
12
|
+
"aamp-acp-bridge": "dist/index.js"
|
|
13
|
+
},
|
|
14
|
+
"scripts": {
|
|
15
|
+
"dev": "tsx src/index.ts",
|
|
16
|
+
"build": "tsc",
|
|
17
|
+
"start": "node dist/index.js"
|
|
18
|
+
},
|
|
19
|
+
"dependencies": {
|
|
20
|
+
"aamp-sdk": "^0.1.0",
|
|
21
|
+
"zod": "^3.22.4"
|
|
22
|
+
},
|
|
23
|
+
"devDependencies": {
|
|
24
|
+
"@types/node": "^20.11.30",
|
|
25
|
+
"tsx": "^4.7.1",
|
|
26
|
+
"typescript": "^5.4.3"
|
|
27
|
+
},
|
|
28
|
+
"peerDependencies": {
|
|
29
|
+
"acpx": ">=0.1.0"
|
|
30
|
+
},
|
|
31
|
+
"peerDependenciesMeta": {
|
|
32
|
+
"acpx": { "optional": true }
|
|
33
|
+
}
|
|
34
|
+
}
|