@claude-flow/cli 3.71.2 → 3.71.4

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 (44) hide show
  1. package/.claude/helpers/auto-memory-hook.mjs +224 -53
  2. package/.claude/helpers/helpers.manifest.json +4 -4
  3. package/.claude/helpers/intelligence.cjs +23 -1
  4. package/.claude/settings.json +2 -1
  5. package/README.md +2 -0
  6. package/catalog-manifest.json +2 -2
  7. package/dist/src/commands/daemon.js +20 -1
  8. package/dist/src/commands/hive-mind-worker.d.ts +10 -0
  9. package/dist/src/commands/hive-mind-worker.js +20 -0
  10. package/dist/src/commands/hive-mind.js +27 -4
  11. package/dist/src/commands/memory.js +4 -3
  12. package/dist/src/init/helpers-generator.js +16 -1
  13. package/dist/src/init/settings-generator.js +4 -0
  14. package/dist/src/mcp-tools/agent-tools.js +4 -4
  15. package/dist/src/mcp-tools/agentbbs-federation.d.ts +18 -1
  16. package/dist/src/mcp-tools/agentbbs-federation.js +23 -7
  17. package/dist/src/mcp-tools/agentdb-tools.js +3 -3
  18. package/dist/src/mcp-tools/browser-argv.d.ts +18 -0
  19. package/dist/src/mcp-tools/browser-argv.js +59 -0
  20. package/dist/src/mcp-tools/browser-tools.d.ts +1 -1
  21. package/dist/src/mcp-tools/browser-tools.js +13 -6
  22. package/dist/src/mcp-tools/claims-tools.js +16 -9
  23. package/dist/src/mcp-tools/hooks-tools.js +11 -1
  24. package/dist/src/mcp-tools/memory-tools.js +49 -32
  25. package/dist/src/mcp-tools/neural-tools.js +8 -2
  26. package/dist/src/mcp-tools/path-containment.d.ts +34 -0
  27. package/dist/src/mcp-tools/path-containment.js +133 -0
  28. package/dist/src/mcp-tools/record-store.d.ts +7 -0
  29. package/dist/src/mcp-tools/record-store.js +19 -2
  30. package/dist/src/mcp-tools/session-tools.js +4 -38
  31. package/dist/src/mcp-tools/swarm-tools.js +4 -0
  32. package/dist/src/mcp-tools/task-tools.js +11 -0
  33. package/dist/src/mcp-tools/terminal-tools.js +7 -2
  34. package/dist/src/mcp-tools/validate-input.d.ts +2 -0
  35. package/dist/src/mcp-tools/validate-input.js +14 -0
  36. package/dist/src/missions/schemas.d.ts +10 -10
  37. package/dist/src/mods/headless-plugins.d.ts +16 -0
  38. package/dist/src/mods/headless-plugins.js +30 -1
  39. package/dist/src/services/daemon-autostart.d.ts +24 -0
  40. package/dist/src/services/daemon-autostart.js +55 -1
  41. package/dist/src/services/headless-worker-executor.d.ts +15 -0
  42. package/dist/src/services/headless-worker-executor.js +28 -0
  43. package/package.json +1 -1
  44. package/plugins/ruflo-console/dist/tui.mjs +132 -132
@@ -14,10 +14,20 @@
14
14
  *
15
15
  * Profile override: `RUFLO_HEADLESS_PLUGINS` is a comma list of plugin names;
16
16
  * `none` (or an empty value) turns plugin loading off.
17
+ *
18
+ * Permission mode: Claude Code 2.1.302 starts a `claude -p` with no
19
+ * permission mode in `manual` mode on third-party providers, with telemetry
20
+ * off, or on a fresh install. A headless worker has nobody to answer a prompt,
21
+ * so a print-mode argv without its own `--permission-mode` or
22
+ * `--dangerously-skip-permissions` gets `--permission-mode auto` — the mode
23
+ * first-party installs already started in. `RUFLO_HEADLESS_PERMISSION_MODE`
24
+ * picks another mode; `none` (or an empty value) passes no flag.
17
25
  */
18
26
  import { type ResolveOptions, type PluginPaths } from './plugin-paths.js';
