jinzd-ai-cli 0.4.154 → 0.4.156

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 (33) hide show
  1. package/dist/{batch-W57MV5OT.js → batch-DBOCPVH5.js} +2 -2
  2. package/dist/{chat-index-LUQWWLKO.js → chat-index-2I7ZHRE5.js} +2 -1
  3. package/dist/{chunk-SH7NTECG.js → chunk-3DCAQKVZ.js} +1 -1
  4. package/dist/{chunk-HVNEBTSF.js → chunk-3SQMKA4I.js} +1 -1
  5. package/dist/{chunk-UE26B3RO.js → chunk-3WLEDKOW.js} +1 -1
  6. package/dist/{chunk-2IODI5TI.js → chunk-7OCOFVQP.js} +1 -1
  7. package/dist/{chunk-NP7WOVIH.js → chunk-AACNCJMD.js} +1 -1
  8. package/dist/{chunk-ZAYDVWY4.js → chunk-GQ647SF3.js} +23 -787
  9. package/dist/{chunk-RXM76HB7.js → chunk-MM3F43H6.js} +3 -117
  10. package/dist/chunk-NZ4X6GUC.js +230 -0
  11. package/dist/{chunk-O6MLS5QO.js → chunk-OJL3PY36.js} +0 -226
  12. package/dist/{hub-OP7EWTQQ.js → chunk-Q3ZUDA6S.js} +10 -237
  13. package/dist/chunk-RUJQ5OUB.js +51 -0
  14. package/dist/chunk-SLSWPBK3.js +120 -0
  15. package/dist/chunk-TOTEUETI.js +768 -0
  16. package/dist/{chunk-OSTMMSOV.js → chunk-UAPNBQLU.js} +1 -1
  17. package/dist/{chunk-XWYWASPT.js → chunk-Z2UJDFJK.js} +4 -4
  18. package/dist/{ci-JYZGZSMP.js → ci-AWA6KC3X.js} +3 -3
  19. package/dist/{constants-S4Y6A25E.js → constants-QDTWBWWC.js} +1 -1
  20. package/dist/{doctor-cli-FMTMDO2Z.js → doctor-cli-4MTG6SFH.js} +6 -6
  21. package/dist/electron-server.js +740 -44
  22. package/dist/hub-QRDXG527.js +260 -0
  23. package/dist/{hub-server-OH7AYQIW.js → hub-server-GSTG5MNE.js} +4 -2
  24. package/dist/index.js +50 -44
  25. package/dist/persist-UI6WRBGB.js +12 -0
  26. package/dist/{run-tests-3QAZGHP2.js → run-tests-CNURD2ST.js} +2 -2
  27. package/dist/{run-tests-4XNY7QB4.js → run-tests-NPWSCWP5.js} +1 -1
  28. package/dist/{server-W4TBZN6I.js → server-HFG2SM3Y.js} +6 -5
  29. package/dist/{server-UL42EXOA.js → server-RRUCZMMM.js} +124 -29
  30. package/dist/{task-orchestrator-RLAZK5EB.js → task-orchestrator-GF6ZMNUK.js} +6 -5
  31. package/dist/web/client/app.js +138 -0
  32. package/dist/web/client/index.html +28 -0
  33. package/package.json +1 -1
@@ -1,12 +1,8 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  HubAgent,
4
- assignRoleColors,
5
- isConverged,
6
- renderHubBanner,
7
- renderHubEvent
8
- } from "./chunk-O6MLS5QO.js";
9
- import "./chunk-PDX44BCA.js";
4
+ isConverged
5
+ } from "./chunk-NZ4X6GUC.js";
10
6
 
11
7
  // src/hub/discuss.ts
