@agirails/sdk 4.4.9 → 4.6.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.
Files changed (97) hide show
  1. package/dist/builders/DeliveryProofBuilder.d.ts +224 -13
  2. package/dist/builders/DeliveryProofBuilder.d.ts.map +1 -1
  3. package/dist/builders/DeliveryProofBuilder.js +247 -13
  4. package/dist/builders/DeliveryProofBuilder.js.map +1 -1
  5. package/dist/cli/agirails.d.ts +85 -1
  6. package/dist/cli/agirails.d.ts.map +1 -1
  7. package/dist/cli/agirails.js +429 -154
  8. package/dist/cli/agirails.js.map +1 -1
  9. package/dist/cli/commands/init.d.ts +95 -0
  10. package/dist/cli/commands/init.d.ts.map +1 -1
  11. package/dist/cli/commands/init.js +338 -1
  12. package/dist/cli/commands/init.js.map +1 -1
  13. package/dist/cli/commands/publish.d.ts +19 -0
  14. package/dist/cli/commands/publish.d.ts.map +1 -1
  15. package/dist/cli/commands/publish.js +14 -1
  16. package/dist/cli/commands/publish.js.map +1 -1
  17. package/dist/cli/commands/receipt.d.ts +70 -2
  18. package/dist/cli/commands/receipt.d.ts.map +1 -1
  19. package/dist/cli/commands/receipt.js +218 -3
  20. package/dist/cli/commands/receipt.js.map +1 -1
  21. package/dist/cli/commands/test.d.ts +77 -1
  22. package/dist/cli/commands/test.d.ts.map +1 -1
  23. package/dist/cli/commands/test.js +264 -2
  24. package/dist/cli/commands/test.js.map +1 -1
  25. package/dist/cli/lib/runRequest.d.ts +90 -0
  26. package/dist/cli/lib/runRequest.d.ts.map +1 -1
  27. package/dist/cli/lib/runRequest.js +300 -9
  28. package/dist/cli/lib/runRequest.js.map +1 -1
  29. package/dist/cli/lib/sentinelReflections.d.ts +111 -0
  30. package/dist/cli/lib/sentinelReflections.d.ts.map +1 -0
  31. package/dist/cli/lib/sentinelReflections.js +193 -0
  32. package/dist/cli/lib/sentinelReflections.js.map +1 -0
  33. package/dist/delivery/MockDeliveryChannel.d.ts +208 -0
  34. package/dist/delivery/MockDeliveryChannel.d.ts.map +1 -0
  35. package/dist/delivery/MockDeliveryChannel.js +445 -0
  36. package/dist/delivery/MockDeliveryChannel.js.map +1 -0
  37. package/dist/delivery/RelayDeliveryChannel.d.ts +176 -0
  38. package/dist/delivery/RelayDeliveryChannel.d.ts.map +1 -0
  39. package/dist/delivery/RelayDeliveryChannel.js +377 -0
  40. package/dist/delivery/RelayDeliveryChannel.js.map +1 -0
  41. package/dist/delivery/channel.d.ts +282 -0
  42. package/dist/delivery/channel.d.ts.map +1 -0
  43. package/dist/delivery/channel.js +76 -0
  44. package/dist/delivery/channel.js.map +1 -0
  45. package/dist/delivery/channelLog.d.ts +115 -0
  46. package/dist/delivery/channelLog.d.ts.map +1 -0
  47. package/dist/delivery/channelLog.js +94 -0
  48. package/dist/delivery/channelLog.js.map +1 -0
  49. package/dist/delivery/crypto.d.ts +312 -0
  50. package/dist/delivery/crypto.d.ts.map +1 -0
  51. package/dist/delivery/crypto.js +495 -0
  52. package/dist/delivery/crypto.js.map +1 -0
  53. package/dist/delivery/eip712.d.ts +248 -0
  54. package/dist/delivery/eip712.d.ts.map +1 -0
  55. package/dist/delivery/eip712.js +397 -0
  56. package/dist/delivery/eip712.js.map +1 -0
  57. package/dist/delivery/envelopeBuilder.d.ts +531 -0
  58. package/dist/delivery/envelopeBuilder.d.ts.map +1 -0
  59. package/dist/delivery/envelopeBuilder.js +832 -0
  60. package/dist/delivery/envelopeBuilder.js.map +1 -0
  61. package/dist/delivery/index.d.ts +53 -0
  62. package/dist/delivery/index.d.ts.map +1 -0
  63. package/dist/delivery/index.js +143 -0
  64. package/dist/delivery/index.js.map +1 -0
  65. package/dist/delivery/keys.d.ts +344 -0
  66. package/dist/delivery/keys.d.ts.map +1 -0
  67. package/dist/delivery/keys.js +513 -0
  68. package/dist/delivery/keys.js.map +1 -0
  69. package/dist/delivery/nonce-keys.d.ts +93 -0
  70. package/dist/delivery/nonce-keys.d.ts.map +1 -0
  71. package/dist/delivery/nonce-keys.js +88 -0
  72. package/dist/delivery/nonce-keys.js.map +1 -0
  73. package/dist/delivery/setupBuilder.d.ts +403 -0
  74. package/dist/delivery/setupBuilder.d.ts.map +1 -0
  75. package/dist/delivery/setupBuilder.js +554 -0
  76. package/dist/delivery/setupBuilder.js.map +1 -0
  77. package/dist/delivery/types.d.ts +722 -0
  78. package/dist/delivery/types.d.ts.map +1 -0
  79. package/dist/delivery/types.js +150 -0
  80. package/dist/delivery/types.js.map +1 -0
  81. package/dist/delivery/validate.d.ts +288 -0
  82. package/dist/delivery/validate.d.ts.map +1 -0
  83. package/dist/delivery/validate.js +648 -0
  84. package/dist/delivery/validate.js.map +1 -0
  85. package/dist/level1/Agent.d.ts +130 -0
  86. package/dist/level1/Agent.d.ts.map +1 -1
  87. package/dist/level1/Agent.js +248 -0
  88. package/dist/level1/Agent.js.map +1 -1
  89. package/dist/level1/types/Options.d.ts +62 -0
  90. package/dist/level1/types/Options.d.ts.map +1 -1
  91. package/dist/level1/types/Options.js +22 -0
  92. package/dist/level1/types/Options.js.map +1 -1
  93. package/dist/runtime/MockRuntime.d.ts +32 -0
  94. package/dist/runtime/MockRuntime.d.ts.map +1 -1
  95. package/dist/runtime/MockRuntime.js +44 -0
  96. package/dist/runtime/MockRuntime.js.map +1 -1
  97. package/package.json +6 -1
