@aura-labs-ai/scout 0.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/README.md +237 -0
- package/bin/scout-cli.js +325 -0
- package/examples/basic-purchase.js +112 -0
- package/examples/protocol-integration.js +294 -0
- package/package.json +63 -0
- package/src/activity.js +420 -0
- package/src/ap2/mandates.js +366 -0
- package/src/client.js +217 -0
- package/src/errors.js +66 -0
- package/src/index.js +655 -0
- package/src/intent-session.js +233 -0
- package/src/key-manager.js +204 -0
- package/src/key-manager.test.js +293 -0
- package/src/mcp/client.js +458 -0
- package/src/session.js +603 -0
- package/src/tap/visa.js +345 -0
- package/src/tests/README.md +157 -0
- package/src/tests/ap2-mandates.test.js +413 -0
- package/src/tests/intent-session.test.js +292 -0
- package/src/tests/mcp-client.test.js +224 -0
- package/src/tests/ping.test.js +282 -0
- package/src/tests/run-protocol-tests.js +57 -0
- package/src/tests/scenarios/ap2-scenarios.test.js +422 -0
- package/src/tests/scenarios/index.js +60 -0
- package/src/tests/scenarios/integration-scenarios.test.js +514 -0
- package/src/tests/scenarios/mcp-scenarios.test.js +419 -0
- package/src/tests/scenarios/tap-scenarios.test.js +461 -0
- package/src/tests/visa-tap.test.js +376 -0
package/README.md
ADDED
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
# @aura-labs-ai/scout
|
|
2
|
+
|
|
3
|
+
Scout SDK for AURA ā Build buying agents that participate in agentic commerce.
|
|
4
|
+
|
|
5
|
+
## What is a Scout?
|
|
6
|
+
|
|
7
|
+
A Scout is a user-sovereign buying agent in the AURA ecosystem. Scouts:
|
|
8
|
+
- Express purchase intent in natural language
|
|
9
|
+
- Discover products through AURA Core's neutral broker
|
|
10
|
+
- Evaluate offers against user-defined constraints
|
|
11
|
+
- Commit to transactions while preserving privacy
|
|
12
|
+
|
|
13
|
+
## Installation
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
npm install @aura-labs-ai/scout
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Quick Start
|
|
20
|
+
|
|
21
|
+
```javascript
|
|
22
|
+
import { createScout } from '@aura-labs-ai/scout';
|
|
23
|
+
|
|
24
|
+
// Zero-config ā auto-generates Ed25519 identity and registers with Core
|
|
25
|
+
const scout = createScout();
|
|
26
|
+
await scout.ready();
|
|
27
|
+
|
|
28
|
+
// Express purchase intent with constraints
|
|
29
|
+
const session = await scout.intent('I need 500 widgets', {
|
|
30
|
+
maxBudget: 50000,
|
|
31
|
+
deliveryBy: new Date('2026-03-01'),
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
// Wait for offers (polling)
|
|
35
|
+
const offers = await session.waitForOffers();
|
|
36
|
+
|
|
37
|
+
// Commit to best offer that meets constraints
|
|
38
|
+
if (session.bestOffer) {
|
|
39
|
+
const tx = await session.commit(session.bestOffer.id);
|
|
40
|
+
console.log('Transaction:', tx.id);
|
|
41
|
+
}
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## CLI Tool
|
|
45
|
+
|
|
46
|
+
The SDK includes a CLI for testing:
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
# Interactive mode (zero-config, uses Ed25519 keys)
|
|
50
|
+
npx @aura-labs-ai/scout
|
|
51
|
+
|
|
52
|
+
# Single intent mode
|
|
53
|
+
npx @aura-labs-ai/scout --intent "I need office supplies" --max-budget 500
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
**HTTPS Enforcement:** The CLI requires HTTPS for all Core API connections. Plaintext HTTP is only permitted for `localhost` and `127.0.0.1` during local development. Use `--core-url https://...` or set `AURA_CORE_URL` with an HTTPS URL.
|
|
57
|
+
|
|
58
|
+
## Constraint Engine
|
|
59
|
+
|
|
60
|
+
Define hard constraints (must be met) and soft preferences (influence ranking):
|
|
61
|
+
|
|
62
|
+
```javascript
|
|
63
|
+
const session = await scout.intent('Buy enterprise software licenses', {
|
|
64
|
+
// Hard constraints - offers that don't meet these are filtered out
|
|
65
|
+
maxBudget: 100000,
|
|
66
|
+
deliveryBy: new Date('2026-06-01'),
|
|
67
|
+
hardConstraints: [
|
|
68
|
+
{ field: 'compliance', operator: 'eq', value: 'SOC2' },
|
|
69
|
+
],
|
|
70
|
+
|
|
71
|
+
// Soft preferences - influence offer scoring
|
|
72
|
+
softPreferences: [
|
|
73
|
+
{ field: 'support', operator: 'eq', value: '24/7', weight: 10 },
|
|
74
|
+
{ field: 'rating', operator: 'gte', value: 4.5, weight: 5 },
|
|
75
|
+
],
|
|
76
|
+
});
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### Constraint Operators
|
|
80
|
+
|
|
81
|
+
Only the following operators are accepted. Unknown operators are rejected (fail-closed) to prevent constraint bypass:
|
|
82
|
+
|
|
83
|
+
| Operator | Description |
|
|
84
|
+
|----------|-------------|
|
|
85
|
+
| `eq` | Equal to |
|
|
86
|
+
| `ne` | Not equal to |
|
|
87
|
+
| `gt` | Greater than |
|
|
88
|
+
| `gte` | Greater than or equal |
|
|
89
|
+
| `lt` | Less than |
|
|
90
|
+
| `lte` | Less than or equal |
|
|
91
|
+
| `contains` | String contains |
|
|
92
|
+
| `in` | Value in array |
|
|
93
|
+
|
|
94
|
+
## API Reference
|
|
95
|
+
|
|
96
|
+
### `createScout(config)`
|
|
97
|
+
|
|
98
|
+
Create a new Scout instance. Authentication is handled via Ed25519 public key registration ā no API keys required.
|
|
99
|
+
|
|
100
|
+
```javascript
|
|
101
|
+
const scout = createScout({
|
|
102
|
+
coreUrl: 'https://aura-labsai-production.up.railway.app', // optional, defaults to production
|
|
103
|
+
timeout: 30000, // optional, ms
|
|
104
|
+
storage: customStorageAdapter, // optional, defaults to in-memory
|
|
105
|
+
constraints: {}, // optional, default constraints
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
// Initialize and register with AURA Core (idempotent)
|
|
109
|
+
await scout.ready();
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
### `scout.ping()`
|
|
113
|
+
|
|
114
|
+
Read-only connectivity check. Verifies Core is reachable and its dependencies are healthy. Does not require `ready()` ā works on a freshly created instance. No auth headers sent.
|
|
115
|
+
|
|
116
|
+
```javascript
|
|
117
|
+
const health = await scout.ping();
|
|
118
|
+
|
|
119
|
+
// When Core is healthy:
|
|
120
|
+
// { status: 'ok', core: { status: 'ready', checks: { database: {...}, redis: {...} } }, latency_ms: 42, timestamp: '...' }
|
|
121
|
+
|
|
122
|
+
// When Core is alive but degraded (503):
|
|
123
|
+
// { status: 'degraded', core: { status: 'not_ready', checks: {...} }, latency_ms: 105, timestamp: '...' }
|
|
124
|
+
|
|
125
|
+
// When Core is unreachable:
|
|
126
|
+
// { status: 'error', code: 'CORE_UNREACHABLE', message: '...', latency_ms: 30000, timestamp: '...' }
|
|
127
|
+
|
|
128
|
+
// When Core times out:
|
|
129
|
+
// { status: 'error', code: 'CORE_TIMEOUT', message: '...', latency_ms: 30000, timestamp: '...' }
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
Activity events: `ping.success` (Core responded), `ping.failed` (network error or timeout). Summary counters available via `scout.activity.getSummary().ping`.
|
|
133
|
+
|
|
134
|
+
### `scout.intent(text, options)`
|
|
135
|
+
|
|
136
|
+
Create a commerce session with purchase intent.
|
|
137
|
+
|
|
138
|
+
```javascript
|
|
139
|
+
const session = await scout.intent('I want to buy...', {
|
|
140
|
+
maxBudget: number,
|
|
141
|
+
deliveryBy: Date,
|
|
142
|
+
hardConstraints: Constraint[],
|
|
143
|
+
softPreferences: Constraint[],
|
|
144
|
+
});
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
### `session.waitForOffers(options)`
|
|
148
|
+
|
|
149
|
+
Poll for offers until available.
|
|
150
|
+
|
|
151
|
+
```javascript
|
|
152
|
+
const offers = await session.waitForOffers({
|
|
153
|
+
timeout: 30000, // max wait time
|
|
154
|
+
interval: 2000, // poll interval
|
|
155
|
+
});
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
### `session.commit(offerId)`
|
|
159
|
+
|
|
160
|
+
Commit to an offer.
|
|
161
|
+
|
|
162
|
+
```javascript
|
|
163
|
+
const transaction = await session.commit(offer.id);
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
### `session.validOffers`
|
|
167
|
+
|
|
168
|
+
Get offers that meet all hard constraints.
|
|
169
|
+
|
|
170
|
+
### `session.bestOffer`
|
|
171
|
+
|
|
172
|
+
Get highest-scoring valid offer.
|
|
173
|
+
|
|
174
|
+
## Error Handling
|
|
175
|
+
|
|
176
|
+
```javascript
|
|
177
|
+
import { ScoutError, AuthenticationError, SessionError } from '@aura-labs-ai/scout';
|
|
178
|
+
|
|
179
|
+
try {
|
|
180
|
+
await scout.intent('...');
|
|
181
|
+
} catch (error) {
|
|
182
|
+
if (error instanceof AuthenticationError) {
|
|
183
|
+
console.log('Invalid API key');
|
|
184
|
+
} else if (error instanceof SessionError) {
|
|
185
|
+
console.log('Session error:', error.message);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
## Key Storage
|
|
191
|
+
|
|
192
|
+
Ed25519 private keys are persisted across restarts using pluggable storage adapters from `@aura-labs-ai/sdk-common`. The `createStorage()` factory auto-detects the best adapter for the current platform:
|
|
193
|
+
|
|
194
|
+
| Platform | Adapter | Details |
|
|
195
|
+
|----------|---------|---------|
|
|
196
|
+
| **macOS** | `KeychainStorage` | Hardware-backed encryption at rest via Secure Enclave on Apple Silicon. Uses the `security` CLI ā zero native dependencies. |
|
|
197
|
+
| **Linux / Windows** | `FileStorage` | JSON file at `~/.aura/keys.json` with `0600` permissions (owner read/write only). |
|
|
198
|
+
| **Testing** | `MemoryStorage` | In-memory, ephemeral. No persistence. |
|
|
199
|
+
|
|
200
|
+
```javascript
|
|
201
|
+
import { createScout, createStorage } from '@aura-labs-ai/scout';
|
|
202
|
+
|
|
203
|
+
// Auto-detect (recommended)
|
|
204
|
+
const scout = createScout({ storage: createStorage() });
|
|
205
|
+
|
|
206
|
+
// Force file-based storage
|
|
207
|
+
const scout = createScout({ storage: createStorage({ type: 'file' }) });
|
|
208
|
+
|
|
209
|
+
// Force Keychain (macOS only ā throws on other platforms)
|
|
210
|
+
const scout = createScout({ storage: createStorage({ type: 'keychain' }) });
|
|
211
|
+
|
|
212
|
+
// Custom file path
|
|
213
|
+
const scout = createScout({ storage: createStorage({ type: 'file', path: '/custom/keys.json' }) });
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
Override the default file path with the `AURA_KEY_PATH` environment variable.
|
|
217
|
+
|
|
218
|
+
## Environment Variables
|
|
219
|
+
|
|
220
|
+
| Variable | Description | Default |
|
|
221
|
+
|----------|-------------|---------|
|
|
222
|
+
| `AURA_CORE_URL` | Core API URL (optional) | `https://aura-labsai-production.up.railway.app` |
|
|
223
|
+
| `AURA_KEY_PATH` | Custom path for file-based key storage | `~/.aura/keys.json` |
|
|
224
|
+
|
|
225
|
+
## Authentication
|
|
226
|
+
|
|
227
|
+
Scout uses **Ed25519 public key cryptography** for identity. When you call `scout.ready()`:
|
|
228
|
+
1. An Ed25519 key pair is auto-generated (or loaded from storage)
|
|
229
|
+
2. The Scout registers with AURA Core via `POST /agents/register` using proof-of-possession (signed request)
|
|
230
|
+
3. Core assigns an agent ID, which is persisted for future sessions
|
|
231
|
+
4. All subsequent requests are signed with the private key for identity verification
|
|
232
|
+
|
|
233
|
+
No API keys or other credentials are required.
|
|
234
|
+
|
|
235
|
+
## License
|
|
236
|
+
|
|
237
|
+
Business Source License 1.1 ā See [LICENSE](LICENSE) for details.
|
package/bin/scout-cli.js
ADDED
|
@@ -0,0 +1,325 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* AURA Scout CLI
|
|
5
|
+
*
|
|
6
|
+
* Interactive command-line tool for testing Scout SDK and Core API.
|
|
7
|
+
*
|
|
8
|
+
* Usage:
|
|
9
|
+
* npx @aura-labs-ai/scout --api-key YOUR_KEY
|
|
10
|
+
* npx @aura-labs-ai/scout --api-key YOUR_KEY --intent "I need 100 widgets"
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { createScout } from '../src/index.js';
|
|
14
|
+
import { createInterface } from 'readline';
|
|
15
|
+
|
|
16
|
+
// Parse command line arguments
|
|
17
|
+
const args = process.argv.slice(2);
|
|
18
|
+
const flags = {};
|
|
19
|
+
for (let i = 0; i < args.length; i++) {
|
|
20
|
+
if (args[i].startsWith('--')) {
|
|
21
|
+
const key = args[i].slice(2);
|
|
22
|
+
const value = args[i + 1] && !args[i + 1].startsWith('--') ? args[++i] : true;
|
|
23
|
+
flags[key] = value;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// Configuration
|
|
28
|
+
const config = {
|
|
29
|
+
apiKey: flags['api-key'] || process.env.AURA_API_KEY,
|
|
30
|
+
coreUrl: flags['core-url'] || process.env.AURA_CORE_URL || 'https://aura-labsai-production.up.railway.app',
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
// SECURITY: Enforce HTTPS for all non-localhost connections
|
|
34
|
+
const coreUrlLower = config.coreUrl.toLowerCase();
|
|
35
|
+
const isLocalhost = coreUrlLower.startsWith('http://localhost') || coreUrlLower.startsWith('http://127.0.0.1');
|
|
36
|
+
if (!coreUrlLower.startsWith('https://') && !isLocalhost) {
|
|
37
|
+
console.error('Error: Core URL must use HTTPS. Plaintext HTTP is only allowed for localhost development.');
|
|
38
|
+
console.error(` Provided: ${config.coreUrl}`);
|
|
39
|
+
console.error(' Use --core-url https://... or set AURA_CORE_URL with an HTTPS URL.');
|
|
40
|
+
process.exit(1);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Help text
|
|
44
|
+
if (flags.help || flags.h) {
|
|
45
|
+
console.log(`
|
|
46
|
+
AURA Scout CLI - Test buying agent interactions
|
|
47
|
+
|
|
48
|
+
Usage:
|
|
49
|
+
scout-cli [options]
|
|
50
|
+
|
|
51
|
+
Options:
|
|
52
|
+
--api-key KEY Your AURA API key (or set AURA_API_KEY env var)
|
|
53
|
+
--core-url URL Core API URL (default: production)
|
|
54
|
+
--intent "TEXT" Run single intent and exit
|
|
55
|
+
--max-budget N Set maximum budget constraint
|
|
56
|
+
--delivery-by D Set delivery deadline (ISO date)
|
|
57
|
+
--help, -h Show this help
|
|
58
|
+
|
|
59
|
+
Interactive Mode:
|
|
60
|
+
Start without --intent for interactive REPL mode.
|
|
61
|
+
|
|
62
|
+
Examples:
|
|
63
|
+
scout-cli --api-key sk_test_123 --intent "I need office supplies"
|
|
64
|
+
scout-cli --api-key sk_test_123 --intent "Buy 500 widgets" --max-budget 50000
|
|
65
|
+
AURA_API_KEY=sk_test_123 scout-cli
|
|
66
|
+
`);
|
|
67
|
+
process.exit(0);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Validate API key
|
|
71
|
+
if (!config.apiKey) {
|
|
72
|
+
console.error('Error: API key required. Use --api-key or set AURA_API_KEY');
|
|
73
|
+
process.exit(1);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Colors for terminal output
|
|
77
|
+
const colors = {
|
|
78
|
+
reset: '\x1b[0m',
|
|
79
|
+
bright: '\x1b[1m',
|
|
80
|
+
dim: '\x1b[2m',
|
|
81
|
+
green: '\x1b[32m',
|
|
82
|
+
yellow: '\x1b[33m',
|
|
83
|
+
blue: '\x1b[34m',
|
|
84
|
+
cyan: '\x1b[36m',
|
|
85
|
+
red: '\x1b[31m',
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
function log(color, ...args) {
|
|
89
|
+
console.log(color, ...args, colors.reset);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function printJSON(obj) {
|
|
93
|
+
console.log(JSON.stringify(obj, null, 2));
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Initialize Scout
|
|
97
|
+
let scout;
|
|
98
|
+
let currentSession = null;
|
|
99
|
+
|
|
100
|
+
async function init() {
|
|
101
|
+
log(colors.cyan, '\nš AURA Scout CLI v0.1.0');
|
|
102
|
+
log(colors.dim, ` Core: ${config.coreUrl}`);
|
|
103
|
+
|
|
104
|
+
scout = createScout(config);
|
|
105
|
+
|
|
106
|
+
log(colors.dim, ' Registering with Core...');
|
|
107
|
+
const { scoutId } = await scout.register();
|
|
108
|
+
log(colors.green, ` ā Registered as ${scoutId}\n`);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Single intent mode
|
|
112
|
+
async function runSingleIntent(intentText) {
|
|
113
|
+
const options = {};
|
|
114
|
+
if (flags['max-budget']) options.maxBudget = parseFloat(flags['max-budget']);
|
|
115
|
+
if (flags['delivery-by']) options.deliveryBy = new Date(flags['delivery-by']);
|
|
116
|
+
|
|
117
|
+
log(colors.blue, `\nš Intent: "${intentText}"`);
|
|
118
|
+
if (options.maxBudget) log(colors.dim, ` Budget: $${options.maxBudget}`);
|
|
119
|
+
if (options.deliveryBy) log(colors.dim, ` Deliver by: ${options.deliveryBy.toDateString()}`);
|
|
120
|
+
|
|
121
|
+
try {
|
|
122
|
+
const session = await scout.intent(intentText, options);
|
|
123
|
+
log(colors.green, `\nā Session created: ${session.id}`);
|
|
124
|
+
log(colors.dim, ` Status: ${session.status}`);
|
|
125
|
+
|
|
126
|
+
if (session.intent?.parsed) {
|
|
127
|
+
log(colors.dim, ' Parsed intent:');
|
|
128
|
+
printJSON(session.intent.parsed);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
log(colors.yellow, '\nā³ Waiting for offers...');
|
|
132
|
+
const offers = await session.waitForOffers({ timeout: 60000 });
|
|
133
|
+
|
|
134
|
+
if (offers.length === 0) {
|
|
135
|
+
log(colors.yellow, ' No offers received');
|
|
136
|
+
} else {
|
|
137
|
+
log(colors.green, `\nā Received ${offers.length} offer(s):\n`);
|
|
138
|
+
for (const offer of offers) {
|
|
139
|
+
log(colors.bright, ` ${offer.beaconName || offer.beaconId}`);
|
|
140
|
+
log(colors.dim, ` $${offer.totalPrice} | Delivery: ${offer.deliveryDate || 'TBD'}`);
|
|
141
|
+
log(colors.dim, ` Score: ${offer.score.toFixed(1)} | Meets constraints: ${offer.meetsConstraints}`);
|
|
142
|
+
if (!offer.meetsConstraints) {
|
|
143
|
+
log(colors.red, ` Violations: ${offer.constraintViolations.join(', ')}`);
|
|
144
|
+
}
|
|
145
|
+
console.log();
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
if (session.bestOffer) {
|
|
149
|
+
log(colors.green, ` Best offer: ${session.bestOffer.beaconName || session.bestOffer.beaconId}`);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
} catch (error) {
|
|
153
|
+
log(colors.red, `\nā Error: ${error.message}`);
|
|
154
|
+
process.exit(1);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// Interactive REPL mode
|
|
159
|
+
async function runInteractive() {
|
|
160
|
+
const rl = createInterface({
|
|
161
|
+
input: process.stdin,
|
|
162
|
+
output: process.stdout,
|
|
163
|
+
prompt: colors.cyan + 'scout> ' + colors.reset,
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
console.log(`
|
|
167
|
+
${colors.bright}Commands:${colors.reset}
|
|
168
|
+
intent <text> Create session with purchase intent
|
|
169
|
+
status Show current session status
|
|
170
|
+
offers List current offers
|
|
171
|
+
commit <id> Commit to offer by ID
|
|
172
|
+
cancel Cancel current session
|
|
173
|
+
budget <amount> Set max budget for next session
|
|
174
|
+
help Show this help
|
|
175
|
+
exit Exit CLI
|
|
176
|
+
`);
|
|
177
|
+
|
|
178
|
+
let constraints = {};
|
|
179
|
+
|
|
180
|
+
rl.prompt();
|
|
181
|
+
|
|
182
|
+
rl.on('line', async (line) => {
|
|
183
|
+
const [command, ...rest] = line.trim().split(' ');
|
|
184
|
+
const arg = rest.join(' ');
|
|
185
|
+
|
|
186
|
+
try {
|
|
187
|
+
switch (command.toLowerCase()) {
|
|
188
|
+
case 'intent':
|
|
189
|
+
if (!arg) {
|
|
190
|
+
log(colors.red, 'Usage: intent <description>');
|
|
191
|
+
break;
|
|
192
|
+
}
|
|
193
|
+
log(colors.blue, `\nš Creating session: "${arg}"`);
|
|
194
|
+
currentSession = await scout.intent(arg, constraints);
|
|
195
|
+
log(colors.green, `ā Session: ${currentSession.id}`);
|
|
196
|
+
log(colors.dim, ` Status: ${currentSession.status}`);
|
|
197
|
+
break;
|
|
198
|
+
|
|
199
|
+
case 'status':
|
|
200
|
+
if (!currentSession) {
|
|
201
|
+
log(colors.yellow, 'No active session. Use "intent <text>" first.');
|
|
202
|
+
break;
|
|
203
|
+
}
|
|
204
|
+
await currentSession.refresh();
|
|
205
|
+
log(colors.cyan, `\nSession: ${currentSession.id}`);
|
|
206
|
+
log(colors.dim, `Status: ${currentSession.status}`);
|
|
207
|
+
log(colors.dim, `Active: ${currentSession.isActive}`);
|
|
208
|
+
break;
|
|
209
|
+
|
|
210
|
+
case 'offers':
|
|
211
|
+
if (!currentSession) {
|
|
212
|
+
log(colors.yellow, 'No active session.');
|
|
213
|
+
break;
|
|
214
|
+
}
|
|
215
|
+
log(colors.yellow, 'ā³ Fetching offers...');
|
|
216
|
+
const offers = await currentSession.waitForOffers({ timeout: 30000 });
|
|
217
|
+
if (offers.length === 0) {
|
|
218
|
+
log(colors.yellow, 'No offers yet.');
|
|
219
|
+
} else {
|
|
220
|
+
log(colors.green, `\n${offers.length} offer(s):\n`);
|
|
221
|
+
for (const offer of offers) {
|
|
222
|
+
console.log(` [${offer.id.slice(0, 8)}] ${offer.beaconName || 'Unknown'} - $${offer.totalPrice} (score: ${offer.score.toFixed(1)})`);
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
break;
|
|
226
|
+
|
|
227
|
+
case 'commit':
|
|
228
|
+
if (!currentSession) {
|
|
229
|
+
log(colors.yellow, 'No active session.');
|
|
230
|
+
break;
|
|
231
|
+
}
|
|
232
|
+
if (!arg) {
|
|
233
|
+
log(colors.red, 'Usage: commit <offer-id>');
|
|
234
|
+
break;
|
|
235
|
+
}
|
|
236
|
+
log(colors.yellow, `ā³ Committing to offer ${arg}...`);
|
|
237
|
+
const tx = await currentSession.commit(arg);
|
|
238
|
+
log(colors.green, `ā Transaction: ${tx.id}`);
|
|
239
|
+
break;
|
|
240
|
+
|
|
241
|
+
case 'cancel':
|
|
242
|
+
if (!currentSession) {
|
|
243
|
+
log(colors.yellow, 'No active session.');
|
|
244
|
+
break;
|
|
245
|
+
}
|
|
246
|
+
await currentSession.cancel();
|
|
247
|
+
log(colors.green, 'ā Session cancelled');
|
|
248
|
+
currentSession = null;
|
|
249
|
+
break;
|
|
250
|
+
|
|
251
|
+
case 'budget':
|
|
252
|
+
if (!arg) {
|
|
253
|
+
log(colors.dim, `Current max budget: ${constraints.maxBudget || 'not set'}`);
|
|
254
|
+
} else {
|
|
255
|
+
constraints.maxBudget = parseFloat(arg);
|
|
256
|
+
log(colors.green, `ā Max budget set to $${constraints.maxBudget}`);
|
|
257
|
+
}
|
|
258
|
+
break;
|
|
259
|
+
|
|
260
|
+
case 'delivery':
|
|
261
|
+
if (!arg) {
|
|
262
|
+
log(colors.dim, `Delivery deadline: ${constraints.deliveryBy || 'not set'}`);
|
|
263
|
+
} else {
|
|
264
|
+
constraints.deliveryBy = new Date(arg);
|
|
265
|
+
log(colors.green, `ā Delivery deadline set to ${constraints.deliveryBy.toDateString()}`);
|
|
266
|
+
}
|
|
267
|
+
break;
|
|
268
|
+
|
|
269
|
+
case 'help':
|
|
270
|
+
console.log(`
|
|
271
|
+
${colors.bright}Commands:${colors.reset}
|
|
272
|
+
intent <text> Create session with purchase intent
|
|
273
|
+
status Show current session status
|
|
274
|
+
offers List current offers (polls until available)
|
|
275
|
+
commit <id> Commit to offer by ID
|
|
276
|
+
cancel Cancel current session
|
|
277
|
+
budget <amount> Set max budget for next session
|
|
278
|
+
delivery <date> Set delivery deadline (e.g., "2026-03-01")
|
|
279
|
+
help Show this help
|
|
280
|
+
exit Exit CLI
|
|
281
|
+
`);
|
|
282
|
+
break;
|
|
283
|
+
|
|
284
|
+
case 'exit':
|
|
285
|
+
case 'quit':
|
|
286
|
+
log(colors.dim, 'Goodbye!');
|
|
287
|
+
rl.close();
|
|
288
|
+
process.exit(0);
|
|
289
|
+
break;
|
|
290
|
+
|
|
291
|
+
case '':
|
|
292
|
+
break;
|
|
293
|
+
|
|
294
|
+
default:
|
|
295
|
+
log(colors.red, `Unknown command: ${command}. Type "help" for commands.`);
|
|
296
|
+
}
|
|
297
|
+
} catch (error) {
|
|
298
|
+
log(colors.red, `Error: ${error.message}`);
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
rl.prompt();
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
rl.on('close', () => {
|
|
305
|
+
process.exit(0);
|
|
306
|
+
});
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
// Main
|
|
310
|
+
async function main() {
|
|
311
|
+
try {
|
|
312
|
+
await init();
|
|
313
|
+
|
|
314
|
+
if (flags.intent) {
|
|
315
|
+
await runSingleIntent(flags.intent);
|
|
316
|
+
} else {
|
|
317
|
+
await runInteractive();
|
|
318
|
+
}
|
|
319
|
+
} catch (error) {
|
|
320
|
+
log(colors.red, `Fatal error: ${error.message}`);
|
|
321
|
+
process.exit(1);
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
main();
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Basic Purchase Example
|
|
3
|
+
*
|
|
4
|
+
* Demonstrates the core Scout SDK flow:
|
|
5
|
+
* 1. Initialize Scout with API key
|
|
6
|
+
* 2. Express purchase intent with constraints
|
|
7
|
+
* 3. Wait for offers
|
|
8
|
+
* 4. Evaluate and commit to best offer
|
|
9
|
+
*
|
|
10
|
+
* Run with: AURA_API_KEY=your-key node examples/basic-purchase.js
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { createScout } from '../src/index.js';
|
|
14
|
+
|
|
15
|
+
async function main() {
|
|
16
|
+
// Initialize Scout
|
|
17
|
+
const scout = createScout({
|
|
18
|
+
apiKey: process.env.AURA_API_KEY,
|
|
19
|
+
// coreUrl: 'http://localhost:3000', // Uncomment for local development
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
console.log('š Scout initialized');
|
|
23
|
+
console.log(` Core URL: ${scout.coreUrl}`);
|
|
24
|
+
|
|
25
|
+
// Express purchase intent with constraints
|
|
26
|
+
console.log('\nš Creating session with intent...');
|
|
27
|
+
|
|
28
|
+
const session = await scout.intent(
|
|
29
|
+
'I need 500 industrial widgets for manufacturing, grade A quality',
|
|
30
|
+
{
|
|
31
|
+
maxBudget: 50000, // $50,000 max
|
|
32
|
+
deliveryBy: new Date('2026-03-01'), // Need by March 1st
|
|
33
|
+
hardConstraints: [
|
|
34
|
+
// Must be grade A
|
|
35
|
+
{ field: 'grade', operator: 'eq', value: 'A' },
|
|
36
|
+
],
|
|
37
|
+
softPreferences: [
|
|
38
|
+
// Prefer sustainable suppliers (+10 to score)
|
|
39
|
+
{ field: 'sustainable', operator: 'eq', value: true, weight: 10 },
|
|
40
|
+
// Prefer suppliers with high ratings (+5 per point above 4.0)
|
|
41
|
+
{ field: 'rating', operator: 'gte', value: 4.0, weight: 5 },
|
|
42
|
+
],
|
|
43
|
+
}
|
|
44
|
+
);
|
|
45
|
+
|
|
46
|
+
console.log(`ā Session created: ${session.id}`);
|
|
47
|
+
console.log(` Status: ${session.status}`);
|
|
48
|
+
|
|
49
|
+
// Wait for offers from Beacons
|
|
50
|
+
console.log('\nā³ Waiting for offers...');
|
|
51
|
+
|
|
52
|
+
try {
|
|
53
|
+
const offers = await session.waitForOffers({
|
|
54
|
+
timeout: 60000, // Wait up to 60 seconds
|
|
55
|
+
interval: 2000, // Poll every 2 seconds
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
console.log(`\nā Received ${offers.length} offer(s)`);
|
|
59
|
+
|
|
60
|
+
// Display all offers
|
|
61
|
+
for (const offer of offers) {
|
|
62
|
+
console.log(`\n š¦ ${offer.beaconName || offer.beaconId}`);
|
|
63
|
+
console.log(` Product: ${offer.product}`);
|
|
64
|
+
console.log(` Price: $${offer.totalPrice.toLocaleString()} (${offer.quantity} Ć $${offer.unitPrice})`);
|
|
65
|
+
console.log(` Delivery: ${offer.deliveryDate || 'TBD'}`);
|
|
66
|
+
console.log(` Score: ${offer.score.toFixed(1)}/100`);
|
|
67
|
+
console.log(` Meets constraints: ${offer.meetsConstraints ? 'ā' : 'ā'}`);
|
|
68
|
+
|
|
69
|
+
if (!offer.meetsConstraints) {
|
|
70
|
+
console.log(` Violations: ${offer.constraintViolations.join(', ')}`);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Get only offers that meet all constraints
|
|
75
|
+
const validOffers = session.validOffers;
|
|
76
|
+
console.log(`\nā ${validOffers.length} offer(s) meet all constraints`);
|
|
77
|
+
|
|
78
|
+
// Get the best offer (highest score among valid offers)
|
|
79
|
+
const bestOffer = session.bestOffer;
|
|
80
|
+
|
|
81
|
+
if (bestOffer) {
|
|
82
|
+
console.log(`\nš Best offer: ${bestOffer.beaconName || bestOffer.beaconId}`);
|
|
83
|
+
console.log(` Total: $${bestOffer.totalPrice.toLocaleString()}`);
|
|
84
|
+
console.log(` Score: ${bestOffer.score.toFixed(1)}/100`);
|
|
85
|
+
|
|
86
|
+
// Commit to the best offer
|
|
87
|
+
console.log('\nš³ Committing to offer...');
|
|
88
|
+
const transaction = await session.commit(bestOffer.id);
|
|
89
|
+
|
|
90
|
+
console.log(`ā Transaction created: ${transaction.id}`);
|
|
91
|
+
console.log(` Status: ${transaction.status}`);
|
|
92
|
+
} else {
|
|
93
|
+
console.log('\nā ļø No offers meet all constraints');
|
|
94
|
+
|
|
95
|
+
// Show why offers were rejected
|
|
96
|
+
for (const offer of offers) {
|
|
97
|
+
if (!offer.meetsConstraints) {
|
|
98
|
+
console.log(` ${offer.beaconName}: ${offer.constraintViolations.join(', ')}`);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
} catch (error) {
|
|
103
|
+
if (error.code === 'TIMEOUT') {
|
|
104
|
+
console.log('\nā ļø Timed out waiting for offers');
|
|
105
|
+
console.log(' This may mean no Beacons matched your intent');
|
|
106
|
+
} else {
|
|
107
|
+
throw error;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
main().catch(console.error);
|