@agirails/sdk 4.4.8 → 4.5.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/builders/DeliveryProofBuilder.d.ts +224 -13
- package/dist/builders/DeliveryProofBuilder.d.ts.map +1 -1
- package/dist/builders/DeliveryProofBuilder.js +247 -13
- package/dist/builders/DeliveryProofBuilder.js.map +1 -1
- package/dist/cli/agirails.d.ts +85 -1
- package/dist/cli/agirails.d.ts.map +1 -1
- package/dist/cli/agirails.js +429 -154
- package/dist/cli/agirails.js.map +1 -1
- package/dist/cli/commands/init.d.ts +54 -0
- package/dist/cli/commands/init.d.ts.map +1 -1
- package/dist/cli/commands/init.js +193 -1
- package/dist/cli/commands/init.js.map +1 -1
- package/dist/cli/commands/receipt.d.ts +70 -2
- package/dist/cli/commands/receipt.d.ts.map +1 -1
- package/dist/cli/commands/receipt.js +218 -3
- package/dist/cli/commands/receipt.js.map +1 -1
- package/dist/cli/commands/test.d.ts +77 -1
- package/dist/cli/commands/test.d.ts.map +1 -1
- package/dist/cli/commands/test.js +264 -2
- package/dist/cli/commands/test.js.map +1 -1
- package/dist/cli/lib/runRequest.d.ts +90 -0
- package/dist/cli/lib/runRequest.d.ts.map +1 -1
- package/dist/cli/lib/runRequest.js +300 -9
- package/dist/cli/lib/runRequest.js.map +1 -1
- package/dist/cli/lib/sentinelReflections.d.ts +111 -0
- package/dist/cli/lib/sentinelReflections.d.ts.map +1 -0
- package/dist/cli/lib/sentinelReflections.js +193 -0
- package/dist/cli/lib/sentinelReflections.js.map +1 -0
- package/dist/delivery/MockDeliveryChannel.d.ts +208 -0
- package/dist/delivery/MockDeliveryChannel.d.ts.map +1 -0
- package/dist/delivery/MockDeliveryChannel.js +445 -0
- package/dist/delivery/MockDeliveryChannel.js.map +1 -0
- package/dist/delivery/RelayDeliveryChannel.d.ts +176 -0
- package/dist/delivery/RelayDeliveryChannel.d.ts.map +1 -0
- package/dist/delivery/RelayDeliveryChannel.js +377 -0
- package/dist/delivery/RelayDeliveryChannel.js.map +1 -0
- package/dist/delivery/channel.d.ts +282 -0
- package/dist/delivery/channel.d.ts.map +1 -0
- package/dist/delivery/channel.js +76 -0
- package/dist/delivery/channel.js.map +1 -0
- package/dist/delivery/channelLog.d.ts +115 -0
- package/dist/delivery/channelLog.d.ts.map +1 -0
- package/dist/delivery/channelLog.js +94 -0
- package/dist/delivery/channelLog.js.map +1 -0
- package/dist/delivery/crypto.d.ts +312 -0
- package/dist/delivery/crypto.d.ts.map +1 -0
- package/dist/delivery/crypto.js +495 -0
- package/dist/delivery/crypto.js.map +1 -0
- package/dist/delivery/eip712.d.ts +248 -0
- package/dist/delivery/eip712.d.ts.map +1 -0
- package/dist/delivery/eip712.js +397 -0
- package/dist/delivery/eip712.js.map +1 -0
- package/dist/delivery/envelopeBuilder.d.ts +531 -0
- package/dist/delivery/envelopeBuilder.d.ts.map +1 -0
- package/dist/delivery/envelopeBuilder.js +832 -0
- package/dist/delivery/envelopeBuilder.js.map +1 -0
- package/dist/delivery/index.d.ts +53 -0
- package/dist/delivery/index.d.ts.map +1 -0
- package/dist/delivery/index.js +143 -0
- package/dist/delivery/index.js.map +1 -0
- package/dist/delivery/keys.d.ts +344 -0
- package/dist/delivery/keys.d.ts.map +1 -0
- package/dist/delivery/keys.js +513 -0
- package/dist/delivery/keys.js.map +1 -0
- package/dist/delivery/nonce-keys.d.ts +93 -0
- package/dist/delivery/nonce-keys.d.ts.map +1 -0
- package/dist/delivery/nonce-keys.js +88 -0
- package/dist/delivery/nonce-keys.js.map +1 -0
- package/dist/delivery/setupBuilder.d.ts +403 -0
- package/dist/delivery/setupBuilder.d.ts.map +1 -0
- package/dist/delivery/setupBuilder.js +554 -0
- package/dist/delivery/setupBuilder.js.map +1 -0
- package/dist/delivery/types.d.ts +722 -0
- package/dist/delivery/types.d.ts.map +1 -0
- package/dist/delivery/types.js +150 -0
- package/dist/delivery/types.js.map +1 -0
- package/dist/delivery/validate.d.ts +288 -0
- package/dist/delivery/validate.d.ts.map +1 -0
- package/dist/delivery/validate.js +648 -0
- package/dist/delivery/validate.js.map +1 -0
- package/dist/level1/Agent.d.ts +130 -0
- package/dist/level1/Agent.d.ts.map +1 -1
- package/dist/level1/Agent.js +248 -0
- package/dist/level1/Agent.js.map +1 -1
- package/dist/level1/types/Options.d.ts +62 -0
- package/dist/level1/types/Options.d.ts.map +1 -1
- package/dist/level1/types/Options.js +22 -0
- package/dist/level1/types/Options.js.map +1 -1
- package/dist/runtime/MockRuntime.d.ts +32 -0
- package/dist/runtime/MockRuntime.d.ts.map +1 -1
- package/dist/runtime/MockRuntime.js +44 -0
- package/dist/runtime/MockRuntime.js.map +1 -1
- package/dist/wallet/aa/BundlerClient.d.ts.map +1 -1
- package/dist/wallet/aa/BundlerClient.js +18 -3
- package/dist/wallet/aa/BundlerClient.js.map +1 -1
- package/dist/wallet/aa/PaymasterClient.d.ts.map +1 -1
- package/dist/wallet/aa/PaymasterClient.js +4 -1
- package/dist/wallet/aa/PaymasterClient.js.map +1 -1
- package/package.json +6 -1
package/dist/cli/agirails.js
CHANGED
|
@@ -2,10 +2,19 @@
|
|
|
2
2
|
/**
|
|
3
3
|
* npx agirails — One Command Entry Point
|
|
4
4
|
*
|
|
5
|
-
* 60-second quickstart: ask 3 questions → generate {slug}.md →
|
|
5
|
+
* 60-second quickstart: ask 3 questions → generate {slug}.md → bootstrap a
|
|
6
|
+
* real testnet wallet (Smart Wallet + 1K test USDC) → run a real Sentinel
|
|
6
7
|
* onboarding request on Base Sepolia → reflection. PRD-event-driven-provider-
|
|
7
8
|
* listening §5.7 replaced the prior MockRuntime earning-loop simulation with
|
|
8
9
|
* a live Level 1 request against the deployed Sentinel agent.
|
|
10
|
+
*
|
|
11
|
+
* FIX-2 (2026-06): the wizard previously wrote a `mock` config with a random
|
|
12
|
+
* address and immediately invoked `runTest({ network: 'testnet' })`. The
|
|
13
|
+
* downstream `resolvePrivateKey` returned `undefined` and ACTPClient crashed.
|
|
14
|
+
* The wizard now runs `runInit({ mode: 'testnet', wallet: 'auto' })` BEFORE
|
|
15
|
+
* `runTest` so the keystore + Smart Wallet exist and the first transaction
|
|
16
|
+
* has a real signer behind it.
|
|
17
|
+
*
|
|
9
18
|
* Re-entrant: if identity already exists, skips onboarding and runs test.
|
|
10
19
|
*
|
|
11
20
|
* @module cli/agirails
|
|
@@ -34,14 +43,17 @@ var __importStar = (this && this.__importStar) || function (mod) {
|
|
|
34
43
|
return result;
|
|
35
44
|
};
|
|
36
45
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
46
|
+
exports.__test = exports.runWizard = exports.buildIdentityFile = exports.hasKeystoreDefault = exports.defaultWizardDeps = void 0;
|
|
37
47
|
const fs = __importStar(require("fs"));
|
|
38
48
|
const readline = __importStar(require("readline"));
|
|
49
|
+
const path = __importStar(require("path"));
|
|
39
50
|
const ethers_1 = require("ethers");
|
|
40
51
|
const output_1 = require("./utils/output");
|
|
41
52
|
const config_1 = require("./utils/config");
|
|
42
53
|
const config_2 = require("./utils/config");
|
|
43
54
|
const slugUtils_1 = require("../config/slugUtils");
|
|
44
55
|
const test_1 = require("./commands/test");
|
|
56
|
+
const init_1 = require("./commands/init");
|
|
45
57
|
const slugUtils_2 = require("../config/slugUtils");
|
|
46
58
|
const agirailsmd_1 = require("../config/agirailsmd");
|
|
47
59
|
const defaults_1 = require("../config/defaults");
|
|
@@ -57,167 +69,401 @@ function ask(rl, question) {
|
|
|
57
69
|
// Service type menu
|
|
58
70
|
// ============================================================================
|
|
59
71
|
const SERVICE_MENU = defaults_1.V4_CONSTRAINTS.KNOWN_SERVICES.map((s, i) => ` ${i + 1}. ${s}`).join('\n');
|
|
72
|
+
/**
|
|
73
|
+
* Production wiring for the wizard. Tests substitute individual fields.
|
|
74
|
+
*/
|
|
75
|
+
function defaultWizardDeps() {
|
|
76
|
+
return {
|
|
77
|
+
collectAnswers: collectAnswersInteractive,
|
|
78
|
+
ensurePassword: ensurePasswordDefault,
|
|
79
|
+
runInit: async (opts, output) => {
|
|
80
|
+
// runInit's signature is `(options, output, cmd?)`. The wizard doesn't
|
|
81
|
+
// have a Commander instance, and `isExplicit(...)` calls inside runInit
|
|
82
|
+
// safely fall back to "not explicit" when `cmd` is undefined, so all
|
|
83
|
+
// CLI-flag values we pass are treated as defaults that AGIRAILS.md /
|
|
84
|
+
// {slug}.md may override. That's exactly what we want during wizard
|
|
85
|
+
// onboarding — the freshly written {slug}.md is the source of truth.
|
|
86
|
+
//
|
|
87
|
+
// runInit's InitOptions interface isn't exported from commands/init.ts.
|
|
88
|
+
// Round-tripping through `unknown` keeps strict mode happy and is the
|
|
89
|
+
// explicitly-sanctioned escape hatch for "I know the shape, the type
|
|
90
|
+
// checker can't see it through the module boundary."
|
|
91
|
+
await (0, init_1.runInit)(opts, output);
|
|
92
|
+
},
|
|
93
|
+
runTest: test_1.runTest,
|
|
94
|
+
hasKeystore: hasKeystoreDefault,
|
|
95
|
+
// process.stdin.isTTY is `true | undefined`. Normalize to boolean so
|
|
96
|
+
// tests can stub without TS narrowing pain.
|
|
97
|
+
isTTY: () => Boolean(process.stdin.isTTY),
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
exports.defaultWizardDeps = defaultWizardDeps;
|
|
101
|
+
/**
|
|
102
|
+
* A keystore is "present" when `.actp/keystore.json` exists OR the user
|
|
103
|
+
* has provided `ACTP_KEYSTORE_BASE64` / `ACTP_PRIVATE_KEY` env vars. Any
|
|
104
|
+
* of those satisfies `resolvePrivateKey()` in `runTest`.
|
|
105
|
+
*/
|
|
106
|
+
function hasKeystoreDefault(projectRoot = process.cwd()) {
|
|
107
|
+
try {
|
|
108
|
+
if (process.env.ACTP_PRIVATE_KEY && process.env.ACTP_PRIVATE_KEY.length > 0)
|
|
109
|
+
return true;
|
|
110
|
+
if (process.env.ACTP_KEYSTORE_BASE64 && process.env.ACTP_KEYSTORE_BASE64.length > 0)
|
|
111
|
+
return true;
|
|
112
|
+
const keystorePath = path.join((0, config_1.getActpDir)(projectRoot), 'keystore.json');
|
|
113
|
+
return fs.existsSync(keystorePath);
|
|
114
|
+
}
|
|
115
|
+
catch {
|
|
116
|
+
return false;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
exports.hasKeystoreDefault = hasKeystoreDefault;
|
|
60
120
|
// ============================================================================
|
|
61
|
-
//
|
|
121
|
+
// Interactive collectors
|
|
62
122
|
// ============================================================================
|
|
63
|
-
async function
|
|
64
|
-
const
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
123
|
+
async function collectAnswersInteractive(output) {
|
|
124
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
125
|
+
try {
|
|
126
|
+
const name = await ask(rl, output_1.fmt.cyan('? ') + 'What does your agent do? (name) ' + output_1.fmt.dim('> '));
|
|
127
|
+
if (!name.trim()) {
|
|
128
|
+
output.error('Agent name is required.');
|
|
129
|
+
return null;
|
|
130
|
+
}
|
|
131
|
+
output.print('');
|
|
132
|
+
output.print(output_1.fmt.dim('Service types:'));
|
|
133
|
+
output.print(SERVICE_MENU);
|
|
134
|
+
const serviceInput = await ask(rl, output_1.fmt.cyan('? ') + 'Pick a service type ' + output_1.fmt.dim('[1-7, or custom] > '));
|
|
135
|
+
const serviceIdx = parseInt(serviceInput, 10);
|
|
136
|
+
const service = (serviceIdx >= 1 && serviceIdx <= defaults_1.V4_CONSTRAINTS.KNOWN_SERVICES.length)
|
|
137
|
+
? defaults_1.V4_CONSTRAINTS.KNOWN_SERVICES[serviceIdx - 1]
|
|
138
|
+
: serviceInput.trim().toLowerCase().replace(/[^a-z0-9-]+/g, '-') || 'automation';
|
|
139
|
+
const priceInput = await ask(rl, output_1.fmt.cyan('? ') + 'Base price in USDC? ' + output_1.fmt.dim('[default: 1.00] > '));
|
|
140
|
+
const price = parseFloat(priceInput) || 1.0;
|
|
141
|
+
return { name: name.trim(), service, price };
|
|
142
|
+
}
|
|
143
|
+
finally {
|
|
144
|
+
rl.close();
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* Resolve ACTP_KEY_PASSWORD.
|
|
149
|
+
*
|
|
150
|
+
* Priority:
|
|
151
|
+
* 1. Existing env var (developer-set / CI secret)
|
|
152
|
+
* 2. Interactive prompt (TTY only)
|
|
153
|
+
* 3. Auto-generated password (non-TTY — see FIX-3 follow-up; current
|
|
154
|
+
* behavior is to surface a clear error so the user can set the env
|
|
155
|
+
* var explicitly).
|
|
156
|
+
*
|
|
157
|
+
* Returns the password (already written to process.env.ACTP_KEY_PASSWORD)
|
|
158
|
+
* or `null` if no password could be obtained.
|
|
159
|
+
*/
|
|
160
|
+
async function ensurePasswordDefault(output) {
|
|
161
|
+
if (process.env.ACTP_KEY_PASSWORD && process.env.ACTP_KEY_PASSWORD.length >= 8) {
|
|
162
|
+
return process.env.ACTP_KEY_PASSWORD;
|
|
163
|
+
}
|
|
164
|
+
if (!process.stdin.isTTY) {
|
|
165
|
+
// Non-TTY without an explicit password — fail closed with a clear hint
|
|
166
|
+
// instead of letting `runInit` throw a generic error. FIX-3 will replace
|
|
167
|
+
// this branch with auto-generated passwords + claim-code persistence.
|
|
168
|
+
output.error('ACTP_KEY_PASSWORD is required in non-interactive environments.\n' +
|
|
169
|
+
'Set ACTP_KEY_PASSWORD=<min 8 chars> and re-run, or use --interactive.');
|
|
170
|
+
return null;
|
|
171
|
+
}
|
|
172
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
173
|
+
try {
|
|
174
|
+
const answer = await new Promise((resolve) => {
|
|
175
|
+
rl.question(output_1.fmt.cyan('? ') + 'Pick a password to encrypt your wallet ' + output_1.fmt.dim('(min 8 chars) > '), resolve);
|
|
176
|
+
});
|
|
177
|
+
const password = answer.trim();
|
|
178
|
+
if (password.length < 8) {
|
|
179
|
+
output.error('Password must be at least 8 characters.');
|
|
180
|
+
return null;
|
|
181
|
+
}
|
|
182
|
+
process.env.ACTP_KEY_PASSWORD = password;
|
|
183
|
+
return password;
|
|
184
|
+
}
|
|
185
|
+
finally {
|
|
186
|
+
rl.close();
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
// ============================================================================
|
|
190
|
+
// Wizard orchestrator (the testable core)
|
|
191
|
+
// ============================================================================
|
|
192
|
+
/**
|
|
193
|
+
* Build the V4 frontmatter + body for a fresh {slug}.md from wizard answers.
|
|
194
|
+
*
|
|
195
|
+
* Extracted so tests can verify the structure without going through the
|
|
196
|
+
* full wizard flow.
|
|
197
|
+
*/
|
|
198
|
+
function buildIdentityFile(answers) {
|
|
199
|
+
const slug = (0, slugUtils_2.generateSlug)(answers.name);
|
|
200
|
+
const slugError = (0, slugUtils_1.validateSlug)(slug);
|
|
201
|
+
if (slugError) {
|
|
202
|
+
throw new Error(`Invalid agent name: ${slugError}`);
|
|
203
|
+
}
|
|
204
|
+
const minPrice = Math.max(0.01, answers.price * 0.99);
|
|
205
|
+
const maxPrice = answers.price * 1.01;
|
|
206
|
+
const frontmatter = {
|
|
207
|
+
name: answers.name,
|
|
208
|
+
slug,
|
|
209
|
+
version: '1.0.0',
|
|
210
|
+
// Default to testnet — the wizard's whole point is to put the user on
|
|
211
|
+
// Base Sepolia with a real keystore and a real Sentinel transaction.
|
|
212
|
+
network: 'base-sepolia',
|
|
213
|
+
services: [
|
|
214
|
+
{
|
|
215
|
+
type: answers.service,
|
|
216
|
+
price: String(answers.price),
|
|
217
|
+
min_price: minPrice,
|
|
218
|
+
max_price: maxPrice,
|
|
219
|
+
},
|
|
220
|
+
],
|
|
221
|
+
pricing: {
|
|
222
|
+
base: answers.price,
|
|
223
|
+
currency: defaults_1.V4_DEFAULTS.pricing.currency,
|
|
224
|
+
unit: defaults_1.V4_DEFAULTS.pricing.unit,
|
|
225
|
+
min_price: minPrice,
|
|
226
|
+
max_price: maxPrice,
|
|
227
|
+
negotiable: false,
|
|
228
|
+
},
|
|
229
|
+
sla: { ...defaults_1.V4_DEFAULTS.sla },
|
|
230
|
+
payment: { modes: [...defaults_1.V4_DEFAULTS.payment.modes] },
|
|
231
|
+
};
|
|
232
|
+
const body = `\n# ${answers.name}\n\nDescribe what your agent does here.\n\n## How to Request This Service\n\nExplain how clients should structure their requests.\n`;
|
|
233
|
+
const content = (0, agirailsmd_1.serializeAgirailsMd)(frontmatter, body);
|
|
234
|
+
return { filename: `${slug}.md`, content, slug };
|
|
235
|
+
}
|
|
236
|
+
exports.buildIdentityFile = buildIdentityFile;
|
|
237
|
+
/**
|
|
238
|
+
* Orchestrate the wizard. Pure-ish: every side effect goes through `deps`,
|
|
239
|
+
* so the test suite can verify behavior without ever touching the network,
|
|
240
|
+
* the keystore encrypt path, or a real Sentinel deployment.
|
|
241
|
+
*
|
|
242
|
+
* Returns the exit code instead of calling process.exit so callers (and
|
|
243
|
+
* tests) can decide what to do with failures.
|
|
244
|
+
*/
|
|
245
|
+
async function runWizard(output, deps = defaultWizardDeps()) {
|
|
68
246
|
try {
|
|
69
|
-
// Re-entrant
|
|
247
|
+
// ── Re-entrant fast path ────────────────────────────────────────────
|
|
248
|
+
// If an identity file already exists, skip onboarding entirely and
|
|
249
|
+
// jump straight to the test transaction. We still require a keystore
|
|
250
|
+
// — if there's an identity but no wallet, init must run.
|
|
70
251
|
const existingIdentity = (0, config_1.resolveIdentityPath)();
|
|
71
252
|
if (existingIdentity) {
|
|
72
253
|
output.print(output_1.fmt.dim('Identity found: ' + existingIdentity));
|
|
73
254
|
output.print('');
|
|
74
|
-
|
|
75
|
-
|
|
255
|
+
if (!deps.hasKeystore(process.cwd())) {
|
|
256
|
+
// Identity but no keystore — typical for users who ran the wizard
|
|
257
|
+
// before FIX-2 landed and left a stale mock config behind. Run
|
|
258
|
+
// init now so runTest has something to sign with.
|
|
259
|
+
const password = await deps.ensurePassword(output);
|
|
260
|
+
if (!password)
|
|
261
|
+
return output_1.ExitCode.INVALID_INPUT;
|
|
262
|
+
try {
|
|
263
|
+
await deps.runInit({ mode: 'testnet', wallet: 'auto', force: true, test: false }, output);
|
|
264
|
+
}
|
|
265
|
+
catch (error) {
|
|
266
|
+
output.error('Wallet bootstrap failed: ' + extractMessage(error));
|
|
267
|
+
printSetupHint(output);
|
|
268
|
+
return output_1.ExitCode.ERROR;
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
try {
|
|
272
|
+
await deps.runTest(output);
|
|
273
|
+
return output_1.ExitCode.SUCCESS;
|
|
274
|
+
}
|
|
275
|
+
catch (error) {
|
|
276
|
+
return handleTestError(error, output);
|
|
277
|
+
}
|
|
76
278
|
}
|
|
77
|
-
// Banner
|
|
279
|
+
// ── Banner ──────────────────────────────────────────────────────────
|
|
78
280
|
if (output.mode === 'human') {
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
// Interactive questions
|
|
87
|
-
const rl = readline.createInterface({
|
|
88
|
-
input: process.stdin,
|
|
89
|
-
output: process.stdout,
|
|
90
|
-
});
|
|
91
|
-
try {
|
|
92
|
-
// Q1: Agent name
|
|
93
|
-
const name = await ask(rl, output_1.fmt.cyan('? ') + 'What does your agent do? (name) ' + output_1.fmt.dim('> '));
|
|
94
|
-
if (!name.trim()) {
|
|
95
|
-
output.error('Agent name is required.');
|
|
96
|
-
process.exit(output_1.ExitCode.INVALID_INPUT);
|
|
97
|
-
}
|
|
98
|
-
// Q2: Service type
|
|
99
|
-
output.print('');
|
|
100
|
-
output.print(output_1.fmt.dim('Service types:'));
|
|
101
|
-
output.print(SERVICE_MENU);
|
|
102
|
-
const serviceInput = await ask(rl, output_1.fmt.cyan('? ') + 'Pick a service type ' + output_1.fmt.dim('[1-7, or custom] > '));
|
|
103
|
-
const serviceIdx = parseInt(serviceInput, 10);
|
|
104
|
-
const service = (serviceIdx >= 1 && serviceIdx <= defaults_1.V4_CONSTRAINTS.KNOWN_SERVICES.length)
|
|
105
|
-
? defaults_1.V4_CONSTRAINTS.KNOWN_SERVICES[serviceIdx - 1]
|
|
106
|
-
: serviceInput.trim().toLowerCase().replace(/[^a-z0-9-]+/g, '-') || 'automation';
|
|
107
|
-
// Q3: Base price
|
|
108
|
-
const priceInput = await ask(rl, output_1.fmt.cyan('? ') + 'Base price in USDC? ' + output_1.fmt.dim('[default: 1.00] > '));
|
|
109
|
-
const price = parseFloat(priceInput) || 1.00;
|
|
110
|
-
rl.close();
|
|
111
|
-
// Generate and validate slug
|
|
112
|
-
const slug = (0, slugUtils_2.generateSlug)(name.trim());
|
|
113
|
-
const slugError = (0, slugUtils_1.validateSlug)(slug);
|
|
114
|
-
if (slugError) {
|
|
115
|
-
output.error(`Invalid agent name: ${slugError}`);
|
|
116
|
-
process.exit(output_1.ExitCode.INVALID_INPUT);
|
|
281
|
+
try {
|
|
282
|
+
const { renderBanner } = await Promise.resolve().then(() => __importStar(require('./utils/banner')));
|
|
283
|
+
output.print('');
|
|
284
|
+
output.print(renderBanner());
|
|
285
|
+
output.print('');
|
|
286
|
+
output.print(output_1.fmt.dim('Your agent earns in 60 seconds.'));
|
|
287
|
+
output.print('');
|
|
117
288
|
}
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
// Services emit as objects { type, price, min_price, max_price } —
|
|
121
|
-
// not plain strings — because the SDK publish pipeline
|
|
122
|
-
// (`extractRegistrationParams`) reads `svc.type` and `svc.min_price`/
|
|
123
|
-
// `svc.max_price` to populate per-service on-chain price bands on
|
|
124
|
-
// AgentRegistry. Plain strings throw "Empty service type" at publish.
|
|
125
|
-
// Default band is 1% around base price to avoid the 0..1000 USDC
|
|
126
|
-
// fallback when min/max are absent.
|
|
127
|
-
const minPrice = Math.max(0.01, price * 0.99);
|
|
128
|
-
const maxPrice = price * 1.01;
|
|
129
|
-
const frontmatter = {
|
|
130
|
-
name: name.trim(),
|
|
131
|
-
slug,
|
|
132
|
-
version: '1.0.0',
|
|
133
|
-
network: defaults_1.V4_DEFAULTS.network,
|
|
134
|
-
services: [
|
|
135
|
-
{
|
|
136
|
-
type: service,
|
|
137
|
-
price: String(price),
|
|
138
|
-
min_price: minPrice,
|
|
139
|
-
max_price: maxPrice,
|
|
140
|
-
},
|
|
141
|
-
],
|
|
142
|
-
pricing: {
|
|
143
|
-
base: price,
|
|
144
|
-
currency: defaults_1.V4_DEFAULTS.pricing.currency,
|
|
145
|
-
unit: defaults_1.V4_DEFAULTS.pricing.unit,
|
|
146
|
-
min_price: minPrice,
|
|
147
|
-
max_price: maxPrice,
|
|
148
|
-
negotiable: false,
|
|
149
|
-
},
|
|
150
|
-
sla: { ...defaults_1.V4_DEFAULTS.sla },
|
|
151
|
-
payment: { modes: [...defaults_1.V4_DEFAULTS.payment.modes] },
|
|
152
|
-
};
|
|
153
|
-
// Build body
|
|
154
|
-
const body = `\n# ${name.trim()}\n\nDescribe what your agent does here.\n\n## How to Request This Service\n\nExplain how clients should structure their requests.\n`;
|
|
155
|
-
// Write {slug}.md (guard against overwriting existing files)
|
|
156
|
-
const filename = `${slug}.md`;
|
|
157
|
-
if (fs.existsSync(filename)) {
|
|
158
|
-
output.error(`File already exists: ${filename}. Remove it or choose a different name.`);
|
|
159
|
-
process.exit(output_1.ExitCode.INVALID_INPUT);
|
|
289
|
+
catch {
|
|
290
|
+
// Banner failure is purely cosmetic — never block the wizard on it.
|
|
160
291
|
}
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
292
|
+
}
|
|
293
|
+
// ── Collect answers ────────────────────────────────────────────────
|
|
294
|
+
const answers = await deps.collectAnswers(output);
|
|
295
|
+
if (!answers) {
|
|
296
|
+
return output_1.ExitCode.INVALID_INPUT;
|
|
297
|
+
}
|
|
298
|
+
// ── Build & write {slug}.md ────────────────────────────────────────
|
|
299
|
+
let identity;
|
|
300
|
+
try {
|
|
301
|
+
identity = buildIdentityFile(answers);
|
|
302
|
+
}
|
|
303
|
+
catch (error) {
|
|
304
|
+
output.error(extractMessage(error));
|
|
305
|
+
return output_1.ExitCode.INVALID_INPUT;
|
|
306
|
+
}
|
|
307
|
+
if (fs.existsSync(identity.filename)) {
|
|
308
|
+
output.error(`File already exists: ${identity.filename}. Remove it or choose a different name.`);
|
|
309
|
+
return output_1.ExitCode.INVALID_INPUT;
|
|
310
|
+
}
|
|
311
|
+
try {
|
|
312
|
+
fs.writeFileSync(identity.filename, identity.content, 'utf-8');
|
|
313
|
+
}
|
|
314
|
+
catch (error) {
|
|
315
|
+
output.error('Could not write identity file: ' + extractMessage(error));
|
|
316
|
+
return output_1.ExitCode.ERROR;
|
|
317
|
+
}
|
|
318
|
+
output.print('');
|
|
319
|
+
output.success(`Created ${output_1.fmt.bold(identity.filename)}`);
|
|
320
|
+
// ── Resolve password BEFORE runInit ────────────────────────────────
|
|
321
|
+
// We deliberately resolve the password before *any* on-chain or
|
|
322
|
+
// keystore-encrypt work — that way we can fail fast if no password
|
|
323
|
+
// is available, and the user doesn't see a half-baked .actp/ dir.
|
|
324
|
+
const password = await deps.ensurePassword(output);
|
|
325
|
+
if (!password) {
|
|
326
|
+
// ensurePassword already printed the actionable error.
|
|
327
|
+
return output_1.ExitCode.INVALID_INPUT;
|
|
328
|
+
}
|
|
329
|
+
// ── Run init: keystore + Smart Wallet + mint test USDC ─────────────
|
|
330
|
+
// runInit picks up the freshly written {slug}.md via its own scan logic
|
|
331
|
+
// and uses it to pre-fill the .actp/config.json values, so we don't
|
|
332
|
+
// need to push answers in twice.
|
|
333
|
+
try {
|
|
334
|
+
await deps.runInit({
|
|
335
|
+
mode: 'testnet',
|
|
336
|
+
wallet: 'auto',
|
|
337
|
+
// We *just* wrote the file; if it existed before, we already
|
|
338
|
+
// bailed above. `force: false` keeps init from clobbering a
|
|
339
|
+
// pre-existing .actp/ that the user might still want.
|
|
340
|
+
force: false,
|
|
341
|
+
// Don't run the post-init prompt — we run runTest ourselves
|
|
342
|
+
// immediately after so we have full control over UX.
|
|
343
|
+
test: false,
|
|
344
|
+
}, output);
|
|
345
|
+
}
|
|
346
|
+
catch (error) {
|
|
347
|
+
output.error('Wallet bootstrap failed: ' + extractMessage(error));
|
|
348
|
+
printSetupHint(output);
|
|
349
|
+
// Critical: do NOT proceed to runTest. Without a keystore, runTest
|
|
350
|
+
// crashes hard with a "Cannot read property 'getAddress' of
|
|
351
|
+
// undefined" stack — exactly what FIX-2 set out to eliminate.
|
|
352
|
+
return output_1.ExitCode.ERROR;
|
|
353
|
+
}
|
|
354
|
+
// ── Defensive fallback for an init that "succeeded" but left no
|
|
355
|
+
// keystore behind (e.g. test mocks, partial failures). Without this
|
|
356
|
+
// guard, runTest would still crash — so we treat it as a hard stop.
|
|
357
|
+
if (!deps.hasKeystore(process.cwd())) {
|
|
358
|
+
output.error('Wallet bootstrap completed but no keystore was found at .actp/keystore.json.');
|
|
359
|
+
printSetupHint(output);
|
|
360
|
+
return output_1.ExitCode.ERROR;
|
|
361
|
+
}
|
|
362
|
+
// ── Backfill identity pointer if init didn't pick it up ────────────
|
|
363
|
+
// runInit writes config.json — but its identity-detection scan can
|
|
364
|
+
// miss freshly-created files in rare race conditions. Always make
|
|
365
|
+
// sure the pointer is set after init.
|
|
366
|
+
if ((0, config_1.isInitialized)() && !(0, config_1.resolveIdentityPath)()) {
|
|
367
|
+
try {
|
|
368
|
+
(0, config_1.updateConfig)({ identity: identity.filename });
|
|
176
369
|
}
|
|
177
|
-
|
|
178
|
-
//
|
|
179
|
-
(
|
|
180
|
-
output.success('Updated .actp/config.json with identity pointer');
|
|
370
|
+
catch {
|
|
371
|
+
// updateConfig failing here is non-fatal — runTest's own
|
|
372
|
+
// resolveIdentityPath() will still find the file via the scan.
|
|
181
373
|
}
|
|
182
|
-
// Run a real Sentinel onboarding request on Base Sepolia. Requires a
|
|
183
|
-
// wallet at ~/.actp/wallets/base-sepolia (or ACTP_KEYSTORE_BASE64) and
|
|
184
|
-
// small testnet ETH + USDC. The PRD §5.7 rewrite intentionally
|
|
185
|
-
// dropped the pre-4.0.0 MockRuntime simulation — "mock success" was a
|
|
186
|
-
// lie and onboarding deserves the real loop.
|
|
187
|
-
output.print('');
|
|
188
|
-
await (0, test_1.runTest)(output);
|
|
189
|
-
// Next steps
|
|
190
|
-
output.print('');
|
|
191
|
-
output.section('Next Steps');
|
|
192
|
-
output.print(` 1. Edit your agent: ${output_1.fmt.bold(filename)}`);
|
|
193
|
-
output.print(` 2. Go live on testnet: ${output_1.fmt.bold('actp publish')}`);
|
|
194
|
-
output.print(` 3. Your agent page: ${output_1.fmt.cyan(`agirails.app/a/${slug}`)} ${output_1.fmt.dim('(after publish)')}`);
|
|
195
|
-
output.print('');
|
|
196
374
|
}
|
|
197
|
-
|
|
198
|
-
|
|
375
|
+
// ── Run the Sentinel test transaction ──────────────────────────────
|
|
376
|
+
output.print('');
|
|
377
|
+
try {
|
|
378
|
+
await deps.runTest(output);
|
|
379
|
+
}
|
|
380
|
+
catch (error) {
|
|
381
|
+
return handleTestError(error, output);
|
|
199
382
|
}
|
|
383
|
+
// ── Next steps ─────────────────────────────────────────────────────
|
|
384
|
+
output.print('');
|
|
385
|
+
output.section('Next Steps');
|
|
386
|
+
output.print(` 1. Edit your agent: ${output_1.fmt.bold(identity.filename)}`);
|
|
387
|
+
output.print(` 2. Go live on testnet: ${output_1.fmt.bold('actp publish')}`);
|
|
388
|
+
output.print(` 3. Your agent page: ${output_1.fmt.cyan(`agirails.app/a/${identity.slug}`)} ${output_1.fmt.dim('(after publish)')}`);
|
|
389
|
+
output.print('');
|
|
390
|
+
return output_1.ExitCode.SUCCESS;
|
|
200
391
|
}
|
|
201
392
|
catch (error) {
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
393
|
+
// Last-resort catch — should be unreachable given the inner try/catch
|
|
394
|
+
// blocks above, but better to surface a clean message than a stack.
|
|
395
|
+
output.error(extractMessage(error));
|
|
396
|
+
return output_1.ExitCode.ERROR;
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
exports.runWizard = runWizard;
|
|
400
|
+
/**
|
|
401
|
+
* Map a runTest error to a structured output + the right exit code.
|
|
402
|
+
* Mirrors the existing setup-hint heuristic so users with no wallet / no
|
|
403
|
+
* funds / missing Sentinel address still get actionable next-step guidance.
|
|
404
|
+
*/
|
|
405
|
+
function handleTestError(error, output) {
|
|
406
|
+
const message = extractMessage(error);
|
|
407
|
+
output.error(message);
|
|
408
|
+
if (looksLikeRunTestSetupError(message)) {
|
|
409
|
+
printSetupHint(output);
|
|
410
|
+
}
|
|
411
|
+
return output_1.ExitCode.ERROR;
|
|
412
|
+
}
|
|
413
|
+
function extractMessage(error) {
|
|
414
|
+
if (error instanceof Error)
|
|
415
|
+
return error.message;
|
|
416
|
+
if (typeof error === 'string')
|
|
417
|
+
return error;
|
|
418
|
+
try {
|
|
419
|
+
return JSON.stringify(error);
|
|
420
|
+
}
|
|
421
|
+
catch {
|
|
422
|
+
return String(error);
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
function printSetupHint(output) {
|
|
426
|
+
output.print('');
|
|
427
|
+
output.print('agirails now runs a real onboarding request against Sentinel on Base Sepolia.\n' +
|
|
428
|
+
'First-run setup:\n' +
|
|
429
|
+
' 1. `actp init` to generate a wallet (or set ACTP_KEYSTORE_BASE64).\n' +
|
|
430
|
+
' 2. Fund the wallet with a small amount of Base Sepolia ETH (gas) + test USDC.\n' +
|
|
431
|
+
' 3. Rerun `npx agirails`.\n' +
|
|
432
|
+
"Override Sentinel's address with ACTP_SENTINEL_ADDRESS=0x... if needed.");
|
|
433
|
+
}
|
|
434
|
+
// ============================================================================
|
|
435
|
+
// Main entry point
|
|
436
|
+
// ============================================================================
|
|
437
|
+
async function main() {
|
|
438
|
+
const args = process.argv.slice(2);
|
|
439
|
+
const jsonMode = args.includes('--json');
|
|
440
|
+
const quietMode = args.includes('-q') || args.includes('--quiet');
|
|
441
|
+
const output = new output_1.Output(jsonMode ? 'json' : quietMode ? 'quiet' : 'human');
|
|
442
|
+
// FIX-2: the wizard previously bootstrapped a mock config with a random
|
|
443
|
+
// address and called runTest in testnet mode → crash. We now invoke
|
|
444
|
+
// runInit({mode:'testnet',wallet:'auto'}) before runTest, so the keystore
|
|
445
|
+
// and Smart Wallet exist by the time runTest needs a signer.
|
|
446
|
+
//
|
|
447
|
+
// Backwards-compat note for existing mock configs: if a previous wizard
|
|
448
|
+
// run left a `.actp/config.json` in `mode: 'mock'` with a random address
|
|
449
|
+
// and no keystore, treat that as the "no real onboarding yet" state and
|
|
450
|
+
// re-run the wizard. Identity is preserved (we don't overwrite the .md),
|
|
451
|
+
// but the config gets upgraded by runInit.
|
|
452
|
+
if ((0, config_1.isInitialized)()) {
|
|
453
|
+
try {
|
|
454
|
+
const { loadConfig } = await Promise.resolve().then(() => __importStar(require('./utils/config')));
|
|
455
|
+
const cfg = loadConfig();
|
|
456
|
+
const looksLikeStaleMock = cfg.mode === 'mock' && !defaultWizardDeps().hasKeystore(process.cwd());
|
|
457
|
+
if (looksLikeStaleMock && (0, config_1.resolveIdentityPath)()) {
|
|
458
|
+
output.print(output_1.fmt.dim('Detected stale mock config — upgrading to testnet wallet so the Sentinel transaction can run.'));
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
catch {
|
|
462
|
+
// ignore — runWizard handles missing/broken config gracefully
|
|
218
463
|
}
|
|
219
|
-
process.exit(output_1.ExitCode.ERROR);
|
|
220
464
|
}
|
|
465
|
+
const code = await runWizard(output, defaultWizardDeps());
|
|
466
|
+
process.exit(code);
|
|
221
467
|
}
|
|
222
468
|
/** Heuristic — match the four most common runRequest / resolveAgent first-run
|
|
223
469
|
* failure-message shapes so the setup hint only fires when actionable. */
|
|
@@ -227,21 +473,50 @@ function looksLikeRunTestSetupError(message) {
|
|
|
227
473
|
/Agent ['"]?sentinel['"]?/i.test(message) ||
|
|
228
474
|
/ACTP_SENTINEL_ADDRESS/i.test(message) ||
|
|
229
475
|
/insufficient funds/i.test(message) ||
|
|
230
|
-
/BASE_SEPOLIA_RPC/i.test(message)
|
|
476
|
+
/BASE_SEPOLIA_RPC/i.test(message) ||
|
|
477
|
+
/keystore/i.test(message) ||
|
|
478
|
+
/ACTP_KEY_PASSWORD/i.test(message));
|
|
231
479
|
}
|
|
480
|
+
// Internal helpers exposed for unit tests only. Not part of the public CLI API.
|
|
481
|
+
exports.__test = {
|
|
482
|
+
looksLikeRunTestSetupError,
|
|
483
|
+
handleTestError,
|
|
484
|
+
extractMessage,
|
|
485
|
+
// Retain the pre-FIX-2 mock-config bootstrap so legacy callers can still
|
|
486
|
+
// construct a mock config inline (used by agirails.test.ts and as a
|
|
487
|
+
// fallback if the wizard ever needs to fall back to mock mode).
|
|
488
|
+
bootstrapMockConfig: (filename) => {
|
|
489
|
+
const randomAddress = ethers_1.ethers.Wallet.createRandom().address;
|
|
490
|
+
(0, config_1.saveConfig)({
|
|
491
|
+
...config_1.CONFIG_DEFAULTS,
|
|
492
|
+
mode: 'mock',
|
|
493
|
+
address: randomAddress,
|
|
494
|
+
identity: filename,
|
|
495
|
+
});
|
|
496
|
+
(0, config_2.addToGitignore)();
|
|
497
|
+
},
|
|
498
|
+
};
|
|
232
499
|
// ============================================================================
|
|
233
500
|
// Subcommand routing: agirails find [query] [options]
|
|
234
501
|
// ============================================================================
|
|
502
|
+
const isMainModule = require.main === module;
|
|
235
503
|
const subCmd = process.argv[2];
|
|
236
|
-
if (
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
504
|
+
if (isMainModule) {
|
|
505
|
+
if (subCmd === 'find') {
|
|
506
|
+
const sub = new commander_1.Command('agirails');
|
|
507
|
+
sub.addCommand((0, find_1.createFindCommand)());
|
|
508
|
+
sub.parse(process.argv);
|
|
509
|
+
}
|
|
510
|
+
else {
|
|
511
|
+
main().catch((error) => {
|
|
512
|
+
// Final safety net — runWizard should already have caught & printed,
|
|
513
|
+
// but if something escapes (e.g. an import failure) we still want a
|
|
514
|
+
// single-line error rather than a raw stack trace.
|
|
515
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
516
|
+
// eslint-disable-next-line no-console
|
|
517
|
+
console.error(message);
|
|
518
|
+
process.exit(1);
|
|
519
|
+
});
|
|
520
|
+
}
|
|
246
521
|
}
|
|
247
522
|
//# sourceMappingURL=agirails.js.map
|