agentic-relay 4.3.0 → 5.0.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.
@@ -2,22 +2,17 @@
2
2
  * Command implementations for the agentrelay CLI. Each returns an envelope payload
3
3
  * (the entry adds `ok`/`errors` and chooses the exit code).
4
4
  *
5
- * `session start|send|pay|end` are thin api_v1 wrappers; the caller (a host
6
- * subagent, or the main agent in direct mode) drives the conversation and
7
- * reads the agent's RAW turns. Compact specs get banked in the on-disk cache
8
- * (`cache put`/`ls`/`show`) so transcripts stay out of the main context.
5
+ * `session start|send|end` are thin api_v1 wrappers; the caller (a host subagent,
6
+ * or the main agent in direct mode) drives the conversation and reads the agent's
7
+ * RAW turns. Search results are cached on disk so a repeat search is near-free.
9
8
  */
10
9
 
11
- import { readFileSync } from "fs";
12
10
  import {
13
11
  search as apiSearch,
14
12
  sessionStart,
15
13
  sessionMessage,
16
14
  sessionEnd,
17
- sessionPay,
18
15
  feedback as apiFeedback,
19
- getPaymentPolicy,
20
- patchPaymentPolicy,
21
16
  } from "./api.mjs";
22
17
  import { slugify } from "./config.mjs";
23
18
  import { Cache } from "./cache.mjs";
