evolclaw 3.1.4 → 3.1.6

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 (99) hide show
  1. package/CHANGELOG.md +60 -0
  2. package/dist/agents/claude-runner.js +398 -161
  3. package/dist/agents/kit-renderer.js +191 -25
  4. package/dist/aun/aid/agentmd.js +75 -103
  5. package/dist/aun/aid/client.js +1 -29
  6. package/dist/aun/aid/identity.js +105 -64
  7. package/dist/aun/aid/index.js +2 -1
  8. package/dist/aun/aid/store.js +74 -0
  9. package/dist/aun/msg/group.js +2 -2
  10. package/dist/aun/msg/p2p.js +26 -2
  11. package/dist/aun/rpc/connection.js +23 -30
  12. package/dist/channels/aun.js +174 -99
  13. package/dist/channels/dingtalk.js +2 -1
  14. package/dist/channels/feishu.js +301 -199
  15. package/dist/channels/qqbot.js +2 -1
  16. package/dist/channels/wechat.js +2 -1
  17. package/dist/channels/wecom.js +2 -1
  18. package/dist/cli/agent.js +21 -16
  19. package/dist/cli/bench.js +41 -28
  20. package/dist/cli/help.js +8 -0
  21. package/dist/cli/index.js +176 -87
  22. package/dist/cli/init-channel.js +5 -1
  23. package/dist/cli/init.js +37 -21
  24. package/dist/cli/link-rules.js +1 -7
  25. package/dist/cli/model.js +549 -0
  26. package/dist/cli/net-check.js +133 -50
  27. package/dist/cli/watch-msg.js +7 -7
  28. package/dist/cli/watch-web/debug-log.js +18 -0
  29. package/dist/cli/watch-web/server.js +306 -0
  30. package/dist/cli/watch-web/sources/aid.js +63 -0
  31. package/dist/cli/watch-web/sources/msg.js +70 -0
  32. package/dist/cli/watch-web/sources/session.js +638 -0
  33. package/dist/cli/watch-web/sources/types.js +10 -0
  34. package/dist/cli/watch-web/static/app.js +546 -0
  35. package/dist/cli/watch-web/static/index.html +54 -0
  36. package/dist/cli/watch-web/static/style.css +247 -0
  37. package/dist/config-store.js +1 -22
  38. package/dist/core/channel-loader.js +7 -4
  39. package/dist/core/command-handler.js +261 -133
  40. package/dist/core/evolagent-registry.js +1 -1
  41. package/dist/core/evolagent.js +4 -22
  42. package/dist/core/interaction-router.js +59 -0
  43. package/dist/core/message/im-renderer.js +9 -20
  44. package/dist/core/message/message-bridge.js +13 -9
  45. package/dist/core/message/message-log.js +2 -2
  46. package/dist/core/message/message-processor.js +211 -123
  47. package/dist/core/message/stream-idle-monitor.js +21 -0
  48. package/dist/core/model/model-catalog.js +215 -0
  49. package/dist/core/model/model-scope.js +250 -0
  50. package/dist/core/relation/peer-identity.js +58 -55
  51. package/dist/core/relation/peer-key.js +16 -0
  52. package/dist/core/session/session-fs-store.js +34 -55
  53. package/dist/core/session/session-key.js +24 -0
  54. package/dist/core/session/session-manager.js +308 -251
  55. package/dist/core/session/session-mapper.js +9 -4
  56. package/dist/core/trigger/manager.js +3 -3
  57. package/dist/core/trigger/parser.js +4 -4
  58. package/dist/core/trigger/scheduler.js +22 -7
  59. package/dist/index.js +61 -7
  60. package/dist/ipc.js +23 -1
  61. package/dist/utils/error-utils.js +6 -0
  62. package/dist/utils/process-introspect.js +7 -5
  63. package/kits/docs/GUIDE.md +2 -2
  64. package/kits/docs/INDEX.md +8 -8
  65. package/kits/docs/channels/aun.md +56 -17
  66. package/kits/docs/channels/feishu.md +41 -12
  67. package/kits/docs/context-assembly.md +182 -0
  68. package/kits/docs/evolclaw/INDEX.md +43 -0
  69. package/kits/docs/evolclaw/agent.md +49 -0
  70. package/kits/docs/evolclaw/aid.md +49 -0
  71. package/kits/docs/evolclaw/ctl.md +46 -0
  72. package/kits/docs/evolclaw/group.md +89 -0
  73. package/kits/docs/evolclaw/model.md +51 -0
  74. package/kits/docs/evolclaw/msg.md +91 -0
  75. package/kits/docs/evolclaw/rpc.md +35 -0
  76. package/kits/docs/evolclaw/storage.md +49 -0
  77. package/kits/docs/venues/aun-group.md +10 -0
  78. package/kits/docs/venues/aun-private.md +10 -0
  79. package/kits/docs/venues/client-desktop.md +10 -0
  80. package/kits/docs/venues/client-mobile.md +10 -0
  81. package/kits/docs/venues/feishu-group.md +13 -0
  82. package/kits/docs/venues/feishu-private.md +9 -0
  83. package/kits/docs/venues/group.md +23 -0
  84. package/kits/docs/venues/private.md +10 -0
  85. package/kits/eck_manifest.json +81 -36
  86. package/kits/rules/01-overview.md +20 -10
  87. package/kits/rules/06-channel.md +34 -27
  88. package/kits/templates/system-fragments/baseagent.md +7 -1
  89. package/kits/templates/system-fragments/channel.md +7 -5
  90. package/kits/templates/system-fragments/commands.md +19 -0
  91. package/kits/templates/system-fragments/session.md +19 -3
  92. package/kits/templates/system-fragments/venue.md +24 -0
  93. package/package.json +10 -5
  94. package/dist/aun/aid/lifecycle-log.js +0 -33
  95. package/dist/utils/aid-lifecycle-log.js +0 -33
  96. package/kits/docs/evolclaw/AGENT_CMD.md +0 -31
  97. package/kits/docs/evolclaw/MSG_GROUP.md +0 -30
  98. package/kits/docs/evolclaw/MSG_PRIVATE.md +0 -72
  99. package/kits/docs/evolclaw/tools.md +0 -25
