@a2a-exposed/control-core 0.8.26 → 0.8.28

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -2,50 +2,29 @@
2
2
 
3
3
  [![npm](https://img.shields.io/npm/v/a2a-exposed)](https://www.npmjs.com/package/a2a-exposed) [![CI](https://github.com/telegraphic-dev/a2a-exposed/actions/workflows/ci.yml/badge.svg)](https://github.com/telegraphic-dev/a2a-exposed/actions/workflows/ci.yml)
4
4
 
5
- a2a-exposed gives an AI agent a public [A2A](https://a2a-protocol.org) address. A Cloudflare Worker serves the agent card and the JSON-RPC endpoint. Inbound messages become tasks on the A2A endpoint. Webhooks wake your agent. Without one, it polls, guided by next-check hints. The agent reads the A2A endpoint and replies with `npx -y a2a-exposed@latest`. The same command sends messages to other A2A agents (1.0 or 0.3). The owner pages on that A2A endpoint are `/` and `/device`.
5
+ a2a-exposed gives an AI agent a public [A2A](https://a2a-protocol.org) address. A Cloudflare Worker serves the agent card and the JSON-RPC endpoint, and inbound messages become tasks. Webhooks wake your agent, or the agent polls. The owner pages are `/` and `/device`.
6
6
 
7
7
  ## Who it's for
8
8
 
9
9
  Agents that can call out over HTTP and cannot run a public server: hosted assistants, coding agents, routines, and chat bots. Peers discover the card at `https://<host>/.well-known/agent-card.json`, send `SendMessage` with a per-peer token, and read the reply with `GetTask` (or a push notification, when the peer registered one).
10
10
 
11
- ## Quick start
12
-
13
- No Cloudflare account, or no preference? Start with a [hosted agent](docs/getting-started.md#hosted-inbox) at `https://<name>.a2a.exposed`. It is in open beta with a Free plan; see [pricing](https://a2a.exposed/pricing).
14
-
15
- Claude Code cloud sessions use the [hosted connector](docs/mcp.md#hosted-connector), with no skill install or CLI. Skip project installs in shared cloud checkouts; optional skills use `npx -y skills add telegraphic-dev/a2a-exposed -g`.
16
-
17
- Self-hosting requires Node 22.18+. The skills are installed into the project the agent works in. They do not install the CLI. Every command runs through npx.
18
-
19
- 1. **Add the skills to the project**
20
-
21
- ```bash
22
- npx -y skills add telegraphic-dev/a2a-exposed
23
- ```
24
-
25
- 2. **Deploy an A2A endpoint** in your Cloudflare account. [Self-hosting](docs/self-hosting.md) covers the login: `cf auth login`, a named profile (`--cf-profile`), an API token, and `--account-id` when that token can see more than one account.
26
-
27
- Before running `init` or another Cloudflare change, the agent shows you the account name/id from `npx cf auth whoami` and the plan: Worker name, D1 database, hostname and any workers.dev registration or custom-domain DNS record, and owner/wake secrets by purpose without values. It waits for your explicit yes; a login is not consent. This also applies to `deploy`, `tunnel create`, façade tunnel/Access steps, and `uninstall`. See the [consent checklist](skills/a2a-exposed-setup/references/deploy.md#before-cloudflare-changes). Ask before anything billable or DNS changes on a zone that already serves something.
28
-
29
- ```bash
30
- npx -y a2a-exposed@latest init --hostname agent.example.com --agent-name "My Agent"
31
- ```
32
-
33
- Omit `--hostname` to serve on `*.workers.dev`. If you already have a wake URL, put `WAKE_WEBHOOK_URL` and `WAKE_WEBHOOK_KEY` in the environment before `init`. Never pass secrets on the command line.
11
+ ## Hosted
34
12
 
35
- 3. **Check the deployment, then let peers connect**
13
+ No Cloudflare account is required. [a2a.exposed](https://a2a.exposed/hosted) hosts the A2A endpoint and is open to everyone. It is in open beta with a Free plan; see [pricing](https://a2a.exposed/pricing). Sign in at https://a2a.exposed/app and create an agent at `https://<name>.a2a.exposed`.
36
14
 
37
- ```bash
38
- npx -y a2a-exposed@latest status
39
- npx -y a2a-exposed@latest pair set-password --web
40
- ```
15
+ When the CLI cannot run, connect https://a2a.exposed/mcp. Sign-in is the owner identity. The owner token stays on the A2A endpoint. Claude Code cloud sessions use the [hosted connector](docs/mcp.md#hosted-connector). The steps are in [Getting started](docs/getting-started.md#hosted-inbox).
41
16
 
42
- `status` is read-only and prints the next step. Open the one-time link (15 minutes by default, `--ttl` from 1 to 60) and choose the approval password yourself. Another agent then runs `npx -y a2a-exposed@latest connect https://agent.example.com` (the code lasts 60 minutes). You approve that request on the `/device` page, or with `npx -y a2a-exposed@latest pair approve <code>`. Removing the A2A endpoint later is `npx -y a2a-exposed@latest uninstall` ([Self-hosting](docs/self-hosting.md#remove-an-inbox)).
17
+ ## Self-host
43
18
 
44
- After the skill is in the project, you can ask the agent to set this up. It follows the setup skill. Claude Code cloud sessions follow the [hosted checklist](docs/mcp.md#hosted-connector); self-hosted deployment runs on a machine you control. Claude Code: `/plugin marketplace add telegraphic-dev/a2a-exposed`, then `/plugin install a2a-exposed@a2a-exposed`. [Getting started](docs/getting-started.md) has Node, the client table, and what to do if setup stops halfway.
19
+ Self-hosting needs Node 22.18+. Commands run through npx. Confirm the Cloudflare account and the plan before `init` changes it. The consent checklist is in the [setup skill](skills/a2a-exposed-setup/references/deploy.md#before-cloudflare-changes). [Self-hosting](docs/self-hosting.md) covers login, API tokens, and `--account-id`.
45
20
 
46
- ## Hosted
21
+ ```bash
22
+ npx -y skills add telegraphic-dev/a2a-exposed
23
+ npx -y a2a-exposed@latest init --hostname agent.example.com --agent-name "My Agent"
24
+ npx -y a2a-exposed@latest status
25
+ ```
47
26
 
48
- [a2a.exposed](https://a2a.exposed/hosted) hosts the A2A endpoint and is open to everyone. Pricing is at [a2a.exposed/pricing](https://a2a.exposed/pricing). Sign in at https://a2a.exposed/app to create an agent; no Cloudflare account is needed. When the CLI cannot run, connect https://a2a.exposed/mcp. Sign-in is the owner identity. The owner token stays on the A2A endpoint. Claude Code cloud sessions with the hosted connector and a routine wake are tested end to end. Other Claude clients and Codex are untested on this address. When to use the hosted endpoint, and the connect commands, are in [Getting started](docs/getting-started.md#hosted-inbox).
27
+ Omit `--hostname` to serve on `*.workers.dev`. If you already have a wake URL, put `WAKE_WEBHOOK_URL` and `WAKE_WEBHOOK_KEY` in the environment before `init`. Never pass secrets on the command line. Pairing and the approval password are in [Getting started](docs/getting-started.md).
49
28
 
50
29
  ## Docs
51
30
 
package/dist/index.js CHANGED
@@ -3182,6 +3182,324 @@ var CONTROL_MIGRATIONS = [
3182
3182
  { name: "0014_cloudflare_inbox_agent.sql", sql: cloudflare_inbox_agent_default },
3183
3183
  { name: "0015_cloudflare_grant_access.sql", sql: cloudflare_grant_access_default }
3184
3184
  ];
3185
+
3186
+ // src/core/redact.ts
3187
+ var REDACTED = "[redacted]";
3188
+ var TOKEN_RE = /(?:a2aow_|s2a_|a2amcpr_|a2amcp_|a2aot_)[A-Za-z0-9_-]{20,}/g;
3189
+ var USERINFO_RE = /([a-z][a-z0-9+.-]*:\/\/)[^/?\s]*@/gi;
3190
+ var BEARER_RE = /(bearer\s+)(\S{8,})/gi;
3191
+ var BASIC_RE = /(basic\s+)(\S+)/gi;
3192
+ var PEM_RE = /-----BEGIN (?:[A-Z0-9]+ )?PRIVATE KEY-----[\s\S]*?(?:-----END (?:[A-Z0-9]+ )?PRIVATE KEY-----|$)/g;
3193
+ var API_KEY_RE = /(?:(?:ghp|gho|ghu|ghs|ghr)_[A-Za-z0-9]{20,}|github_pat_[A-Za-z0-9_]{20,}|\bAKIA[0-9A-Z]{16}\b|\bsk_(?:live|test)_[A-Za-z0-9]{10,}|\brk_(?:live|test)_[A-Za-z0-9]{10,}|\bsk-(?:proj-)?[A-Za-z0-9_-]{20,}|\bxox[baprs]-[A-Za-z0-9-]{10,}|\bAIza[0-9A-Za-z_-]{35}\b)/g;
3194
+ var JWT_RE = /\beyJ[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\b/g;
3195
+ var AUTH_RE = /\b((?:proxy-)?authorization)(\s*[:=]\s*)([^\n]+)/gi;
3196
+ var QUERY_RE = /(^|[?&#\s])([^?&#\s="'<>]+)(\s*=\s*)("(?:\\.|[^"\\])*(?:"|$)|'(?:\\.|[^'\\])*(?:'|$)|(?:\\(?!["'])|[^&#\s"'<>\\])*)/g;
3197
+ var ASSIGN_RE = /\b([A-Za-z_][A-Za-z0-9_]*)(\s*=\s*)/g;
3198
+ var SECRET_EXACT = /* @__PURE__ */ new Set([
3199
+ "authorization",
3200
+ "proxyauthorization",
3201
+ "cookie",
3202
+ "setcookie",
3203
+ "password",
3204
+ "passwd",
3205
+ "secret",
3206
+ "token",
3207
+ "apikey",
3208
+ "accesstoken",
3209
+ "refreshtoken",
3210
+ "idtoken",
3211
+ "clientsecret",
3212
+ "privatekey",
3213
+ "hmacsecret",
3214
+ "credential",
3215
+ "credentials",
3216
+ "bearer",
3217
+ "webhookkey",
3218
+ "accessclientid",
3219
+ "accessclientsecret"
3220
+ ]);
3221
+ var SECRET_SUFFIX = [
3222
+ "authorization",
3223
+ "token",
3224
+ "secret",
3225
+ "password",
3226
+ "passwd",
3227
+ "credential",
3228
+ "credentials",
3229
+ "apikey",
3230
+ "clientsecret",
3231
+ "privatekey",
3232
+ "webhookkey",
3233
+ "accessclientid",
3234
+ "accessclientsecret",
3235
+ "cookie"
3236
+ ];
3237
+ function parameterName(name) {
3238
+ return name.replace(/\+/g, " ").replace(/(?:%[0-9a-f]{2})+/gi, (encoded) => {
3239
+ try {
3240
+ return decodeURIComponent(encoded);
3241
+ } catch {
3242
+ return encoded.replace(/%([0-7][0-9a-f])/gi, (_all, hex) => String.fromCharCode(parseInt(hex, 16)));
3243
+ }
3244
+ });
3245
+ }
3246
+ function compactName(name) {
3247
+ return name.trim().toLowerCase().replace(/[^a-z0-9]/g, "");
3248
+ }
3249
+ function secretName(name) {
3250
+ const compact = compactName(name);
3251
+ if (!compact || compact === "key" || compact === "name" || compact === "value" || compact === "id") return false;
3252
+ if (SECRET_EXACT.has(compact)) return true;
3253
+ return SECRET_SUFFIX.some((suffix) => compact.length > suffix.length && compact.endsWith(suffix));
3254
+ }
3255
+ function replaceAll(value, pattern, replacement) {
3256
+ pattern.lastIndex = 0;
3257
+ return value.replace(pattern, replacement);
3258
+ }
3259
+ function quotedValueEnd(text, start) {
3260
+ const quote = text[start];
3261
+ if (quote !== '"' && quote !== "'") return -1;
3262
+ for (let i = start + 1; i < text.length; i += 1) {
3263
+ if (text[i] === "\\") {
3264
+ i += 1;
3265
+ continue;
3266
+ }
3267
+ if (text[i] === quote) return i + 1;
3268
+ }
3269
+ return text.length;
3270
+ }
3271
+ function unquotedValueEnd(text, start) {
3272
+ let i = start;
3273
+ while (i < text.length && !/\s/.test(text[i])) i += 1;
3274
+ return i;
3275
+ }
3276
+ function redactAssignments(text) {
3277
+ const pattern = new RegExp(ASSIGN_RE.source, "g");
3278
+ const parameters = Array.from(text.matchAll(new RegExp(QUERY_RE.source, "g")));
3279
+ let parameter = 0;
3280
+ let out = "";
3281
+ let last = 0;
3282
+ let match;
3283
+ while ((match = pattern.exec(text)) !== null) {
3284
+ while (parameter < parameters.length && parameters[parameter].index + parameters[parameter][0].length <= match.index) parameter += 1;
3285
+ if (parameter < parameters.length && parameters[parameter].index <= match.index) continue;
3286
+ const name = match[1] ?? "";
3287
+ if (!secretName(name)) continue;
3288
+ const sep = match[2] ?? "=";
3289
+ const valueStart = match.index + match[0].length;
3290
+ const quoted = quotedValueEnd(text, valueStart);
3291
+ const valueEnd = quoted >= 0 ? quoted : unquotedValueEnd(text, valueStart);
3292
+ out += text.slice(last, match.index);
3293
+ out += secretName(name) && valueEnd > valueStart ? `${name}${sep}${REDACTED}` : text.slice(match.index, valueEnd);
3294
+ last = valueEnd;
3295
+ pattern.lastIndex = valueEnd;
3296
+ }
3297
+ return out + text.slice(last);
3298
+ }
3299
+ function jsonValueEnd(text, start) {
3300
+ if (text[start] === '"') return quotedValueEnd(text, start);
3301
+ if (text[start] === "[" || text[start] === "{") {
3302
+ const closers = [text[start] === "[" ? "]" : "}"];
3303
+ for (let i = start + 1; i < text.length; i += 1) {
3304
+ const char = text[i];
3305
+ if (char === '"') i = quotedValueEnd(text, i) - 1;
3306
+ else if (char === "[" || char === "{") closers.push(char === "[" ? "]" : "}");
3307
+ else if (char === closers[closers.length - 1]) {
3308
+ closers.pop();
3309
+ if (!closers.length) return i + 1;
3310
+ }
3311
+ }
3312
+ return text.length;
3313
+ }
3314
+ let end = start;
3315
+ while (end < text.length && !/[\s,}\]]/.test(text[end])) end += 1;
3316
+ return end;
3317
+ }
3318
+ function redactJsonFields(text, depth, cache) {
3319
+ let out = "";
3320
+ let last = 0;
3321
+ for (let i = 0; i < text.length; i += 1) {
3322
+ if (text[i] !== '"') continue;
3323
+ const keyEnd = quotedValueEnd(text, i);
3324
+ let cursor = keyEnd;
3325
+ while (/\s/.test(text[cursor] ?? "") && cursor < text.length) cursor += 1;
3326
+ if (text[cursor] === ":") {
3327
+ let name;
3328
+ try {
3329
+ name = JSON.parse(text.slice(i, keyEnd));
3330
+ } catch {
3331
+ i = keyEnd - 1;
3332
+ continue;
3333
+ }
3334
+ if (secretName(name)) {
3335
+ cursor += 1;
3336
+ while (cursor < text.length && /\s/.test(text[cursor])) cursor += 1;
3337
+ const end = jsonValueEnd(text, cursor);
3338
+ if (end > cursor) {
3339
+ out += text.slice(last, cursor) + JSON.stringify(REDACTED);
3340
+ last = end;
3341
+ i = end - 1;
3342
+ continue;
3343
+ }
3344
+ }
3345
+ }
3346
+ if (text[cursor] !== ":") {
3347
+ try {
3348
+ const decoded = JSON.parse(text.slice(i, keyEnd));
3349
+ const cleaned = redactNested(decoded, depth + 1, cache);
3350
+ if (cleaned !== decoded) {
3351
+ out += text.slice(last, i) + JSON.stringify(cleaned);
3352
+ last = keyEnd;
3353
+ }
3354
+ } catch {
3355
+ }
3356
+ }
3357
+ i = keyEnd - 1;
3358
+ }
3359
+ return out + text.slice(last);
3360
+ }
3361
+ function redactNested(value, depth, cache = /* @__PURE__ */ new Map()) {
3362
+ try {
3363
+ if (depth >= 64) return REDACTED;
3364
+ const key = depth + ":" + value;
3365
+ const cached = cache.get(key);
3366
+ if (cached !== void 0) return cached;
3367
+ const text = redactText(value, depth, cache);
3368
+ const decoded = parameterName(text);
3369
+ let result = text;
3370
+ if (decoded !== text) {
3371
+ const cleaned = redactNested(decoded, depth + 1, cache);
3372
+ if (cleaned !== decoded) result = encodeURIComponent(cleaned);
3373
+ }
3374
+ cache.set(key, result);
3375
+ return result;
3376
+ } catch {
3377
+ return REDACTED;
3378
+ }
3379
+ }
3380
+ function redactText(value, depth, cache) {
3381
+ let text = redactJsonFields(value, depth, cache);
3382
+ text = replaceAll(text, PEM_RE, REDACTED);
3383
+ text = replaceAll(text, JWT_RE, REDACTED);
3384
+ text = replaceAll(text, API_KEY_RE, REDACTED);
3385
+ text = replaceAll(text, TOKEN_RE, REDACTED);
3386
+ text = replaceAll(text, BEARER_RE, `$1${REDACTED}`);
3387
+ text = replaceAll(text, BASIC_RE, `$1${REDACTED}`);
3388
+ text = replaceAll(text, AUTH_RE, (_all, name, sep, rest) => {
3389
+ if (rest.trim().length < 8 && !/bearer|basic/i.test(rest)) return `${name}${sep}${rest}`;
3390
+ return `${name}${sep}${REDACTED}`;
3391
+ });
3392
+ text = replaceAll(text, USERINFO_RE, `$1${REDACTED}@`);
3393
+ const cleanParameter = (all, sep, name, assignment, item) => {
3394
+ let decoded = name;
3395
+ while (true) {
3396
+ const next = parameterName(decoded);
3397
+ if (next === decoded) break;
3398
+ decoded = next;
3399
+ }
3400
+ let setup = false;
3401
+ if (decoded.trim().toLowerCase() === "t") {
3402
+ let candidate = item.replace(/^["']|["']$/g, "");
3403
+ while (true) {
3404
+ const next = parameterName(candidate);
3405
+ if (next === candidate) break;
3406
+ candidate = next;
3407
+ }
3408
+ setup = /^[A-Za-z0-9_-]{16,}/.test(candidate);
3409
+ }
3410
+ if (!item) return all;
3411
+ if (!secretName(decoded) && !setup) {
3412
+ const quote = item[0];
3413
+ const wrapped = (quote === '"' || quote === "'") && item.endsWith(quote) && item.length > 1;
3414
+ const cleaned = wrapped ? quote + redactNested(item.slice(1, -1), depth + 1, cache) + quote : redactNested(item, depth + 1, cache);
3415
+ return `${sep}${name}${assignment}${cleaned}`;
3416
+ }
3417
+ return `${sep}${name}${assignment}${REDACTED}`;
3418
+ };
3419
+ const strings = [];
3420
+ for (let i = 0; i < text.length; i += 1) {
3421
+ if (text[i] !== '"') continue;
3422
+ const end = quotedValueEnd(text, i);
3423
+ try {
3424
+ JSON.parse(text.slice(i, end));
3425
+ strings.push([i, end]);
3426
+ } catch {
3427
+ }
3428
+ i = end - 1;
3429
+ }
3430
+ const parameters = new RegExp(QUERY_RE.source, "g");
3431
+ let out = "";
3432
+ let last = 0;
3433
+ let match;
3434
+ while ((match = parameters.exec(text)) !== null) {
3435
+ if (strings.some(([start, end2]) => match.index >= start && match.index < end2)) continue;
3436
+ const valueStart = match.index + match[1].length + match[2].length + match[3].length;
3437
+ const quoted = quotedValueEnd(text, valueStart);
3438
+ let end = quoted >= 0 ? quoted : valueStart;
3439
+ while (end < text.length && !/[&#\s<>]/.test(text[end])) {
3440
+ if ((text[end] === '"' || text[end] === "'") && (quoted >= 0 || text[end - 1] === "=")) end = quotedValueEnd(text, end);
3441
+ else end += 1;
3442
+ }
3443
+ match[4] = text.slice(valueStart, end);
3444
+ match[0] = text.slice(match.index, end);
3445
+ parameters.lastIndex = end;
3446
+ const cleaned = cleanParameter(match[0], match[1], match[2], match[3], match[4]);
3447
+ if (cleaned === match[0]) {
3448
+ parameters.lastIndex = match.index + match[1].length + match[2].length + match[3].length;
3449
+ continue;
3450
+ }
3451
+ out += text.slice(last, match.index) + cleaned;
3452
+ last = parameters.lastIndex;
3453
+ }
3454
+ text = out + text.slice(last);
3455
+ text = redactAssignments(text);
3456
+ return text;
3457
+ }
3458
+ function own(record, name) {
3459
+ for (const key of Object.keys(record)) {
3460
+ if (key.toLowerCase() === name) return { key, value: record[key] };
3461
+ }
3462
+ return null;
3463
+ }
3464
+ function pairLabel(record) {
3465
+ const name = own(record, "name");
3466
+ const key = own(record, "key");
3467
+ if (name && typeof name.value === "string" && secretName(name.value)) return name.value;
3468
+ if (key && typeof key.value === "string" && secretName(key.value)) return key.value;
3469
+ return "";
3470
+ }
3471
+ function walk(value, seen) {
3472
+ if (typeof value === "string") return redactNested(value, 0);
3473
+ if (!value || typeof value !== "object") return value;
3474
+ if (seen.has(value)) return REDACTED;
3475
+ seen.add(value);
3476
+ if (Array.isArray(value)) {
3477
+ if (value.length === 2 && typeof value[0] === "string" && secretName(value[0])) return [value[0], REDACTED];
3478
+ return value.map((item) => walk(item, seen));
3479
+ }
3480
+ const proto = Object.getPrototypeOf(value);
3481
+ if (proto !== Object.prototype && proto !== null) return value;
3482
+ const record = value;
3483
+ const hidden = /* @__PURE__ */ new Set();
3484
+ const valueField = own(record, "value");
3485
+ if (valueField && pairLabel(record)) hidden.add(valueField.key);
3486
+ const out = {};
3487
+ for (const [key, item] of Object.entries(record)) {
3488
+ if (hidden.has(key) || secretName(key)) {
3489
+ out[key] = REDACTED;
3490
+ continue;
3491
+ }
3492
+ out[key] = walk(item, seen);
3493
+ }
3494
+ return out;
3495
+ }
3496
+ function sanitized(value) {
3497
+ return walk(value, /* @__PURE__ */ new WeakSet());
3498
+ }
3499
+ function redact(value) {
3500
+ if (typeof value === "string") return redactNested(value, 0);
3501
+ return sanitized(value);
3502
+ }
3185
3503
  export {
3186
3504
  ACCOUNT_SETTINGS_READ,
3187
3505
  CONTROL_MIGRATIONS,
@@ -3278,6 +3596,7 @@ export {
3278
3596
  readCloudflareInbox,
3279
3597
  readCloudflareInboxById,
3280
3598
  readInboxById,
3599
+ redact,
3281
3600
  refreshCloudflareGrant,
3282
3601
  refreshTokenAfterResponse,
3283
3602
  registerWorkersDevSubdomain,
@@ -3292,6 +3611,7 @@ export {
3292
3611
  runInboxStep,
3293
3612
  runRemovalStep,
3294
3613
  sanitizeCloudflareText,
3614
+ sanitized,
3295
3615
  saveCloudflareGrant,
3296
3616
  saveWakeSettings,
3297
3617
  sealCloudflareRefresh,
@@ -32,3 +32,4 @@ export type { ApprovalOidcInput, InboxMigration, InboxWizard, ProvisionFields, W
32
32
  export { INBOX_MIGRATIONS } from "../provision/inbox-migrations.js";
33
33
  export { CONTROL_MIGRATIONS, CONTROL_MIGRATIONS_DIR } from "./migrations.js";
34
34
  export type { ControlMigration } from "./migrations.js";
35
+ export { redact, sanitized } from "./redact.js";
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Copy `value` with secrets removed.
3
+ * A `{ name, value }` or `{ key, value }` pair, and a `[name, value]` pair, lose `value`
4
+ * when that name is a secret. A pair whose name is an ordinary field stays.
5
+ */
6
+ export declare function sanitized<T>(value: T): T;
7
+ /** Remove secrets from text. An object is copied with {@link sanitized}. */
8
+ export declare function redact(value: string): string;
9
+ export declare function redact(value: unknown): unknown;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@a2a-exposed/control-core",
3
- "version": "0.8.26",
3
+ "version": "0.8.28",
4
4
  "description": "A2A endpoint provisioning, routing, and D1 migrations for a2a-exposed.",
5
5
  "type": "module",
6
6
  "scripts": {