19
27
  export declare const DEFAULT_WORKER_PLUGINS: readonly string[];
20
28
  export declare const PROFILE_ENV = "RUFLO_HEADLESS_PLUGINS";
29
+ export declare const PERMISSION_MODE_ENV = "RUFLO_HEADLESS_PERMISSION_MODE";
30
+ export declare const DEFAULT_HEADLESS_PERMISSION_MODE = "auto";
21
31
  export interface HeadlessPluginOptions extends ResolveOptions {
22
32
  /** Plugin names to load; defaults to the env profile, else DEFAULT_WORKER_PLUGINS. */
23
33
  profile?: readonly string[];
@@ -40,5 +50,11 @@ export declare function resetHeadlessPluginWarnings(): void;
40
50
  export declare function workerProfile(env?: NodeJS.ProcessEnv, explicit?: readonly string[]): string[];
41
51
  /** Every `--plugin-dir` value already in argv (both `--plugin-dir x` and `--plugin-dir=x`). */
42
52
  export declare function existingPluginDirs(argv: readonly string[]): string[];
53
+ /**
54
+ * The `--permission-mode` args to append to a headless argv: none when the
55
+ * argv is not print mode or already sets a mode, else the env choice
56
+ * (default `auto`). An unknown mode is ignored with the default used instead.
57
+ */
58
+ export declare function headlessPermissionArgs(argv: readonly string[], env?: NodeJS.ProcessEnv): string[];
43
59
  export declare function withHeadlessPlugins<E extends Record<string, string | undefined>>(argv: readonly string[], env: E, opts?: HeadlessPluginOptions): HeadlessPluginResult<E>;
44
60
  //# sourceMappingURL=headless-plugins.d.ts.map
@@ -14,6 +14,14 @@
14
14
  *
15
15
  * Profile override: `RUFLO_HEADLESS_PLUGINS` is a comma list of plugin names;
16
16
  * `none` (or an empty value) turns plugin loading off.
17
+ *
18
+ * Permission mode: Claude Code 2.1.302 starts a `claude -p` with no
19
+ * permission mode in `manual` mode on third-party providers, with telemetry
20
+ * off, or on a fresh install. A headless worker has nobody to answer a prompt,
21
+ * so a print-mode argv without its own `--permission-mode` or
22
+ * `--dangerously-skip-permissions` gets `--permission-mode auto` — the mode
23
+ * first-party installs already started in. `RUFLO_HEADLESS_PERMISSION_MODE`
24
+ * picks another mode; `none` (or an empty value) passes no flag.
17
25
  */
18
26
  import { realpathSync } from 'node:fs';
19
27
  import { basename, resolve } from 'node:path';
@@ -21,6 +29,9 @@ import { ENABLE_ENV } from './install.js';
21
29
  import { resolvePluginPaths } from './plugin-paths.js';
22
30
  export const DEFAULT_WORKER_PLUGINS = ['ruflo-mods'];
23
31
  export const PROFILE_ENV = 'RUFLO_HEADLESS_PLUGINS';
32
+ export const PERMISSION_MODE_ENV = 'RUFLO_HEADLESS_PERMISSION_MODE';
33
+ export const DEFAULT_HEADLESS_PERMISSION_MODE = 'auto';
34
+ const PERMISSION_MODES = new Set(['acceptEdits', 'auto', 'bypassPermissions', 'manual', 'default', 'dontAsk', 'plan']);
24
35
  const warned = new Set();
25
36
  /** For tests: forget which missing plugins were already logged. */
26
37
  export function resetHeadlessPluginWarnings() {
@@ -56,9 +67,27 @@ export function existingPluginDirs(argv) {
56
67
  }
57
68
  return dirs;
58
69
  }
70
+ /**
71
+ * The `--permission-mode` args to append to a headless argv: none when the
72
+ * argv is not print mode or already sets a mode, else the env choice
73
+ * (default `auto`). An unknown mode is ignored with the default used instead.
74
+ */
75
+ export function headlessPermissionArgs(argv, env = process.env) {
76
+ const isPrint = argv.some((a) => a === '-p' || a === '--print');
77
+ if (!isPrint)
78
+ return [];
79
+ const hasMode = argv.some((a) => a === '--permission-mode' || a.startsWith('--permission-mode=') || a === '--dangerously-skip-permissions');
80
+ if (hasMode)
81
+ return [];
82
+ const raw = env[PERMISSION_MODE_ENV];
83
+ if (raw !== undefined && (raw.trim() === '' || raw.trim().toLowerCase() === 'none'))
84
+ return [];
85
+ const mode = raw !== undefined && PERMISSION_MODES.has(raw.trim()) ? raw.trim() : DEFAULT_HEADLESS_PERMISSION_MODE;
86
+ return ['--permission-mode', mode];
87
+ }
59
88
  export function withHeadlessPlugins(argv, env, opts = {}) {
60
89
  const profile = workerProfile(opts.env ?? env, opts.profile);
61
- const outArgv = [...argv];
90
+ const outArgv = [...argv, ...headlessPermissionArgs(argv, opts.env ?? env)];
62
91
  const outEnv = { ...env };
63
92
  if (profile.length === 0)
64
93
  return { argv: outArgv, env: outEnv, added: [], missing: [] };
@@ -1,5 +1,29 @@
1
1
  /** True if a live daemon already holds the project's pidfile. */
2
2
  export declare function isDaemonAlive(projectRoot: string): boolean;
3
+ /**
4
+ * #4654: an explicit `daemon stop` is an opt-out that must survive the next
5
+ * hook. Every ruflo-core plugin hook (PreToolUse/PostToolUse, PreCompact, Stop)
6
+ * runs the CLI, and the CLI auto-starts a daemon whenever none is alive, so a
7
+ * stopped daemon came back within minutes. `daemon stop` now writes this
8
+ * marker, auto-start honours it, and only an explicit `daemon start` clears it.
9
+ */
10
+ export declare const DAEMON_STOP_MARKER = "daemon.stopped";
11
+ /**
12
+ * Set on the daemon that auto-start spawns, so that spawned `daemon start` does
13
+ * not clear a stop marker written after auto-start checked it (a `daemon stop`
14
+ * racing a hook). Only a user-driven start clears the marker.
15
+ */
16
+ export declare const AUTOSTART_ENV = "RUFLO_DAEMON_AUTOSTARTED";
17
+ /** True if the user explicitly stopped the daemon for this project (#4654). */
18
+ export declare function isDaemonStopMarked(projectRoot: string): boolean;
19
+ /**
20
+ * Record an explicit `daemon stop` (#4654). Writes only into a directory that is
21
+ * already a Ruflo project or already has `.claude-flow/`, so `daemon stop` in an
22
+ * unrelated directory does not litter it. Best-effort; never throws.
23
+ */
24
+ export declare function writeDaemonStopMarker(projectRoot: string): boolean;
25
+ /** Clear the stop marker. Called by an explicit `daemon start` (#4654). Never throws. */
26
+ export declare function clearDaemonStopMarker(projectRoot: string): void;
3
27
  /**
4
28
  * Return true only when the directory contains a durable Ruflo project marker.
5
29
  *
@@ -81,6 +81,57 @@ function autostartDisabledByProjectConfig(projectRoot) {
81
81
  }
82
82
  return false;
83
83
  }
84
+ /**
85
+ * #4654: an explicit `daemon stop` is an opt-out that must survive the next
86
+ * hook. Every ruflo-core plugin hook (PreToolUse/PostToolUse, PreCompact, Stop)
87
+ * runs the CLI, and the CLI auto-starts a daemon whenever none is alive, so a
88
+ * stopped daemon came back within minutes. `daemon stop` now writes this
89
+ * marker, auto-start honours it, and only an explicit `daemon start` clears it.
90
+ */
91
+ export const DAEMON_STOP_MARKER = 'daemon.stopped';
92
+ /**
93
+ * Set on the daemon that auto-start spawns, so that spawned `daemon start` does
94
+ * not clear a stop marker written after auto-start checked it (a `daemon stop`
95
+ * racing a hook). Only a user-driven start clears the marker.
96
+ */
97
+ export const AUTOSTART_ENV = 'RUFLO_DAEMON_AUTOSTARTED';
98
+ function stopMarkerPath(projectRoot) {
99
+ return path.join(projectRoot, '.claude-flow', DAEMON_STOP_MARKER);
100
+ }
101
+ /** True if the user explicitly stopped the daemon for this project (#4654). */
102
+ export function isDaemonStopMarked(projectRoot) {
103
+ try {
104
+ return fs.existsSync(stopMarkerPath(projectRoot));
105
+ }
106
+ catch {
107
+ return false;
108
+ }
109
+ }
110
+ /**
111
+ * Record an explicit `daemon stop` (#4654). Writes only into a directory that is
112
+ * already a Ruflo project or already has `.claude-flow/`, so `daemon stop` in an
113
+ * unrelated directory does not litter it. Best-effort; never throws.
114
+ */
115
+ export function writeDaemonStopMarker(projectRoot) {
116
+ try {
117
+ const dir = path.join(projectRoot, '.claude-flow');
118
+ if (!fs.existsSync(dir) && !isRufloProject(projectRoot))
119
+ return false;
120
+ fs.mkdirSync(dir, { recursive: true });
121
+ fs.writeFileSync(stopMarkerPath(projectRoot), JSON.stringify({ stoppedAt: new Date().toISOString(), pid: process.pid }) + '\n');
122
+ return true;
123
+ }
124
+ catch {
125
+ return false;
126
+ }
127
+ }
128
+ /** Clear the stop marker. Called by an explicit `daemon start` (#4654). Never throws. */
129
+ export function clearDaemonStopMarker(projectRoot) {
130
+ try {
131
+ fs.unlinkSync(stopMarkerPath(projectRoot));
132
+ }
133
+ catch { /* absent */ }
134
+ }
84
135
  function autostartDisabled(projectRoot) {
85
136
  if (/^(0|false|no|off)$/i.test(process.env.RUFLO_DAEMON_AUTOSTART ?? ''))
86
137
  return true;
@@ -166,7 +217,7 @@ const defaultSpawn = (projectRoot) => {
166
217
  cwd: projectRoot,
167
218
  detached: true,
168
219
  stdio: 'ignore',
169
- env: { ...process.env },
220
+ env: { ...process.env, [AUTOSTART_ENV]: '1' },
170
221
  });
171
222
  child.unref();
172
223
  };
@@ -186,6 +237,9 @@ export function ensureDaemonRunning(startDir, opts = {}) {
186
237
  if (!isRufloProject(projectRoot)) {
187
238
  return { started: false, reason: 'not a ruflo project' };
188
239
  }
240
+ if (isDaemonStopMarked(projectRoot)) {
241
+ return { started: false, reason: 'stopped by user (daemon stop); run `daemon start` to re-enable' };
242
+ }
189
243
  const alive = (opts.isAlive ?? isDaemonAlive)(projectRoot);
190
244
  if (alive)
191
245
  return { started: false, reason: 'already running' };
@@ -123,6 +123,12 @@ export interface HeadlessExecutionResult {
123
123
  inputTokens?: number;
124
124
  outputTokens?: number;
125
125
  costUsd?: number;
126
+ /**
127
+ * When the stored claude.ai sign-in the worker ran under ends (ISO 8601),
128
+ * from `credential_expires_at` in the `claude -p` JSON result (Claude Code
129
+ * 2.1.318+). Absent for API-key auth and older CLIs.
130
+ */
131
+ credentialExpiresAt?: string;
126
132
  /** Model used for execution */
127
133
  model: string;
128
134
  /** Sandbox mode used */
@@ -202,7 +208,16 @@ export interface ClaudePrintEnvelope {
202
208
  outputTokens?: number;
203
209
  costUsd?: number;
204
210
  durationMs?: number;
211
+ /** ISO 8601 end of the stored claude.ai sign-in (Claude Code 2.1.318+). */
212
+ credentialExpiresAt?: string;
205
213
  }
214
+ /** Days before a sign-in ends at which the executor emits 'credential-expiring' (matches Claude Code's own stderr warning). */
215
+ export declare const CREDENTIAL_EXPIRY_WARN_DAYS = 10;
216
+ /**
217
+ * Normalise `credential_expires_at` to ISO 8601. Accepts an ISO string or an
218
+ * epoch number (seconds or milliseconds); anything else is undefined.
219
+ */
220
+ export declare function normalizeCredentialExpiry(v: unknown): string | undefined;
206
221
  /**
207
222
  * #2661 root-fix — best-effort parse of `claude --print --output-format
208
223
  * json`'s response envelope. Deliberately lenient: probes a couple of
@@ -337,6 +337,23 @@ export function isLocalWorker(type) {
337
337
  export function getModelId(model) {
338
338
  return MODEL_IDS[model];
339
339
  }
340
+ /** Days before a sign-in ends at which the executor emits 'credential-expiring' (matches Claude Code's own stderr warning). */
341
+ export const CREDENTIAL_EXPIRY_WARN_DAYS = 10;
342
+ /**
343
+ * Normalise `credential_expires_at` to ISO 8601. Accepts an ISO string or an
344
+ * epoch number (seconds or milliseconds); anything else is undefined.
345
+ */
346
+ export function normalizeCredentialExpiry(v) {
347
+ let ms;
348
+ if (typeof v === 'number' && Number.isFinite(v) && v > 0)
349
+ ms = v < 1e12 ? v * 1000 : v;
350
+ else if (typeof v === 'string' && v.trim()) {
351
+ const t = Date.parse(v);
352
+ if (Number.isFinite(t))
353
+ ms = t;
354
+ }
355
+ return ms === undefined ? undefined : new Date(ms).toISOString();
356
+ }
340
357
  /**
341
358
  * #2661 root-fix — best-effort parse of `claude --print --output-format
342
359
  * json`'s response envelope. Deliberately lenient: probes a couple of
@@ -371,6 +388,7 @@ export function parseClaudePrintJsonEnvelope(raw) {
371
388
  outputTokens: numOrUndef(usage?.output_tokens ?? usage?.outputTokens),
372
389
  costUsd: numOrUndef(obj.total_cost_usd ?? obj.cost_usd ?? obj.totalCostUsd),
373
390
  durationMs: numOrUndef(obj.duration_ms ?? obj.durationMs),
391
+ credentialExpiresAt: normalizeCredentialExpiry(obj.credential_expires_at ?? obj.credentialExpiresAt),
374
392
  };
375
393
  }
376
394
  /**
@@ -779,7 +797,16 @@ export class HeadlessWorkerExecutor extends EventEmitter {
779
797
  executionId,
780
798
  error: result.error,
781
799
  ...(result.missingPlugins ? { missingPlugins: result.missingPlugins } : {}),
800
+ ...(result.credentialExpiresAt ? { credentialExpiresAt: result.credentialExpiresAt } : {}),
782
801
  };
802
+ // Claude Code 2.1.318: surface a claude.ai sign-in that ends soon, so the
803
+ // daemon can say so before workers start failing on auth.
804
+ if (result.credentialExpiresAt) {
805
+ const daysLeft = (Date.parse(result.credentialExpiresAt) - Date.now()) / 86_400_000;
806
+ if (daysLeft <= CREDENTIAL_EXPIRY_WARN_DAYS) {
807
+ this.emit('credential-expiring', { expiresAt: result.credentialExpiresAt, daysLeft: Math.max(0, Math.floor(daysLeft)), workerType });
808
+ }
809
+ }
783
810
  // Log result
784
811
  this.logExecution(executionId, 'result', JSON.stringify(executionResult, null, 2));
785
812
  // #2661 root-fix — structured per-launch telemetry, receipted
@@ -1182,6 +1209,7 @@ Analyze the above codebase context and provide your response following the forma
1182
1209
  : undefined,
1183
1210
  costUsd: envelope?.costUsd,
1184
1211
  apiDurationMs: envelope?.durationMs,
1212
+ credentialExpiresAt: envelope?.credentialExpiresAt,
1185
1213
  error: code !== 0 ? stderr || `Process exited with code ${code}` : undefined,
1186
1214
  });
1187
1215
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@claude-flow/cli",
3
- "version": "3.71.2",
3
+ "version": "3.71.4",
4
4
  "type": "module",
5
5
  "description": "Ruflo CLI - Enterprise AI agent orchestration with 60+ specialized agents, swarm coordination, MCP server, self-learning hooks, and vector memory for Claude Code",
6
6
  "main": "dist/src/index.js",