@@ -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 → real Sentinel
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
- // Main
121
+ // Interactive collectors
62
122
  // ============================================================================
63
- async function main() {
64
- const args = process.argv.slice(2);
65
- const jsonMode = args.includes('--json');
66
- const quietMode = args.includes('-q') || args.includes('--quiet');
67
- const output = new output_1.Output(jsonMode ? 'json' : quietMode ? 'quiet' : 'human');
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: if identity exists, skip onboarding
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
- await (0, test_1.runTest)(output);
75
- return;
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
- const { renderBanner } = await Promise.resolve().then(() => __importStar(require('./utils/banner')));
80
- output.print('');
81
- output.print(renderBanner());
82
- output.print('');
83
- output.print(output_1.fmt.dim('Your agent earns in 60 seconds.'));
84
- output.print('');
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
- // Build frontmatter.
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
- const content = (0, agirailsmd_1.serializeAgirailsMd)(frontmatter, body);
162
- fs.writeFileSync(filename, content, 'utf-8');
163
- output.print('');
164
- output.success(`Created ${output_1.fmt.bold(filename)}`);
165
- // Bootstrap .actp/ or backfill identity pointer
166
- if (!(0, config_1.isInitialized)()) {
167
- const randomAddress = ethers_1.ethers.Wallet.createRandom().address;
168
- (0, config_1.saveConfig)({
169
- ...config_1.CONFIG_DEFAULTS,
170
- mode: 'mock',
171
- address: randomAddress,
172
- identity: filename,
173
- });
174
- (0, config_2.addToGitignore)();
175
- output.success('Initialized .actp/ (mock mode)');
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
- else if (!(0, config_1.resolveIdentityPath)()) {
178
- // Existing config without identity pointerbackfill it
179
- (0, config_1.updateConfig)({ identity: filename });
180
- output.success('Updated .actp/config.json with identity pointer');
370
+ catch {
371
+ // updateConfig failing here is non-fatalrunTest'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
- finally {
198
- rl.close();
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
- const message = error instanceof Error ? error.message : String(error);
203
- output.error(message);
204
- // Surface the 4.0.0 setup expectation that runTest() now imposes. The
205
- // common first-run failure modes — no keystore, no testnet ETH, no
206
- // sentinel address — all flow through here, and a bare error message
207
- // gives a new developer nothing to act on. The hint is conditional on
208
- // the error shape so non-runtime errors (e.g. file-write failures
209
- // earlier in onboarding) don't get the wrong remediation glued on.
210
- if (looksLikeRunTestSetupError(message)) {
211
- output.print('');
212
- output.print('agirails now runs a real onboarding request against Sentinel on Base Sepolia.\n' +
213
- 'First-run setup:\n' +
214
- " 1. `actp init` to generate a wallet (or set ACTP_KEYSTORE_BASE64).\n" +
215
- " 2. Fund the wallet with a small amount of Base Sepolia ETH (gas) + test USDC.\n" +
216
- " 3. Rerun `npx agirails`.\n" +
217
- 'Override Sentinel\'s address with ACTP_SENTINEL_ADDRESS=0x... if needed.');
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 (subCmd === 'find') {
237
- const sub = new commander_1.Command('agirails');
238
- sub.addCommand((0, find_1.createFindCommand)());
239
- sub.parse(process.argv);
240
- }
241
- else {
242
- main().catch((error) => {
243
- console.error(error?.message || error);
244
- process.exit(1);
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