12
8
  var DiscussionOrchestrator = class {
@@ -41,6 +37,10 @@ var DiscussionOrchestrator = class {
41
37
  getAgents() {
42
38
  return this.agents;
43
39
  }
40
+ /** Current discussion state (used to persist even after an error). */
41
+ getState() {
42
+ return this.state;
43
+ }
44
44
  /** Signal the orchestrator to stop after current turn */
45
45
  abort() {
46
46
  this.aborted = true;
@@ -354,236 +354,9 @@ function resolveRoleProviders(roles, lookup, defaultProvider, defaultModel, avai
354
354
  return { roles: outRoles, assignments, warnings };
355
355
  }
356
356
 
357
- // src/hub/index.ts
358
- import { readFileSync, existsSync } from "fs";
359
- import chalk from "chalk";
360
- async function startHub(options, configManager, providers) {
361
- if (options.listPresets) {
362
- console.log("\n Available discussion presets:\n");
363
- for (const p of listPresets()) {
364
- console.log(` ${p.id.padEnd(16)} ${p.name}`);
365
- console.log(` ${"".padEnd(16)} ${p.description}`);
366
- for (const r of p.roles) {
367
- console.log(` ${"".padEnd(18)} \u2022 ${r.name} (${r.id})`);
368
- }
369
- console.log();
370
- }
371
- return;
372
- }
373
- let roles;
374
- let presetUsed;
375
- if (options.rolesFile) {
376
- if (!existsSync(options.rolesFile)) {
377
- console.error(` \u2717 Roles file not found: ${options.rolesFile}`);
378
- process.exit(1);
379
- }
380
- try {
381
- const raw = readFileSync(options.rolesFile, "utf-8");
382
- const parsed = JSON.parse(raw);
383
- const rawRoles = Array.isArray(parsed) ? parsed : parsed.roles;
384
- if (!Array.isArray(rawRoles) || rawRoles.length === 0) {
385
- throw new Error("roles must be a non-empty array");
386
- }
387
- roles = rawRoles.map((r) => ({
388
- id: r.id ?? r.name?.toLowerCase().replace(/\s+/g, "-") ?? "agent",
389
- name: r.name ?? r.id ?? "Agent",
390
- persona: r.persona ?? r.description ?? `You are ${r.name ?? "an AI assistant"}.`,
391
- ...r.provider && { provider: r.provider },
392
- ...r.model && { model: r.model },
393
- ...r.color && { color: r.color }
394
- }));
395
- } catch (err) {
396
- console.error(` \u2717 Invalid roles file: ${err.message}`);
397
- process.exit(1);
398
- }
399
- } else {
400
- const presetId = options.preset ?? "tech-review";
401
- presetUsed = getPreset(presetId);
402
- if (!presetUsed) {
403
- console.error(` \u2717 Preset "${presetId}" not found. Use --presets to list available presets.`);
404
- process.exit(1);
405
- }
406
- roles = presetUsed.roles;
407
- }
408
- const defaultProvider = options.provider ?? configManager.get("defaultProvider");
409
- const allDefaultModels = configManager.get("defaultModels");
410
- let defaultModel = options.model ?? allDefaultModels[defaultProvider] ?? "";
411
- if (!defaultModel) {
412
- try {
413
- const p = providers.get(defaultProvider);
414
- defaultModel = p.info.defaultModel;
415
- } catch {
416
- console.error(` \u2717 Provider "${defaultProvider}" not configured. Run \`aicli config\` first.`);
417
- process.exit(1);
418
- }
419
- }
420
- if (!providers.has(defaultProvider)) {
421
- console.error(` \u2717 Provider "${defaultProvider}" not available. Check API key configuration.`);
422
- process.exit(1);
423
- }
424
- if (!options.topic?.trim()) {
425
- console.error(" \u2717 Please provide a discussion topic.");
426
- console.error(' Usage: aicli hub "your topic here"');
427
- console.error(' aicli hub --preset brainstorm "your topic here"');
428
- process.exit(1);
429
- }
430
- let context;
431
- const contextFileNames = [];
432
- if (options.contextFiles && options.contextFiles.length > 0) {
433
- const parts = [];
434
- for (const filePath of options.contextFiles) {
435
- if (!existsSync(filePath)) {
436
- console.error(` \u2717 Context file not found: ${filePath}`);
437
- process.exit(1);
438
- }
439
- const content = readFileSync(filePath, "utf-8");
440
- const fileName = filePath.replace(/\\/g, "/").split("/").pop() ?? filePath;
441
- contextFileNames.push(fileName);
442
- parts.push(`### ${fileName}
443
-
444
- ${content}`);
445
- }
446
- context = parts.join("\n\n---\n\n");
447
- }
448
- {
449
- const resolution = resolveRoleProviders(
450
- roles,
451
- {
452
- has: (id) => providers.has(id),
453
- defaultModelFor: (id) => providers.has(id) ? providers.get(id).info.defaultModel : void 0
454
- },
455
- defaultProvider,
456
- defaultModel,
457
- providers.listAvailable().map((p) => p.info.id),
458
- options.mix
459
- );
460
- roles = resolution.roles;
461
- for (const w of resolution.warnings) console.error(chalk.yellow(` \u26A0 ${w}`));
462
- }
463
- const mode = options.mode ?? "discuss";
464
- const config = {
465
- mode,
466
- roles,
467
- defaultProvider,
468
- defaultModel,
469
- maxRounds: options.maxRounds ?? (mode === "task" ? 15 : 10),
470
- enableTools: mode === "task",
471
- maxToolRoundsPerTurn: mode === "task" ? options.taskRounds ?? 30 : void 0,
472
- context,
473
- contextFiles: contextFileNames.length > 0 ? contextFileNames : void 0,
474
- humanSteer: options.steer === true,
475
- voteConverge: options.vote === true
476
- };
477
- if (mode === "discuss") {
478
- if (options.distributed) {
479
- await runDistributedDiscussion(config, providers, options.topic, options.port ?? 9527);
480
- } else {
481
- await runDiscussion(config, providers, options.topic);
482
- }
483
- } else if (mode === "task") {
484
- await runTaskMode(config, providers, configManager, options.topic);
485
- }
486
- }
487
- async function runTaskMode(config, providers, configManager, topic) {
488
- const { TaskOrchestrator } = await import("./task-orchestrator-RLAZK5EB.js");
489
- const orchestrator = new TaskOrchestrator(config, providers, configManager);
490
- let interrupted = false;
491
- const onSigint = () => {
492
- if (interrupted) {
493
- console.log("\n Force exit.");
494
- process.exit(0);
495
- }
496
- interrupted = true;
497
- orchestrator.abort();
498
- };
499
- process.on("SIGINT", onSigint);
500
- console.log();
501
- console.log(chalk.bold.white(" \u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557"));
502
- console.log(chalk.bold.white(" \u2551") + chalk.bold.yellow(" \u{1F680} AI-CLI Multi-Agent Hub \u2014 Task Mode ") + chalk.bold.white("\u2551"));
503
- console.log(chalk.bold.white(" \u2560\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2563"));
504
- console.log(chalk.bold.white(" \u2551") + chalk.dim(` Goal: ${topic.slice(0, 52)}`.padEnd(62)) + chalk.bold.white("\u2551"));
505
- console.log(chalk.bold.white(" \u2551") + chalk.dim(` Team: ${config.roles.map((r) => r.name).join(", ")}`.slice(0, 62).padEnd(62)) + chalk.bold.white("\u2551"));
506
- if (config.contextFiles && config.contextFiles.length > 0) {
507
- console.log(chalk.bold.white(" \u2551") + chalk.dim(` Context: ${config.contextFiles.join(", ")}`.padEnd(62)) + chalk.bold.white("\u2551"));
508
- }
509
- console.log(chalk.bold.white(" \u2551") + chalk.dim(` Rounds/task: up to ${config.maxToolRoundsPerTurn ?? 15}`.padEnd(62)) + chalk.bold.white("\u2551"));
510
- console.log(chalk.bold.white(" \u2560\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2563"));
511
- console.log(chalk.bold.white(" \u2551") + chalk.dim(" Plan \u2192 Approve \u2192 Execute \u2192 Review".padEnd(62)) + chalk.bold.white("\u2551"));
512
- console.log(chalk.bold.white(" \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D"));
513
- console.log();
514
- try {
515
- await orchestrator.run(topic);
516
- } catch (err) {
517
- console.error(chalk.red(`
518
- \u2717 Task mode error: ${err.message}`));
519
- } finally {
520
- process.removeListener("SIGINT", onSigint);
521
- }
522
- }
523
- async function runDistributedDiscussion(config, providers, topic, port) {
524
- const { HubServer } = await import("./hub-server-OH7AYQIW.js");
525
- const hub = new HubServer(config, providers, port);
526
- let interrupted = false;
527
- const onSigint = () => {
528
- if (interrupted) {
529
- console.log("\n Force exit.");
530
- hub.shutdown();
531
- process.exit(0);
532
- }
533
- interrupted = true;
534
- hub.abort();
535
- };
536
- process.on("SIGINT", onSigint);
537
- try {
538
- await hub.start(topic);
539
- } catch (err) {
540
- console.error(`
541
- \u2717 Hub error: ${err.message}`);
542
- } finally {
543
- process.removeListener("SIGINT", onSigint);
544
- }
545
- }
546
- async function runDiscussion(config, providers, topic) {
547
- assignRoleColors(config.roles);
548
- const orchestrator = new DiscussionOrchestrator(config, providers);
549
- orchestrator.onEvent = renderHubEvent;
550
- if (config.humanSteer) {
551
- const { createInterface } = await import("readline");
552
- orchestrator.onRoundReview = async ({ round, maxRounds }) => {
553
- const rl = createInterface({ input: process.stdin, output: process.stdout });
554
- const answer = await new Promise((resolve) => {
555
- rl.question(
556
- chalk.cyan(`
557
- \u{1F9ED} Round ${round}/${maxRounds} done. Steer next round? `) + chalk.dim("[Enter=continue \xB7 type guidance \xB7 /stop=end] "),
558
- resolve
559
- );
560
- });
561
- rl.close();
562
- const t = answer.trim();
563
- if (t === "/stop" || t.toLowerCase() === "stop") return { action: "stop" };
564
- return { action: "continue", message: t || void 0 };
565
- };
566
- }
567
- let interrupted = false;
568
- const onSigint = () => {
569
- if (interrupted) {
570
- console.log("\n Force exit.");
571
- process.exit(0);
572
- }
573
- interrupted = true;
574
- orchestrator.abort();
575
- };
576
- process.on("SIGINT", onSigint);
577
- renderHubBanner(topic, config.roles, config.maxRounds ?? 10, config.contextFiles);
578
- try {
579
- await orchestrator.run(topic);
580
- } catch (err) {
581
- console.error(`
582
- \u2717 Hub error: ${err.message}`);
583
- } finally {
584
- process.removeListener("SIGINT", onSigint);
585
- }
586
- }
587
357
  export {
588
- startHub
358
+ DiscussionOrchestrator,
359
+ getPreset,
360
+ listPresets,
361
+ resolveRoleProviders
589
362
  };
@@ -0,0 +1,51 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ SessionManager
4
+ } from "./chunk-TOTEUETI.js";
5
+
6
+ // src/hub/persist.ts
7
+ import { join } from "path";
8
+ function discussionToMessages(state) {
9
+ const out = [];
10
+ const t0 = state.messages[0]?.timestamp ?? /* @__PURE__ */ new Date();
11
+ out.push({ role: "user", content: `\u{1F3DB} Topic: ${state.topic}`, timestamp: t0 });
12
+ for (const m of state.messages) {
13
+ if (m.speaker === "system") {
14
+ out.push({ role: "system", content: m.content, timestamp: m.timestamp });
15
+ continue;
16
+ }
17
+ if (m.speaker === "human") {
18
+ out.push({ role: "user", content: `\u{1F9ED} ${m.speakerName}: ${m.content}`, timestamp: m.timestamp });
19
+ continue;
20
+ }
21
+ if (m.passed || !m.content.trim()) continue;
22
+ const tag = m.converged ? " \u2713converged" : "";
23
+ out.push({
24
+ role: "assistant",
25
+ content: `**${m.speakerName}** (${m.speaker})${tag}
26
+
27
+ ${m.content}`,
28
+ timestamp: m.timestamp
29
+ });
30
+ }
31
+ if (state.summary && state.summary.trim()) {
32
+ out.push({ role: "assistant", content: `\u{1F4CB} **Summary**
33
+
34
+ ${state.summary}`, timestamp: /* @__PURE__ */ new Date() });
35
+ }
36
+ return out;
37
+ }
38
+ async function persistDiscussion(state, config, defaultProvider, defaultModel) {
39
+ const sm = new SessionManager(config);
40
+ const session = sm.createSession(defaultProvider, defaultModel);
41
+ session.messages = discussionToMessages(state);
42
+ session.title = `[Hub] ${state.topic.slice(0, 48)}`.replace(/\n/g, " ");
43
+ session.titleAiGenerated = true;
44
+ await sm.save();
45
+ return { id: session.id, path: join(config.getHistoryDir(), `${session.id}.json`) };
46
+ }
47
+
48
+ export {
49
+ discussionToMessages,
50
+ persistDiscussion
51
+ };
@@ -0,0 +1,120 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/security/redactor.ts
4
+ var DEFAULT_PATTERNS = [
5
+ // password: xxx / password = xxx / password="xxx"
6
+ // Covers YAML / JSON / shell-ish / env-file forms.
7
+ { kind: "password", regex: /\b(password|passwd|pwd)\s*[:=]\s*["']?([^\s"',;{}]{4,200})["']?/gi },
8
+ // PGPASSWORD=xxx (explicit bash env-var form, separate rule because no quotes usually)
9
+ { kind: "pgpassword-env", regex: /\b(PGPASSWORD)=([^\s"']{4,200})/g },
10
+ // JDBC/PG/MySQL/Mongo connection strings with inline credentials
11
+ // postgresql://user:pass@host/db → redact pass
12
+ { kind: "db-uri-password", regex: /(\b(?:postgres(?:ql)?|mysql|mongodb(?:\+srv)?|redis|amqp|mssql):\/\/[^:\s]+:)([^@\s]+)(@)/gi },
13
+ // Anthropic API keys
14
+ { kind: "anthropic-key", regex: /(sk-ant-[a-zA-Z0-9_-]{90,})/g },
15
+ // L6 (v0.4.108): Zhipu / GLM API keys — `<24+ hex/base64-ish>.<32+>`
16
+ // Two segments separated by a dot, each safely identifiable by length
17
+ // and char class. Conservative on the lower bound so we don't eat
18
+ // version strings like `1.0.0` or filenames.
19
+ { kind: "zhipu-key", regex: /\b([a-zA-Z0-9]{24,}\.[a-zA-Z0-9]{32,})\b/g },
20
+ // OpenAI / generic sk- keys — requires length ≥32 to avoid eating short identifiers
21
+ { kind: "openai-key", regex: /(sk-(?:proj-)?[a-zA-Z0-9_-]{32,})/g },
22
+ // GitHub personal access tokens
23
+ { kind: "github-pat", regex: /\b(ghp_[a-zA-Z0-9]{36})\b/g },
24
+ { kind: "github-oauth", regex: /\b(gho_[a-zA-Z0-9]{36})\b/g },
25
+ { kind: "github-install", regex: /\b(ghs_[a-zA-Z0-9]{36})\b/g },
26
+ // Slack tokens
27
+ { kind: "slack-bot", regex: /\b(xoxb-\d+-\d+-[a-zA-Z0-9]+)\b/g },
28
+ { kind: "slack-user", regex: /\b(xoxp-\d+-\d+-\d+-[a-zA-Z0-9]+)\b/g },
29
+ // AWS access key IDs (AKIA...) and secret access keys are context-dependent;
30
+ // we only catch the ID because secret key alone is indistinguishable from random base64.
31
+ { kind: "aws-access-key-id", regex: /\b(AKIA[0-9A-Z]{16})\b/g },
32
+ // Google API keys
33
+ { kind: "google-api-key", regex: /\b(AIza[0-9A-Za-z_-]{35})\b/g },
34
+ // Generic "api_key": "..." / "apiKey": "..." / api-key=xxx
35
+ { kind: "api-key", regex: /\b(api[_-]?key)\s*[:=]\s*["']?([a-zA-Z0-9_\-.]{16,200})["']?/gi },
36
+ // Generic token: xxx (only when value looks token-shaped; avoids eating human prose)
37
+ { kind: "token", regex: /\b(token|access[_-]?token|bearer[_-]?token)\s*[:=]\s*["']?([a-zA-Z0-9_\-.]{20,300})["']?/gi },
38
+ // Bearer <token> in Authorization headers
39
+ { kind: "bearer", regex: /\b(Authorization:\s*Bearer\s+)([a-zA-Z0-9_\-.=]{20,500})/g },
40
+ // Private key PEM blocks — catch the header+footer together
41
+ { kind: "private-key", regex: /-----BEGIN [A-Z ]*PRIVATE KEY-----[\s\S]*?-----END [A-Z ]*PRIVATE KEY-----/g }
42
+ ];
43
+ var MAX_CUSTOM = 32;
44
+ var MAX_PATTERN_LEN = 500;
45
+ var SUSPICIOUS_REDOS = /\([^)]*[+*][^)]*\)\s*[+*{]/;
46
+ function render(placeholder, kind) {
47
+ return placeholder.replace("{kind}", kind);
48
+ }
49
+ function redactString(input, options) {
50
+ if (!options.enabled || !input) return { redacted: input, hits: [] };
51
+ const placeholder = options.placeholder ?? "[REDACTED:{kind}]";
52
+ const customSrcs = (options.customRegexes ?? []).slice(0, MAX_CUSTOM);
53
+ const patterns = [
54
+ ...options.patterns ?? DEFAULT_PATTERNS,
55
+ ...customSrcs.flatMap((src, i) => {
56
+ if (typeof src !== "string" || src.length === 0 || src.length > MAX_PATTERN_LEN) return [];
57
+ try {
58
+ const flags = src.match(/^\/.*\/([gimsuy]*)$/)?.[1] ?? "";
59
+ const body = src.replace(/^\/(.*)\/[gimsuy]*$/, "$1");
60
+ if (SUSPICIOUS_REDOS.test(body)) return [];
61
+ const regex = new RegExp(body, flags.includes("g") ? flags : flags + "g");
62
+ return [{ kind: `custom-${i}`, regex }];
63
+ } catch {
64
+ return [];
65
+ }
66
+ })
67
+ ];
68
+ let redacted = input;
69
+ const hits = [];
70
+ for (const { kind, regex } of patterns) {
71
+ const rx = new RegExp(regex.source, regex.flags);
72
+ const captureCount = new RegExp(rx.source + "|").exec("").length - 1;
73
+ redacted = redacted.replace(rx, (...args) => {
74
+ const match = args[0];
75
+ const g1 = captureCount >= 1 ? args[1] : void 0;
76
+ const g2 = captureCount >= 2 ? args[2] : void 0;
77
+ const offset = args[1 + captureCount];
78
+ if (captureCount >= 2 && typeof g2 === "string") {
79
+ hits.push({ kind, start: offset + (g1?.length ?? 0), length: g2.length, secret: g2 });
80
+ return `${g1}${render(placeholder, kind)}`;
81
+ }
82
+ hits.push({ kind, start: offset, length: match.length, secret: g1 ?? match });
83
+ return render(placeholder, kind);
84
+ });
85
+ }
86
+ return { redacted, hits };
87
+ }
88
+ function redactJson(value, options) {
89
+ if (!options.enabled) return { value, hits: [] };
90
+ const allHits = [];
91
+ function walk(v) {
92
+ if (typeof v === "string") {
93
+ const r = redactString(v, options);
94
+ allHits.push(...r.hits);
95
+ return r.redacted;
96
+ }
97
+ if (Array.isArray(v)) return v.map(walk);
98
+ if (v && typeof v === "object") {
99
+ const out = {};
100
+ for (const [k, vv] of Object.entries(v)) {
101
+ out[k] = walk(vv);
102
+ }
103
+ return out;
104
+ }
105
+ return v;
106
+ }
107
+ const redacted = walk(value);
108
+ return { value: redacted, hits: allHits };
109
+ }
110
+ function scanString(input, options) {
111
+ const { hits } = redactString(input, { ...options, enabled: true });
112
+ return hits;
113
+ }
114
+
115
+ export {
116
+ DEFAULT_PATTERNS,
117
+ redactString,
118
+ redactJson,
119
+ scanString
120
+ };