@@ -335,7 +335,7 @@ export class QQBotChannelPlugin {
335
335
  const adapter = {
336
336
  channelName: inst.name,
337
337
  channelKey: inst.name,
338
- capabilities: { file: true, image: true, interaction: false, markdown: true, thought: false, status: false },
338
+ capabilities: { file: true, image: true, interaction: false, markdown: true, thought: false, status: false, thread: false },
339
339
  send: async (envelope, payload) => {
340
340
  const ctx = envelope.replyContext;
341
341
  const channelId = envelope.channelId;
@@ -427,6 +427,7 @@ export class QQBotChannelPlugin {
427
427
  channel: adapter.channelName,
428
428
  channelType,
429
429
  channelId: event.channelId,
430
+ selfAID: inst.agentName,
430
431
  content: event.content,
431
432
  images: event.images,
432
433
  chatType: event.chatType || 'private',
@@ -731,7 +731,7 @@ export class WechatChannelPlugin {
731
731
  const adapter = {
732
732
  channelName: inst.name,
733
733
  channelKey: inst.name,
734
- capabilities: { file: false, image: false, interaction: false, markdown: false, thought: false, status: true },
734
+ capabilities: { file: false, image: false, interaction: false, markdown: false, thought: false, status: true, thread: false },
735
735
  send: async (envelope, payload) => {
736
736
  const channelId = envelope.channelId;
737
737
  switch (payload.kind) {
@@ -823,6 +823,7 @@ export class WechatChannelPlugin {
823
823
  channel: adapter.channelName,
824
824
  channelType,
825
825
  channelId,
826
+ selfAID: inst.agentName,
826
827
  content,
827
828
  images,
828
829
  chatType: chatType || 'private',
@@ -491,7 +491,7 @@ export class WecomChannelPlugin {
491
491
  const adapter = {
492
492
  channelName: inst.name,
493
493
  channelKey: inst.name,
494
- capabilities: { file: true, image: true, interaction: false, markdown: true, thought: false, status: false },
494
+ capabilities: { file: true, image: true, interaction: false, markdown: true, thought: false, status: false, thread: false },
495
495
  send: async (envelope, payload) => {
496
496
  const ctx = envelope.replyContext;
497
497
  const channelId = envelope.channelId;
@@ -583,6 +583,7 @@ export class WecomChannelPlugin {
583
583
  channel: adapter.channelName,
584
584
  channelType,
585
585
  channelId: event.channelId,
586
+ selfAID: inst.agentName,
586
587
  content: event.content,
587
588
  images: event.images,
588
589
  chatType: event.chatType || 'private',
package/dist/cli/agent.js CHANGED
@@ -1,6 +1,5 @@
1
1
  import fs from 'fs';
2
2
  import path from 'path';
3
- import os from 'os';
4
3
  import readline from 'readline';
5
4
  import { resolvePaths, agentMdPath as getAgentMdPathFromPaths, aunPath as defaultAunPath } from '../paths.js';
6
5
  import { loadDefaults, loadAllAgents, loadAgent, saveAgent, ensureAgentDirSkeleton } from '../config-store.js';
@@ -11,11 +10,6 @@ import { commandExists } from '../utils/cross-platform.js';
11
10
  import { isCodexSdkAvailable } from '../agents/codex-runner.js';
12
11
  // ==================== Helpers ====================
13
12
  const BASEAGENT_CANDIDATES = ['claude', 'codex', 'gemini'];
14
- const BASEAGENT_ENV_KEY = {
15
- claude: 'ANTHROPIC_API_KEY',
16
- codex: 'OPENAI_API_KEY',
17
- gemini: 'GEMINI_API_KEY',
18
- };
19
13
  function isBaseagentAvailable(baseagent) {
20
14
  if (baseagent === 'codex')
21
15
  return isCodexSdkAvailable();
@@ -30,8 +24,7 @@ function pickDefaultBaseagent(available) {
30
24
  return available.includes('claude') ? 'claude' : available[0];
31
25
  }
32
26
  function buildBaseagentsBlock(chosen) {
33
- const env = BASEAGENT_ENV_KEY[chosen];
34
- return { [chosen]: env ? { apiKey: `$ENV:${env}` } : {} };
27
+ return { [chosen]: {} };
35
28
  }
36
29
  const DEFAULT_CHATMODE = { private: 'interactive', group: 'proactive', nothuman: 'proactive' };
37
30
  const DEFAULT_DISPATCH = 'mention';
@@ -296,11 +289,11 @@ export async function agentCreateInteractive(opts = {}) {
296
289
  const defaults = loadDefaults();
297
290
  const rootPath = defaults?.projects?.rootPath
298
291
  || (defaults?.projects?.defaultPath && path.dirname(defaults.projects.defaultPath))
299
- || path.join(os.homedir(), 'evolclaw-projects');
292
+ || resolvePaths().root + '/projects';
300
293
  suggestedProjectPath = deriveAgentProjectPath(rootPath, aid);
301
294
  }
302
295
  catch {
303
- suggestedProjectPath = deriveAgentProjectPath(path.join(os.homedir(), 'evolclaw-projects'), aid);
296
+ suggestedProjectPath = deriveAgentProjectPath(resolvePaths().root + '/projects', aid);
304
297
  }
305
298
  const projectInput = (await ask(`Project path [${suggestedProjectPath}]: `)).trim();
306
299
  const projectPath = projectInput || suggestedProjectPath;
@@ -419,11 +412,14 @@ export async function agentCreateInteractive(opts = {}) {
419
412
  catch (e) {
420
413
  console.warn(` ⚠ agent.md generation failed: ${e?.message || e}`);
421
414
  }
422
- // Attempt hot-load via IPC (if daemon is running)
415
+ // Attempt hot-load via IPC (if daemon is running).
416
+ // Cold-starting a new agent (connecting AUN WebSocket) routinely takes
417
+ // >3s, so use a generous timeout to avoid a false "service not running"
418
+ // report while the daemon actually finishes bringing the agent online.
423
419
  let hotLoaded = false;
424
420
  let hotLoadError;
425
421
  try {
426
- const ipcResult = await ipcQuery(p.socket, { type: 'evolagent.load', aid });
422
+ const ipcResult = await ipcQuery(p.socket, { type: 'evolagent.load', aid }, 30_000);
427
423
  if (ipcResult?.ok) {
428
424
  hotLoaded = true;
429
425
  }
@@ -569,11 +565,14 @@ export async function agentCreateNonInteractive(opts) {
569
565
  catch (e) {
570
566
  console.warn(`⚠ agent.md generation failed: ${e?.message || e}`);
571
567
  }
572
- // Attempt hot-load via IPC (if daemon is running)
568
+ // Attempt hot-load via IPC (if daemon is running).
569
+ // Cold-starting a new agent (connecting AUN WebSocket) routinely takes
570
+ // >3s, so use a generous timeout to avoid a false "service not running"
571
+ // report while the daemon actually finishes bringing the agent online.
573
572
  let hotLoaded = false;
574
573
  let hotLoadError;
575
574
  try {
576
- const ipcResult = await ipcQuery(p.socket, { type: 'evolagent.load', aid: opts.aid });
575
+ const ipcResult = await ipcQuery(p.socket, { type: 'evolagent.load', aid: opts.aid }, 30_000);
577
576
  if (ipcResult?.ok) {
578
577
  hotLoaded = true;
579
578
  }
@@ -627,7 +626,7 @@ export async function agentSyncAids() {
627
626
  const defaults = loadDefaults();
628
627
  const rootPath = defaults?.projects?.rootPath
629
628
  || (defaults?.projects?.defaultPath && path.dirname(defaults.projects.defaultPath))
630
- || path.join(os.homedir(), 'evolclaw-projects');
629
+ || resolvePaths().root + '/projects';
631
630
  const created = [];
632
631
  for (const aid of localAids) {
633
632
  if (existingAids.has(aid))
@@ -755,6 +754,12 @@ export async function agentSet(aid, key, rawValue) {
755
754
  return { ok: false, error: `Failed to read config: ${e?.message || e}` };
756
755
  }
757
756
  const value = parseJsonValue(rawValue);
757
+ // active_baseagent 白名单校验:只允许已知 baseagent,挡住把模型名(如 deepseek)误设为后端
758
+ if (key === 'active_baseagent') {
759
+ if (typeof value !== 'string' || !BASEAGENT_CANDIDATES.includes(value)) {
760
+ return { ok: false, error: `无效 active_baseagent: ${JSON.stringify(value)}(可选: ${BASEAGENT_CANDIDATES.join(' / ')})` };
761
+ }
762
+ }
758
763
  setNestedValue(config, key, value);
759
764
  try {
760
765
  saveAgent(config);
@@ -829,7 +834,7 @@ export async function agentChannelUpsert(opts) {
829
834
  return {
830
835
  ok: true,
831
836
  aid: opts.aid,
832
- channelKey: `${opts.channel.type}#${encodeURIComponent(opts.aid)}#${opts.channel.name}`,
837
+ channelKey: `${opts.channel.type}#${opts.aid}#${opts.channel.name}`,
833
838
  reloaded,
834
839
  };
835
840
  }
package/dist/cli/bench.js CHANGED
@@ -7,8 +7,8 @@ import { promisify } from 'util';
7
7
  import { aidList, aidCreate } from '../aun/aid/identity.js';
8
8
  import { msgSend, msgPull } from '../aun/msg/index.js';
9
9
  import { getPackageRoot, aunPath as defaultAunPath } from '../paths.js';
10
- import { createAunClient } from '../aun/aid/client.js';
11
- import { isHelpFlag } from './help.js';
10
+ import { getAidStore, loadClient, SLOT } from '../aun/aid/store.js';
11
+ import { isHelpFlag, getArgValue } from './help.js';
12
12
  const execFileAsync = promisify(execFile);
13
13
  // ==================== ANSI ====================
14
14
  const GREEN = '\x1b[32m';
@@ -132,10 +132,6 @@ function percentile(sorted, p) {
132
132
  const idx = Math.ceil((p / 100) * sorted.length) - 1;
133
133
  return sorted[Math.max(0, idx)];
134
134
  }
135
- function getArgValue(args, flag) {
136
- const idx = args.indexOf(flag);
137
- return idx >= 0 && idx + 1 < args.length ? args[idx + 1] : undefined;
138
- }
139
135
  // ==================== Promise Pool ====================
140
136
  function withTimeout(promise, ms, label) {
141
137
  return new Promise((resolve, reject) => {
@@ -248,34 +244,48 @@ function computeMetrics(results, received, durationSec) {
248
244
  function filterBySize(results, received, cls, durationSec) {
249
245
  return computeMetrics(results.filter(r => r.sizeClass === cls), received.filter(r => r.sizeClass === cls), durationSec);
250
246
  }
251
- async function benchAuth(aids, concurrency, aunPath, slotId) {
252
- const resolvedAunPath = aunPath ?? defaultAunPath();
253
- const tasks = aids.map(aid => async () => {
247
+ async function benchAuth(aids, concurrency, aunPath) {
248
+ const tasks = aids.map((aid, index) => async () => {
254
249
  const start = Date.now();
250
+ const slotId = `${SLOT.bench}-${index}`;
251
+ let store = null;
252
+ let client = null;
255
253
  try {
256
- const client = await createAunClient({ aunPath: resolvedAunPath });
257
- await client.auth.createAid({ aid });
258
- const authResult = await client.auth.authenticate({ aid });
259
- const accessToken = authResult?.access_token ?? client._access_token;
260
- const gateway = client._gatewayUrl ?? authResult?.gateway ?? '';
261
- await client.connect({ access_token: accessToken, gateway, slot_id: slotId ?? '', connection_kind: 'short' }, { auto_reconnect: false });
254
+ store = await getAidStore({ slotId, aunPath });
255
+ client = await loadClient(store, aid);
256
+ await client.connect({ auto_reconnect: false });
257
+ const gateway = ''; // Gateway URL not exposed in 0.4.3
258
+ const authMs = Date.now() - start;
262
259
  try {
263
260
  await client.close();
264
261
  }
265
262
  catch { }
266
- return { aid, ok: true, authMs: Date.now() - start, gateway };
263
+ try {
264
+ store.close();
265
+ }
266
+ catch { }
267
+ return { aid, ok: true, authMs, gateway };
267
268
  }
268
269
  catch (e) {
270
+ if (client)
271
+ try {
272
+ await client.close();
273
+ }
274
+ catch { }
275
+ if (store)
276
+ try {
277
+ store.close();
278
+ }
279
+ catch { }
269
280
  return { aid, ok: false, authMs: Date.now() - start, error: e.message };
270
281
  }
271
282
  });
272
283
  return runPool(tasks, concurrency);
273
284
  }
274
- async function benchSwitch(aids, rounds, aunPath, useCli, slotId, encrypt) {
285
+ async function benchSwitch(aids, rounds, aunPath, useCli, encrypt) {
275
286
  const results = [];
276
287
  const start = Date.now();
277
288
  let seq = 0;
278
- const slot = slotId ?? 'bench';
279
289
  for (let round = 0; round < rounds; round++) {
280
290
  for (let i = 0; i < aids.length; i++) {
281
291
  const from = aids[i];
@@ -286,8 +296,9 @@ async function benchSwitch(aids, rounds, aunPath, useCli, slotId, encrypt) {
286
296
  let ok = false;
287
297
  let serverTimestamp;
288
298
  if (useCli) {
299
+ const slotId = `${SLOT.bench}-${i}`;
289
300
  try {
290
- const res = await withTimeout(cliSend(from, to, text, slot, encrypt), 10000, `${from.split('.')[0]}→${to.split('.')[0]}`);
301
+ const res = await withTimeout(cliSend(from, to, text, slotId, encrypt), 10000, `${from.split('.')[0]}→${to.split('.')[0]}`);
291
302
  ok = res.ok;
292
303
  serverTimestamp = res.timestamp;
293
304
  }
@@ -296,8 +307,9 @@ async function benchSwitch(aids, rounds, aunPath, useCli, slotId, encrypt) {
296
307
  }
297
308
  }
298
309
  else {
310
+ const slotId = `${SLOT.bench}-${i}`;
299
311
  try {
300
- const res = await withTimeout(msgSend({ from, to, body: { mode: 'text', text }, slotId: slot, aunPath, encrypt }), 10000, `${from.split('.')[0]}→${to.split('.')[0]}`);
312
+ const res = await withTimeout(msgSend({ from, to, body: { mode: 'text', text }, slotId, aunPath, encrypt }), 10000, `${from.split('.')[0]}→${to.split('.')[0]}`);
301
313
  ok = res.ok;
302
314
  serverTimestamp = res.ok ? res.timestamp : undefined;
303
315
  }
@@ -392,7 +404,6 @@ Options:
392
404
  const defaultWait = encrypt ? '10' : '3';
393
405
  const waitSec = Math.max(1, parseInt(getArgValue(args, '--wait') || defaultWait, 10));
394
406
  const sessionId = crypto.randomBytes(4).toString('hex');
395
- const benchSlot = `bench-${sessionId}`;
396
407
  if (!formatJson) {
397
408
  console.log(`\n${BOLD} evolclaw bench${RST} — AUN 消息性能基准测试`);
398
409
  console.log(` ${'━'.repeat(50)}`);
@@ -495,14 +506,15 @@ Options:
495
506
  authTasks.push(...aids);
496
507
  let authResults;
497
508
  if (cliMode) {
498
- const cliAuthTasks = authTasks.map(aid => async () => {
499
- const r = await cliAuth(aid, benchSlot);
509
+ const cliAuthTasks = authTasks.map((aid, index) => async () => {
510
+ const slotId = `${SLOT.bench}-${index}`;
511
+ const r = await cliAuth(aid, slotId);
500
512
  return { aid, ok: r.ok, authMs: r.authMs };
501
513
  });
502
514
  authResults = await runPool(cliAuthTasks, concurrency);
503
515
  }
504
516
  else {
505
- authResults = await benchAuth(authTasks, concurrency, aunPath, benchSlot);
517
+ authResults = await benchAuth(authTasks, concurrency, aunPath);
506
518
  }
507
519
  const authOk = authResults.filter(r => r.ok);
508
520
  const authFail = authResults.filter(r => !r.ok);
@@ -592,7 +604,7 @@ Options:
592
604
  // Suppress SDK error logs during send phase (we track errors ourselves)
593
605
  const origError2 = console.error;
594
606
  console.error = () => { };
595
- const sendFns = tasks.map(t => async () => {
607
+ const sendFns = tasks.map((t, taskIndex) => async () => {
596
608
  let lastError;
597
609
  let retries = 0;
598
610
  for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
@@ -605,10 +617,11 @@ Options:
605
617
  ? buildFileChunkText(t.seq, fileChunks.length, sendTs, sessionId, fileChunks[t.seq]?.toString('base64') ?? '')
606
618
  : buildMessageText(t.seq, t.sizeClass, sendTs, sessionId);
607
619
  const t0 = Date.now();
620
+ const slotId = `${SLOT.bench}-${taskIndex % concurrency}`;
608
621
  try {
609
622
  const sendPromise = cliMode
610
- ? cliSend(t.from, t.to, text, benchSlot, encrypt)
611
- : msgSend({ from: t.from, to: t.to, body: { mode: 'text', text }, slotId: benchSlot, aunPath, encrypt });
623
+ ? cliSend(t.from, t.to, text, slotId, encrypt)
624
+ : msgSend({ from: t.from, to: t.to, body: { mode: 'text', text }, slotId, aunPath, encrypt });
612
625
  const res = await withTimeout(sendPromise, SEND_TIMEOUT_MS, `${t.from.split('.')[0]}→${t.to.split('.')[0]}`);
613
626
  if (cliMode) {
614
627
  const cliRes = res;
@@ -922,7 +935,7 @@ Options:
922
935
  if (!formatJson)
923
936
  console.log(`${DIM} Phase 5: 频繁切换账号收发测试${RST}`);
924
937
  const switchRounds = Math.max(2, Math.min(rounds, 5));
925
- const switchOut = await benchSwitch(aids, switchRounds, aunPath, cliMode, benchSlot, encrypt);
938
+ const switchOut = await benchSwitch(aids, switchRounds, aunPath, cliMode, encrypt);
926
939
  const switchOkResults = switchOut.results.filter(r => r.ok);
927
940
  const switchLatencies = switchOkResults
928
941
  .filter(r => r.serverTimestamp !== undefined)
package/dist/cli/help.js CHANGED
@@ -21,3 +21,11 @@ export function wantsHelp(args) {
21
21
  return true;
22
22
  return false;
23
23
  }
24
+ /**
25
+ * 取出 `--flag <value>` 形式的参数值。
26
+ * flag 不存在或其后无值时返回 undefined。
27
+ */
28
+ export function getArgValue(args, flag) {
29
+ const idx = args.indexOf(flag);
30
+ return idx !== -1 && idx + 1 < args.length ? args[idx + 1] : undefined;
31
+ }