@@ -30,7 +25,6 @@ function normalizeCapability(c) {
30
25
  return {
31
26
  slug: slugify(name),
32
27
  name,
33
- type: "dynamic_capability", // single type now; kept for the --dynamic-only filter
34
28
  agent_id: c.agent_id || null,
35
29
  relevance: typeof c.relevance_score === "number" ? c.relevance_score : null,
36
30
  reputation: typeof c.reputation_score === "number" ? c.reputation_score : null,
@@ -60,13 +54,9 @@ export async function cmdSearch(query, opts) {
60
54
  const cache = new Cache(opts.cacheDir, { enabled: !opts.noCache });
61
55
  const maxResults = opts.max || 25;
62
56
 
63
- // The buyer-side spend policy snapshot the search API returns alongside the
64
- // menu the agent decides auto-pay-vs-bubble against it (SKILL.md Pay). Pass
65
- // it through here so it survives to the CLI's JSON output and gets cached.
66
- // On a cache hit the snapshot can be up to the search TTL (1h) stale, so the
67
- // payload carries from_cache/cached_at for the caller's pay decision.
57
+ // A repeat search inside the cache TTL (1h) is served from disk; the payload
58
+ // carries from_cache/cached_at so the caller knows how fresh the menu is.
68
59
  const cachedEntry = opts.refresh ? null : cache.readSearchEntry(query);
69
- let paymentContext = cachedEntry ? (cachedEntry.payment_context ?? null) : null;
70
60
  let capabilities = cachedEntry ? cachedEntry.capabilities : null;
71
61
  const fromCache = Boolean(cachedEntry);
72
62
  const cachedAt = cachedEntry ? (cachedEntry.ts ?? null) : null;
@@ -74,13 +64,11 @@ export async function cmdSearch(query, opts) {
74
64
  if (!capabilities) {
75
65
  const data = await apiSearch(query, {
76
66
  apiKey: opts.apiKey,
77
- userContext: opts.userContext,
78
67
  maxResults,
79
68
  timeoutMs: opts.timeoutMs,
80
69
  });
81
- paymentContext = data.payment_context ?? null;
82
70
  capabilities = dedupeSlugs((data.capabilities || []).map(normalizeCapability));
83
- cache.writeSearch(query, capabilities, paymentContext);
71
+ cache.writeSearch(query, capabilities);
84
72
  // Seed the index so session start can resolve agent_id without re-searching.
85
73
  for (const c of capabilities) {
86
74
  if (c.agent_id) {
@@ -93,9 +81,6 @@ export async function cmdSearch(query, opts) {
93
81
  }
94
82
  }
95
83
 
96
- if (opts.dynamicOnly) {
97
- capabilities = capabilities.filter((c) => c.type === "dynamic_capability");
98
- }
99
84
  capabilities = capabilities.slice(0, maxResults);
100
85
 
101
86
  return {
@@ -104,15 +89,13 @@ export async function cmdSearch(query, opts) {
104
89
  from_cache: fromCache,
105
90
  cached_at: cachedAt,
106
91
  capabilities,
107
- payment_context: paymentContext,
108
92
  };
109
93
  }
110
94
 
111
95
  /**
112
- * Resolve a slug (or agent_id) to { agentId, slug, name }. Order: cache index
113
- * banked spec file → fresh search keyed on the de-kebabed slug. The spec-file
114
- * fallback lets a depth session resolve even when the cache was filled only by
115
- * `cache put` during the breadth pass (which doesn't write the index).
96
+ * Resolve a slug (or agent_id) to { agentId, slug, name }. Order: cache index (seeded
97
+ * by a prior `search`) → fresh live search keyed on the de-kebabed slug. A cold
98
+ * `session start` with no prior search still resolves via the live search below.
116
99
  * agent_id is the only id needed — the ad_unit + price are resolved server-side.
117
100
  */
118
101
  export async function resolveCapability(slugOrId, opts) {
@@ -124,14 +107,6 @@ export async function resolveCapability(slugOrId, opts) {
124
107
  return { agentId: entry.agent_id, slug, name: entry.name || slug };
125
108
  }
126
109
 
127
- // Banked spec fallback — a breadth subagent stored the agent_id here via `cache put`.
128
- if (!opts.refresh) {
129
- const spec = cache.readSpec(slug, { ttlMs: Infinity });
130
- if (spec && spec.agent_id) {
131
- return { agentId: spec.agent_id, slug, name: spec.name || slug };
132
- }
133
- }
134
-
135
110
  // Fresh search keyed on the human form of the slug.
136
111
  const queryGuess = String(slugOrId).replace(/-/g, " ");
137
112
  const found = await cmdSearch(queryGuess, { ...opts, refresh: true, max: 25 });
@@ -211,6 +186,10 @@ export async function cmdSession(sub, positionals, opts) {
211
186
  const start = await sessionStart(
212
187
  {
213
188
  agentId: resolved.agentId,
189
+ // A2A current-task brief → forwarded to /session/start → /initiate, where it's
190
+ // stored on capability_sessions.task_context and rendered as the business agent's
191
+ // "## Current task" prompt block. Optional on the CLI path (server stores null when absent).
192
+ taskContext: opts.taskContext || null,
214
193
  initialData: parseJsonFlag(opts.initial, "initial"),
215
194
  },
216
195
  net,
@@ -256,18 +235,7 @@ export async function cmdSession(sub, positionals, opts) {
256
235
  return sessionEnd({ sessionId }, net);
257
236
  }
258
237
 
259
- if (sub === "pay") {
260
- const sessionId = positionals[0];
261
- if (!sessionId) {
262
- const e = new Error("usage: agentrelay session pay <session_id> [--credential <mpp_credential>]");
263
- e.usage = true;
264
- throw e;
265
- }
266
- // Server-side grant when no --credential; client-side MPP wallet when provided.
267
- return sessionPay({ sessionId, paymentCredential: opts.credential || null }, net);
268
- }
269
-
270
- const e = new Error(`unknown session subcommand "${sub}" (start|send|end|pay)`);
238
+ const e = new Error(`unknown session subcommand "${sub}" (start|send|end)`);
271
239
  e.usage = true;
272
240
  throw e;
273
241
  }
@@ -316,140 +284,3 @@ export async function cmdFeedback(sessionId, opts) {
316
284
  { apiKey: opts.apiKey, timeoutMs: opts.timeoutMs },
317
285
  );
318
286
  }
319
-
320
- /** Convert a dollar string/number to integer cents; throws a usage error on garbage. */
321
- export function dollarsToCents(label, value) {
322
- const n = Number(value);
323
- if (!Number.isFinite(n) || n < 0) {
324
- const e = new Error(`${label} must be a non-negative dollar amount (e.g. 5 or 2.50)`);
325
- e.usage = true;
326
- throw e;
327
- }
328
- return Math.round(n * 100);
329
- }
330
-
331
- /**
332
- * Read or update the account's server-canonical autonomous-spend policy.
333
- * No flags → GET (print current policy). Any flag → PATCH then return the new policy.
334
- * Dollar flags are converted to cents; the server is canonical (nothing stored locally).
335
- */
336
- export async function cmdBudget(opts) {
337
- const patch = {};
338
- if (opts.autoApprove != null) patch.auto_approve_under_cents = dollarsToCents("--auto-approve", opts.autoApprove);
339
- if (opts.dailyCap != null) patch.daily_cap_cents = dollarsToCents("--daily-cap", opts.dailyCap);
340
- if (opts.requireHumanOver != null) patch.require_human_over_cents = dollarsToCents("--require-human-over", opts.requireHumanOver);
341
- if (opts.perSessionCap != null) patch.per_session_cap_cents = dollarsToCents("--per-session-cap", opts.perSessionCap);
342
- if (opts.enable && opts.disable) {
343
- const e = new Error("pass only one of --enable / --disable");
344
- e.usage = true;
345
- throw e;
346
- }
347
- if (opts.enable) patch.autonomous_enabled = true;
348
- if (opts.disable) patch.autonomous_enabled = false;
349
-
350
- const policy = Object.keys(patch).length > 0
351
- ? await patchPaymentPolicy(patch, { apiKey: opts.apiKey, timeoutMs: opts.timeoutMs })
352
- : await getPaymentPolicy({ apiKey: opts.apiKey, timeoutMs: opts.timeoutMs });
353
- return { policy };
354
- }
355
-
356
- /**
357
- * Stub: real wallet connection is the (deferred) dashboard OAuth flow. The CLI can't
358
- * provision a wallet, so this just points the user at the dashboard.
359
- */
360
- export function cmdConnectWallet() {
361
- return {
362
- connected: false,
363
- message:
364
- "Wallet connection is set up from the AttentionMarket dashboard (OAuth flow). " +
365
- "The CLI can't provision a wallet yet. Once a wallet is connected, run " +
366
- "`agentrelay budget --enable --auto-approve=<$>` so the agent can auto-pay within your limits.",
367
- };
368
- }
369
-
370
- export function cmdCache(sub, slug, opts) {
371
- const cache = new Cache(opts.cacheDir, { enabled: true });
372
- if (sub === "ls" || !sub) {
373
- // Triage view read from the spec files themselves (race-safe under the
374
- // parallel breadth pass; includes each summary so the main agent can pick
375
- // what to `cache show` without opening every file).
376
- return {
377
- cache_dir: opts.cacheDir,
378
- specs: cache.listSpecHeadlines(),
379
- };
380
- }
381
- if (sub === "show") {
382
- if (!slug) {
383
- const e = new Error("usage: agentrelay cache show <slug>");
384
- e.usage = true;
385
- throw e;
386
- }
387
- const spec = cache.readSpec(slug, { ttlMs: Infinity });
388
- if (!spec) throw new Error(`no cached spec for "${slug}"`);
389
- return { deliverable: spec };
390
- }
391
- if (sub === "put") {
392
- // Persist a distilled result a subagent/main agent produced, so it can be
393
- // reviewed (`cache show`) or reused later. JSON from --file or stdin.
394
- if (!slug) {
395
- const e = new Error("usage: agentrelay cache put <slug> (--file f.json | JSON on stdin)");
396
- e.usage = true;
397
- throw e;
398
- }
399
- let raw;
400
- try {
401
- raw = opts.file ? readFileSync(opts.file, "utf-8") : readFileSync(0, "utf-8");
402
- } catch (err) {
403
- throw new Error(`could not read deliverable input: ${err.message}`);
404
- }
405
- let deliverable;
406
- try {
407
- deliverable = JSON.parse(raw);
408
- } catch (err) {
409
- const e = new Error(`deliverable must be valid JSON: ${err.message}`);
410
- e.usage = true;
411
- throw e;
412
- }
413
- if (!deliverable || typeof deliverable !== "object") {
414
- const e = new Error("deliverable must be a JSON object");
415
- e.usage = true;
416
- throw e;
417
- }
418
- deliverable.slug = deliverable.slug || slug;
419
- deliverable.ts = deliverable.ts || new Date().toISOString();
420
- // Soft contract check (schema/deliverable.json spine). Warn-only: a spec
421
- // with gaps is still worth banking, and concurrent breadth subagents must
422
- // never be blocked by validation.
423
- const warnings = [];
424
- if (!deliverable.session_id || !String(deliverable.session_id).trim()) {
425
- warnings.push("no session_id — build-outcome feedback can't be filed against this consult later");
426
- }
427
- if (!deliverable.summary || !String(deliverable.summary).trim()) {
428
- warnings.push("no summary — `cache ls` triage will show a blank line for this spec");
429
- }
430
- if (!Array.isArray(deliverable.features) || deliverable.features.length === 0) {
431
- warnings.push("no features[] — build-time has no surface map; depth sessions will have to rediscover it");
432
- } else {
433
- const missingStatus = deliverable.features.filter(
434
- (f) => !f || (f.status !== "coded" && f.status !== "surface_only"),
435
- ).length;
436
- if (missingStatus > 0) {
437
- warnings.push(
438
- `${missingStatus} feature(s) missing status ("coded"|"surface_only") — build-time can't tell banked code from surface-only`,
439
- );
440
- }
441
- }
442
- // Write ONLY the per-slug spec file — no shared index.json mutation — so N
443
- // breadth subagents can `cache put` concurrently without clobbering each
444
- // other. `cache ls` reads the spec files directly.
445
- cache.writeSpec(slug, deliverable);
446
- return { stored: true, slug, cache_dir: opts.cacheDir, warnings };
447
- }
448
- if (sub === "clear") {
449
- cache.clear();
450
- return { cleared: true, cache_dir: opts.cacheDir };
451
- }
452
- const e = new Error(`unknown cache subcommand "${sub}" (ls|show|put|clear)`);
453
- e.usage = true;
454
- throw e;
455
- }
@@ -58,7 +58,7 @@ export function resolveApiKey(flagKey) {
58
58
  return readKeyFile(CREDENTIALS_PATH) || readKeyFile(LEGACY_CREDENTIALS_PATH);
59
59
  }
60
60
 
61
- /** kebab-case a capability name into a stable slug used for cache + consult. */
61
+ /** kebab-case a capability name into a stable slug (search cache key + session resolution). */
62
62
  export function slugify(name) {
63
63
  return String(name || "")
64
64
  .toLowerCase()
@@ -1,60 +1,19 @@
1
1
  /**
2
- * System-environment probes shared by the installer and `agentrelay doctor`.
2
+ * System-environment probes used by the installer's global-CLI linking.
3
3
  *
4
4
  * These exist because the failure modes they detect are exactly the ones that
5
5
  * made "successful" installs produce `command not found` in the field:
6
- * - npx prepends its ephemeral cache .bin to PATH, shadowing (or faking) a
7
- * real global install for the lifetime of the npx process
8
6
  * - npm's global prefix may not be writable (system Node → EACCES)
9
7
  * - ~/.npm may contain root-owned files (old sudo npm) which kills BOTH
10
8
  * `npm install -g` and npx itself
11
9
  * - npm's global bin dir may not be on the user's PATH
12
10
  */
13
11
 
14
- import { existsSync, realpathSync, accessSync, statSync, constants } from "fs";
12
+ import { existsSync, accessSync, statSync, constants } from "fs";
15
13
  import { homedir } from "os";
16
- import { join, sep, dirname } from "path";
14
+ import { join, dirname } from "path";
17
15
  import { execSync } from "child_process";
18
16
 
19
- /** Where a command actually resolves on this PATH, or null. */
20
- export function resolveCommandPath(cmd) {
21
- try {
22
- const probe = process.platform === "win32" ? `where ${cmd}` : `command -v ${cmd}`;
23
- const out = execSync(probe, { encoding: "utf-8", stdio: ["ignore", "pipe", "ignore"] });
24
- return out.split("\n")[0].trim() || null;
25
- } catch {
26
- return null;
27
- }
28
- }
29
-
30
- export function realpathSafe(p) {
31
- try {
32
- return realpathSync(p);
33
- } catch {
34
- return p;
35
- }
36
- }
37
-
38
- /**
39
- * True when a resolved bin path is an ephemeral npx/npm-exec shim (or a copy
40
- * inside the running package itself), not a real install. A dangling symlink
41
- * also counts as not-a-usable-install.
42
- */
43
- export function isEphemeralBin(binPath, pkgRoot) {
44
- if (!binPath) return false;
45
- let real = binPath;
46
- try {
47
- real = realpathSync(binPath);
48
- } catch {
49
- return true;
50
- }
51
- return (
52
- real.includes(`${sep}_npx${sep}`) ||
53
- binPath.includes(`${sep}_npx${sep}`) ||
54
- (pkgRoot ? real.startsWith(realpathSafe(pkgRoot)) : false)
55
- );
56
- }
57
-
58
17
  /** npm's global prefix (e.g. /opt/homebrew, %AppData%\npm), or null. */
59
18
  export function npmGlobalPrefix() {
60
19
  try {
@@ -1,18 +1,17 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  /**
4
- * agentrelay (CLI core) — live agent sessions, payments, and deliverable fetch.
4
+ * agentrelay (CLI core) — live agent sessions and deliverable fetch.
5
5
  *
6
6
  * Wraps the AttentionMarket REST API so a coding agent can converse with expert
7
7
  * "solutions-engineer" capability agents. The caller (a host subagent, or the
8
- * main agent directly) drives the conversation via the session primitives and
9
- * banks compact specs in the on-disk cache; raw transcripts stay out of the
10
- * main context.
8
+ * main agent directly) drives the conversation via the session primitives; raw
9
+ * transcripts stay out of the main context.
11
10
  *
12
11
  * Commands:
13
12
  * agentrelay search <query> broad discovery (cheap; safe in main context)
14
- * agentrelay session start|send|pay|end drive a live consult
15
- * agentrelay cache ls|show|put|clear inspect/manage the on-disk spec cache
13
+ * agentrelay session start|send|end drive a live consult
14
+ * agentrelay feedback <id> rate a session
16
15
  * agentrelay fetch download + verify + unpack a paid deliverable
17
16
  *
18
17
  * Output: a `{ ok, ...payload, errors: [] }` envelope. --json forces machine
@@ -29,13 +28,9 @@ import {
29
28
  cmdSearch,
30
29
  cmdSession,
31
30
  cmdFeedback,
32
- cmdCache,
33
- cmdBudget,
34
- cmdConnectWallet,
35
31
  cmdFetchSession,
36
32
  } from "./lib/commands.mjs";
37
33
  import { cmdFetch } from "./lib/fetch.mjs";
38
- import { cmdDoctor, fmtDoctor } from "./lib/doctor.mjs";
39
34
  import { maybeNotifyUpdate } from "./lib/update-check.mjs";
40
35
 
41
36
  const EXIT = { OK: 0, PARTIAL: 1, USAGE: 2, AUTH: 3 };
@@ -54,18 +49,13 @@ const FLAGS_WITH_VALUE = new Set([
54
49
  "--cache-dir",
55
50
  "--timeout",
56
51
  "--max",
57
- "--user-context",
58
52
  "--initial",
53
+ "--task-context",
59
54
  "--message",
60
55
  "--data",
61
56
  "--score",
62
57
  "--text",
63
58
  "--file",
64
- "--auto-approve",
65
- "--daily-cap",
66
- "--require-human-over",
67
- "--per-session-cap",
68
- "--credential",
69
59
  "--checksum",
70
60
  "--filename",
71
61
  "--session",
@@ -154,54 +144,19 @@ const fmtSearch = (e) => {
154
144
  lines.push(` offers paid deliverable${dp}`);
155
145
  }
156
146
  }
157
- // Surface the spend policy in human output too — agents reading --json get
158
- // the full payment_context object; humans shouldn't be blind to it.
159
- const p = e.payment_context;
160
- if (p) {
161
- const dol = (c) => (c == null ? "—" : `$${(c / 100).toFixed(2)}`);
162
- lines.push(
163
- `pay policy: autonomous ${p.autonomous_enabled ? "ON" : "off"}, auto-approve under ${dol(p.auto_approve_under_cents)},` +
164
- ` daily cap left ${dol(p.daily_cap_remaining_cents)}, wallet: ${p.wallet?.methods?.length ? p.wallet.methods.join(",") : "none"}` +
165
- `${e.from_cache ? " (cached snapshot — `agentrelay budget` for live)" : ""}`,
166
- );
167
- }
168
147
  return lines.join("\n");
169
148
  };
170
149
 
171
- const fmtBudget = (e) => {
172
- const p = e.policy || {};
173
- const dol = (c) => (c == null ? "—" : `$${(c / 100).toFixed(2)}`);
174
- return [
175
- `autonomous pay: ${p.autonomous_enabled ? "ENABLED" : "disabled (ask every time)"}`,
176
- ` auto-approve under: ${dol(p.auto_approve_under_cents)}`,
177
- ` require human over: ${dol(p.require_human_over_cents)}`,
178
- ` daily cap: ${dol(p.daily_cap_cents)}`,
179
- ` per-session cap: ${dol(p.per_session_cap_cents)}`,
180
- ` wallet methods: ${(p.wallet_methods && p.wallet_methods.length) ? p.wallet_methods.join(", ") : "none connected"}`,
181
- ].join("\n");
182
- };
183
-
184
150
  const HELP = `agentrelay — converse with capability agents while you build.
185
151
 
186
152
  Conversation (LLM-driven — drive these from a subagent, or directly when you're stuck):
187
- agentrelay session start <slug> [--initial json] [--message "<first msg>"]
153
+ agentrelay session start <slug> [--initial json] [--task-context <s>] [--message "<m>"]
188
154
  agentrelay session send <id> "<m>" [--data json]
189
- agentrelay session pay <id> [--credential <mpp>] settle a payment_required
190
155
  agentrelay session end <id>
191
156
  agentrelay feedback <id> --score <0-100> --text "<s>"
192
157
 
193
- Discovery + cache:
194
- agentrelay search <query> [--max 25] [--user-context <s>] [--dynamic-only]
195
- agentrelay cache ls | show <slug> | put <slug> [--file f.json] | clear
196
-
197
- Payments (autonomous spend policy — server-canonical):
198
- agentrelay budget show current policy
199
- agentrelay budget --enable|--disable [--auto-approve=<$>] [--daily-cap=<$>]
200
- [--require-human-over=<$>] [--per-session-cap=<$>]
201
- agentrelay connect-wallet (wallet connect is set up in the dashboard)
202
-
203
- Diagnostics:
204
- agentrelay doctor check node/key/API/npm-prefix/PATH health (--json)
158
+ Discovery:
159
+ agentrelay search <query> [--max 25]
205
160
 
206
161
  Deliverables (after a paid session returns a delivery object):
207
162
  agentrelay fetch --session <id> [--message "<m>"] [dest] ONE-STEP after payment:
@@ -225,14 +180,6 @@ async function main() {
225
180
  process.stdout.write(HELP + "\n");
226
181
  process.exit(cmd ? EXIT.OK : EXIT.USAGE);
227
182
  }
228
- if (cmd === "consult" || cmd === "consult-batch") {
229
- return fail(
230
- `"${cmd}" was removed in v4 — drive a live session instead: ` +
231
- "`agentrelay session start <slug>` → `session send` → `session end` (see SKILL.md)",
232
- EXIT.USAGE,
233
- Boolean(flags.json) || !process.stdout.isTTY,
234
- );
235
- }
236
183
  if (cmd === "version" || flags.version) {
237
184
  process.stdout.write(`agentrelay ${packageVersion()}\n`);
238
185
  process.exit(EXIT.OK);
@@ -253,9 +200,12 @@ async function main() {
253
200
  }
254
201
  const json = shared.json;
255
202
 
256
- // cache, raw-URL fetch, and doctor don't need a key (doctor resolves and
257
- // reports on the key itself). `fetch --session` calls the session API.
258
- const keylessCmd = cmd === "cache" || cmd === "doctor" || (cmd === "fetch" && !flags.session);
203
+ // Raw-URL fetch downloads a signed URL directly and needs no key. Everything
204
+ // else (including `fetch --session`, which calls the session API) does. An
205
+ // UNKNOWN command needs no key either it should get the clean "unknown
206
+ // command" error below, not a misleading "no API key" one.
207
+ const KNOWN_COMMANDS = new Set(["search", "session", "feedback", "fetch"]);
208
+ const keylessCmd = (cmd === "fetch" && !flags.session) || !KNOWN_COMMANDS.has(cmd);
259
209
  if (!keylessCmd && !shared.apiKey) {
260
210
  return fail(
261
211
  "no API key — set --api-key, $AGENT_RELAY_API_KEY, or ~/.config/agent-relay/credentials.json",
@@ -272,8 +222,6 @@ async function main() {
272
222
  const payload = await cmdSearch(query, {
273
223
  ...shared,
274
224
  max: Number(flags.max) || 25,
275
- userContext: flags["user-context"] || null,
276
- dynamicOnly: Boolean(flags["dynamic-only"]),
277
225
  });
278
226
  return emit(payload, { json, human: fmtSearch });
279
227
  }
@@ -282,9 +230,9 @@ async function main() {
282
230
  const payload = await cmdSession(positionals[0], positionals.slice(1), {
283
231
  ...shared,
284
232
  initial: flags.initial || null,
233
+ taskContext: flags["task-context"] || null,
285
234
  message: flags.message || null,
286
235
  data: flags.data || null,
287
- credential: flags.credential || null,
288
236
  });
289
237
  return emit(payload, { json });
290
238
  }
@@ -298,24 +246,6 @@ async function main() {
298
246
  return emit(payload, { json });
299
247
  }
300
248
 
301
- case "budget": {
302
- const payload = await cmdBudget({
303
- ...shared,
304
- autoApprove: flags["auto-approve"] != null ? flags["auto-approve"] : null,
305
- dailyCap: flags["daily-cap"] != null ? flags["daily-cap"] : null,
306
- requireHumanOver: flags["require-human-over"] != null ? flags["require-human-over"] : null,
307
- perSessionCap: flags["per-session-cap"] != null ? flags["per-session-cap"] : null,
308
- enable: Boolean(flags.enable),
309
- disable: Boolean(flags.disable),
310
- });
311
- return emit(payload, { json, human: fmtBudget });
312
- }
313
-
314
- case "connect-wallet": {
315
- const payload = cmdConnectWallet();
316
- return emit(payload, { json, human: (e) => e.message });
317
- }
318
-
319
249
  case "fetch": {
320
250
  // --session: one-step deliverable retrieval for a PAID session — drive a turn
321
251
  // to mint a fresh signed download_url, then download+unpack. Needs the key.
@@ -351,28 +281,9 @@ async function main() {
351
281
  });
352
282
  }
353
283
 
354
- case "doctor": {
355
- const payload = await cmdDoctor({ ...shared, apiKey: flags["api-key"] || null });
356
- const exitCode = payload.exit_code;
357
- return emit(payload, { json, ok: payload.doctor_ok, exitCode, human: fmtDoctor });
358
- }
359
-
360
- case "cache": {
361
- const payload = cmdCache(positionals[0], positionals[1], { ...shared, file: flags.file || null });
362
- const human = Array.isArray(payload.specs)
363
- ? (e) =>
364
- [`${e.specs.length} banked specs (${e.cache_dir})`, ...e.specs.map((s) => {
365
- const feat = s.features != null ? ` (${s.features} features)` : "";
366
- const sum = s.summary ? ` — ${s.summary.length > 100 ? s.summary.slice(0, 100) + "…" : s.summary}` : "";
367
- return ` ${s.slug}${feat}${sum}`;
368
- })].join("\n")
369
- : undefined;
370
- return emit(payload, { json, human });
371
- }
372
-
373
284
  default:
374
285
  return fail(
375
- `unknown command "${cmd}" (session|search|feedback|cache|budget|connect-wallet|fetch|doctor)`,
286
+ `unknown command "${cmd}" (search|session|feedback|fetch)`,
376
287
  EXIT.USAGE,
377
288
  json,
378
289
  );
@@ -1,15 +1,10 @@
1
1
  /**
2
- * Tests for the session-primitive output shaping and the cache put/show
3
- * round-trip (the LLM-driven path's transport). No network. Run:
4
- * `node --test bin/test/session.test.mjs`.
2
+ * Tests for the session-primitive output shaping (the LLM-driven path's
3
+ * transport). No network. Run: `node --test bin/test/session.test.mjs`.
5
4
  */
6
5
  import { test } from "node:test";
7
6
  import assert from "node:assert/strict";
8
- import { mkdtempSync, rmSync } from "node:fs";
9
- import { tmpdir } from "node:os";
10
- import { join } from "node:path";
11
7
  import { shapeSessionTurn, deliveryToFetchArgs } from "../lib/commands.mjs";
12
- import { Cache } from "../lib/cache.mjs";
13
8
  import { slugify } from "../lib/config.mjs";
14
9
 
15
10
  test("shapeSessionTurn: passes the agent message through verbatim + surfaces flags", () => {
@@ -81,27 +76,7 @@ test("deliveryToFetchArgs: maps a delivery object to cmdFetch args (tolerant of
81
76
  assert.equal(deliveryToFetchArgs(null), null);
82
77
  });
83
78
 
84
- test("cache: put/show round-trip preserves an arbitrary LLM-chosen structure", () => {
85
- const dir = mkdtempSync(join(tmpdir(), "agent-relay-cache-"));
86
- try {
87
- const cache = new Cache(dir);
88
- // A non-dev shape an agent's distillation might use — must survive verbatim.
89
- const distilled = {
90
- summary: "Quote obtained",
91
- quote_usd: 1240,
92
- coverages: ["liability", "collision"],
93
- next_steps: ["sign", "pay deposit"],
94
- };
95
- cache.writeSpec("acme-insurance", distilled);
96
- const got = cache.readSpec("acme-insurance", { ttlMs: Infinity });
97
- assert.deepEqual(got, distilled);
98
- assert.deepEqual(cache.listSpecs(), ["acme-insurance"]);
99
- } finally {
100
- rmSync(dir, { recursive: true, force: true });
101
- }
102
- });
103
-
104
- test("slugify: stable kebab-case keys for cache/consult", () => {
79
+ test("slugify: stable kebab-case keys for slugs", () => {
105
80
  assert.equal(slugify("Open-Meteo Weather API Solutions Engineer"), "open-meteo-weather-api-solutions-engineer");
106
81
  assert.equal(slugify("Acme Insurance ✨ (Auto)"), "acme-insurance-auto");
107
82
  assert.equal(slugify(""), "capability");
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Tests for the pure sysenv probes used by the installer's global-CLI linking.
3
+ * No network, no real npm install. Run: `node --test bin/test/sysenv.test.mjs`.
4
+ */
5
+ import { test } from "node:test";
6
+ import assert from "node:assert/strict";
7
+ import { dirOnPath, npmGlobalBinDir, globalBinShimPath } from "../lib/sysenv.mjs";
8
+
9
+ test("dirOnPath: exact component match, trailing-slash tolerant", () => {
10
+ const delim = process.platform === "win32" ? ";" : ":";
11
+ const pathVar = ["/usr/bin", "/opt/homebrew/bin/"].join(delim);
12
+ assert.equal(dirOnPath("/opt/homebrew/bin", pathVar), true);
13
+ assert.equal(dirOnPath("/usr/bin", pathVar), true);
14
+ assert.equal(dirOnPath("/usr/local/bin", pathVar), false);
15
+ assert.equal(dirOnPath(null, pathVar), false);
16
+ });
17
+
18
+ test("npmGlobalBinDir/globalBinShimPath shape per platform", () => {
19
+ const prefix = process.platform === "win32" ? "C:\\npm" : "/opt/homebrew";
20
+ const binDir = npmGlobalBinDir(prefix);
21
+ const shim = globalBinShimPath(prefix, "agentrelay");
22
+ if (process.platform === "win32") {
23
+ assert.equal(binDir, prefix);
24
+ assert.ok(shim.endsWith("agentrelay.cmd"));
25
+ } else {
26
+ assert.equal(binDir, `${prefix}/bin`);
27
+ assert.ok(shim.endsWith("/bin/agentrelay"));
28
+ }
29
+ assert.equal(npmGlobalBinDir(null), null);
30
+ });
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "agentic-relay",
3
- "version": "4.3.0",
4
- "description": "Install Agent Relay AI search across 17+ agents — Claude Code, Codex, Cursor, Gemini CLI, Goose, Windsurf, Cline, BoltAI, Claude Desktop, VS Code, Amazon Q, Roo Code, Witsy, LibreChat, OpenClaw, Tome, Raycast — plus the `agentrelay` CLI for live agent sessions, payments, and deliverable fetch",
3
+ "version": "5.0.0",
4
+ "description": "Install Agent Relay AI search across 17+ agents — Claude Code, Codex, Cursor, Gemini CLI, Goose, Windsurf, Cline, BoltAI, Claude Desktop, VS Code, Amazon Q, Roo Code, Witsy, LibreChat, OpenClaw, Tome, Raycast — plus the `agentrelay` CLI for live agent sessions and deliverable fetch",
5
5
  "bin": {
6
6
  "agentrelay": "bin/cli.mjs"
7
7
  },
@@ -42,7 +42,6 @@
42
42
  },
43
43
  "files": [
44
44
  "bin/",
45
- "schema/",
46
45
  "skill/",
47
46
  "CLAUDE.md"
48
47
  ]