network-ai 5.3.1 → 5.4.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.
@@ -305,6 +305,83 @@ Connect your AI model to `http://localhost:3001/sse` — it can now:
305
305
 
306
306
  ---
307
307
 
308
+ ### Phase 7 — Multi-Environment Promotion (v5.4.0+)
309
+
310
+ **Goal:** Promote validated config from dev → staging → production through gate-enforced checkpoints.
311
+
312
+ ```bash
313
+ # Initialise the full environment chain in one command
314
+ npx network-ai env init --all
315
+
316
+ # After validating in dev, promote config to st (auto-gate — no approval needed)
317
+ npx network-ai env promote --from dev --to st
318
+
319
+ # Review differences before promoting further
320
+ npx network-ai env diff --from st --to sit
321
+
322
+ # preprod requires a human confirmation
323
+ npx network-ai env promote --from sit --to qa
324
+ npx network-ai env promote --from qa --to preprod --confirmed-by "jane.doe"
325
+
326
+ # prod requires an approval token
327
+ npx network-ai env promote --from preprod --to prod --approved-by "security-board"
328
+ ```
329
+
330
+ **Backup and rollback:**
331
+
332
+ ```bash
333
+ # Create a named backup before a risky change
334
+ npx network-ai env backup create --env prod
335
+
336
+ # List available backups
337
+ npx network-ai env backup list --env prod
338
+
339
+ # Roll back to the latest backup
340
+ npx network-ai env backup restore --env prod --latest
341
+ ```
342
+
343
+ **From TypeScript:**
344
+
345
+ ```typescript
346
+ import { EnvironmentManager } from 'network-ai';
347
+
348
+ const mgr = new EnvironmentManager('.');
349
+ await mgr.initAll();
350
+
351
+ // Promote config files only (live state never promotes)
352
+ const result = await mgr.promote('dev', 'st');
353
+ console.log(result.copiedFiles); // ['trust_levels.json', 'budget_ceilings.json']
354
+
355
+ // Auto-backup destination before overwriting
356
+ const backup = await mgr.backup('st');
357
+ console.log(backup.id); // '2026-05-10T12-00-00-000Z'
358
+
359
+ // Diff two envs
360
+ const diff = await mgr.diff('dev', 'prod');
361
+ for (const f of diff.files) {
362
+ console.log(f.file, f.status, f.changedKeys);
363
+ }
364
+ ```
365
+
366
+ **Source protection** — lock agents inside `data/<env>/`:
367
+
368
+ ```typescript
369
+ import { AgentRuntime, SandboxPolicy } from 'network-ai';
370
+
371
+ const policy: SandboxPolicy = {
372
+ allowedCommands: [],
373
+ allowedPaths: ['data/dev/'],
374
+ sourceProtection: true,
375
+ env: 'dev',
376
+ };
377
+
378
+ const runtime = new AgentRuntime({ policy });
379
+ ```
380
+
381
+ Any `FileAccessor.read/write/list` call outside `data/dev/` will throw `SourceProtectionError` and be caught as `{ success: false, error: '...' }`.
382
+
383
+ ---
384
+
308
385
  ## 5. Enterprise Concerns
309
386
 
310
387
  ### Authentication & IAM
@@ -419,7 +496,7 @@ Run these before declaring the integration production-ready:
419
496
  - [ ] `npx ts-node test-phase4.ts` — 147 behavioral tests pass
420
497
  - [ ] `npx ts-node test-qa.ts` — 67 QA orchestrator tests pass
421
498
  - [ ] `npx ts-node test-phase7.ts` — 94 Phase 7 tests pass (hooks, flow control, composer, semantic search)
422
- - [ ] `npm run test:all` — all 2,711 tests pass across 26 suites
499
+ - [ ] `npm run test:all` — all 2,976 tests pass across 29 suites
423
500
  - [ ] `npm run demo -- --08` runs to completion in < 10 seconds
424
501
 
425
502
  ### Race Condition Safety
@@ -477,7 +554,7 @@ Run these before declaring the integration production-ready:
477
554
  |----------|---------------|
478
555
  | [QUICKSTART.md](QUICKSTART.md) | Get running in 5 minutes |
479
556
  | [QUICKSTART.md § CLI](QUICKSTART.md) | CLI reference — bb, auth, budget, audit commands |
480
- | [references/adapter-system.md](references/adapter-system.md) | All 28 adapters with code examples |
557
+ | [references/adapter-system.md](references/adapter-system.md) | All 29 adapters with code examples |
481
558
  | [references/trust-levels.md](references/trust-levels.md) | Trust scoring formula and agent roles |
482
559
  | [references/auth-guardian.md](references/auth-guardian.md) | Permission system, justification scoring, token lifecycle |
483
560
  | [references/blackboard-schema.md](references/blackboard-schema.md) | Blackboard key conventions and namespacing |
@@ -487,4 +564,4 @@ Run these before declaring the integration production-ready:
487
564
 
488
565
  ---
489
566
 
490
- *Network-AI v5.1.4 · MIT License · https://github.com/Jovancoding/Network-AI*
567
+ *Network-AI v5.4.0 · MIT License · https://github.com/Jovancoding/Network-AI*
package/QUICKSTART.md CHANGED
@@ -323,6 +323,46 @@ network-ai auth check grant_a1b2c3...
323
323
  network-ai auth revoke grant_a1b2c3...
324
324
  ```
325
325
 
326
+ ### Multi-Environment (`env`) — v5.4.0+
327
+
328
+ Isolate agent state across dev / staging / production using the promotion chain.
329
+
330
+ ```bash
331
+ # Initialise all environments at once
332
+ network-ai env init --all
333
+
334
+ # Or initialise a single environment
335
+ network-ai env init --env dev
336
+
337
+ # List environments and key counts
338
+ network-ai env list
339
+
340
+ # Show the promotion chain
341
+ network-ai env chain
342
+
343
+ # Diff two environments (shows +added / -removed / ~changed config keys)
344
+ network-ai env diff --from dev --to prod
345
+
346
+ # Promote config from dev → st (auto-gate, no approval needed)
347
+ network-ai env promote --from dev --to st
348
+
349
+ # Promote to preprod (requires --confirmed-by)
350
+ network-ai env promote --from qa --to preprod --confirmed-by "jane.doe"
351
+
352
+ # Promote to prod (requires --approved-by)
353
+ network-ai env promote --from preprod --to prod --approved-by "security-board"
354
+
355
+ # Backup / restore
356
+ network-ai env backup create --env prod
357
+ network-ai env backup list --env prod
358
+ network-ai env backup restore --env prod --latest
359
+ network-ai env backup prune --env prod --keep 5
360
+ ```
361
+
362
+ Set `NETWORK_AI_ENV=dev` to automatically route all blackboard and Python script operations to `data/dev/`.
363
+
364
+ ---
365
+
326
366
  ### Budget (`budget`)
327
367
 
328
368
  ```bash
package/README.md CHANGED
@@ -5,9 +5,9 @@
5
5
  [![Website](https://img.shields.io/badge/website-network--ai.org-4b9df2?style=flat&logo=web&logoColor=white)](https://network-ai.org/)
6
6
  [![CI](https://github.com/Jovancoding/Network-AI/actions/workflows/ci.yml/badge.svg)](https://github.com/Jovancoding/Network-AI/actions/workflows/ci.yml)
7
7
  [![CodeQL](https://github.com/Jovancoding/Network-AI/actions/workflows/codeql.yml/badge.svg)](https://github.com/Jovancoding/Network-AI/actions/workflows/codeql.yml)
8
- [![Release](https://img.shields.io/badge/release-v5.3.1-blue.svg)](https://github.com/Jovancoding/Network-AI/releases)
8
+ [![Release](https://img.shields.io/badge/release-v5.4.0-blue.svg)](https://github.com/Jovancoding/Network-AI/releases)
9
9
  [![npm](https://img.shields.io/npm/dw/network-ai.svg?label=npm%20downloads)](https://www.npmjs.com/package/network-ai)
10
- [![Tests](https://img.shields.io/badge/tests-2899%20passing-brightgreen.svg)](#testing)
10
+ [![Tests](https://img.shields.io/badge/tests-2976%20passing-brightgreen.svg)](#testing)
11
11
  [![Adapters](https://img.shields.io/badge/frameworks-29%20supported-blueviolet.svg)](#adapter-system)
12
12
  [![License](https://img.shields.io/badge/license-MIT-brightgreen.svg)](LICENSE)
13
13
  [![Socket](https://socket.dev/api/badge/npm/package/network-ai)](https://socket.dev/npm/package/network-ai/overview)
@@ -354,7 +354,7 @@ npx ts-node examples/10-nemoclaw-sandbox-swarm.ts
354
354
 
355
355
  ## Adapter System
356
356
 
357
- 28 adapters, zero adapter dependencies. You bring your own SDK objects.
357
+ 29 adapters, zero adapter dependencies. You bring your own SDK objects.
358
358
 
359
359
  | Adapter | Framework / Protocol | Register method |
360
360
  |---|---|---|
@@ -419,7 +419,7 @@ Extend `BaseAdapter` (or `StreamingBaseAdapter` for streaming) to add your own i
419
419
  npm run test:all # All suites in sequence
420
420
  npm test # Core orchestrator
421
421
  npm run test:security # Security module
422
- npm run test:adapters # All 28 adapters
422
+ npm run test:adapters # All 29 adapters
423
423
  npm run test:streaming # Streaming adapters
424
424
  npm run test:a2a # A2A protocol adapter
425
425
  npm run test:codex # Codex adapter
@@ -429,7 +429,7 @@ npm run test:phase9 # Agent runtime, console, strategy agent
429
429
  npm run test:phase12 # Context Throttler, Partition Planner, Coverage Gate, Route Classifier
430
430
  ```
431
431
 
432
- **2,899 passing assertions across 28 test suites** (`npm run test:all`):
432
+ **2,976 passing assertions across 29 test suites** (`npm run test:all`):
433
433
 
434
434
  | Suite | Assertions | Covers |
435
435
  |---|---|---|
@@ -437,7 +437,7 @@ npm run test:phase12 # Context Throttler, Partition Planner, Coverage Gate,
437
437
  | `test-phase5f.ts` | 127 | SSE transport, `McpCombinedBridge`, extended MCP tools |
438
438
  | `test-phase5g.ts` | 121 | CRDT backend, vector clocks, bidirectional sync |
439
439
  | `test-phase6.ts` | 121 | MCP server, control-plane tools, audit tools |
440
- | `test-adapters.ts` | 218 | All 28 adapters, registry routing, integration, edge cases |
440
+ | `test-adapters.ts` | 218 | All 29 adapters, registry routing, integration, edge cases |
441
441
  | `test-phase5d.ts` | 117 | Pluggable backend (Redis, CRDT, Memory) |
442
442
  | `test-standalone.ts` | 88 | Blackboard, auth, integration, persistence, parallelisation, quality gate |
443
443
  | `test-phase5e.ts` | 87 | Federated budget tracking |
@@ -460,6 +460,7 @@ npm run test:phase12 # Context Throttler, Partition Planner, Coverage Gate,
460
460
  | `test-topology.ts` | 304 | WorkTree, ControlPlane, dashboard server, topology visualization, WebSocket protocol |
461
461
  | `test-rlm-phases.ts` | 123 | FederatedBudget child spending, blackboard metadata API, best-partial result, HookContext depth, sub-goal recursion, semaphore fan-out, PhasePipeline compaction, RLMAdapter end-to-end |
462
462
  | `test-phase12.ts` | 65 | Context Throttler, Partition Planner, Coverage Gate, Route Classifier, EVALUATING FSM state, runTeam integration |
463
+ | `test-env-manager.ts` | 77 | Multi-environment isolation, promotion chain, backup/restore, source protection, NETWORK_AI_ENV, blackboard env routing |
463
464
  | `test.ts` | 39 | Core orchestrator smoke tests |
464
465
 
465
466
  ---
@@ -476,7 +477,7 @@ npm run test:phase12 # Context Throttler, Partition Planner, Coverage Gate,
476
477
  | [AUDIT_LOG_SCHEMA.md](AUDIT_LOG_SCHEMA.md) | Audit log field reference, all event types, scoring formula |
477
478
  | [ADOPTERS.md](ADOPTERS.md) | Known adopters — open a PR to add yourself |
478
479
  | [INTEGRATION_GUIDE.md](INTEGRATION_GUIDE.md) | End-to-end integration walkthrough with v5.0 modules |
479
- | [references/adapter-system.md](references/adapter-system.md) | Adapter architecture, all 28 adapters, writing custom adapters |
480
+ | [references/adapter-system.md](references/adapter-system.md) | Adapter architecture, all 29 adapters, writing custom adapters |
480
481
  | [references/auth-guardian.md](references/auth-guardian.md) | Permission scoring, resource types, IAuthValidator interface |
481
482
  | [references/trust-levels.md](references/trust-levels.md) | Trust level configuration, APS delegation-chain mapping |
482
483
 
package/SKILL.md CHANGED
@@ -34,7 +34,7 @@ metadata:
34
34
 
35
35
  > **Advisory tokens notice:** Grant tokens issued by `check_permission.py` are **advisory scoring outputs only** — the caller-supplied `--agent` identity is not cryptographically verified. Downstream systems must not treat these tokens as authenticated credentials without adding a separate identity-verification step or human approval gate, especially for PAYMENTS, DATABASE, and FILE_EXPORT resources.
36
36
 
37
- > **Data-flow notice (host platform — not this skill):** This skill does NOT implement, invoke, or control `sessions_send`. That is a host-platform built-in (OpenClaw runtime). The orchestration instructions below describe *when* to call the platform’s `sessions_send` after budget checks pass but the actual network call, model endpoint, and data transmission are entirely the **host platform’s** responsibility. If you need to prevent external network calls, disable or reroute `sessions_send` in your **platform settings** before installing this skill. This skill has no access to that configuration.
37
+ > **Data-flow notice (host platform — not this skill):** This skill does NOT implement, invoke, or control `sessions_send` or any inter-agent messaging. All bundled Python scripts are local-only tools (budget guard, blackboard, permission scorer, context manager). If your platform has a `sessions_send` built-in, whether and how it is used is entirely the **host platform’s** responsibility and is outside this skill’s scope. If you need to prevent external network calls, disable or reroute delegation in your **platform settings** before installing this skill.
38
38
 
39
39
  > **Context file integrity:** The `context_manager.py inject` command now validates `data/project-context.json` for injection patterns and oversized fields before printing the context block. Review any warnings printed to stderr before passing the output to an agent system prompt.
40
40
 
@@ -60,6 +60,14 @@ pip install filelock # only needed if you see locking issues on Windows
60
60
 
61
61
  The `data/` directory is created automatically on first run. No configuration files, environment variables, or credentials are required.
62
62
 
63
+ > **Multi-environment support (v5.4.0):** All five Python scripts now read the `NETWORK_AI_ENV` environment variable at startup and accept a `--env <name>` CLI argument. When set, all data paths are routed to `data/<env>/` instead of the root `data/` directory. Use this to isolate dev, staging, and production state.
64
+ >
65
+ > ```bash
66
+ > # Run against the dev environment
67
+ > NETWORK_AI_ENV=dev python3 scripts/blackboard.py list
68
+ > python3 scripts/check_permission.py --active-grants --env dev
69
+ > ```
70
+
63
71
  Multi-agent coordination system for complex workflows requiring task delegation, parallel execution, and permission-controlled access to sensitive APIs.
64
72
 
65
73
  ## 🎯 Orchestrator System Instructions
@@ -117,12 +125,12 @@ Sub-Task 3 (RECOMMEND): [strategy_advisor]
117
125
  - Output: Recommendations with rationale
118
126
  ```
119
127
 
120
- ### Budget-Aware Handoff Protocol
128
+ ### Budget Check Protocol
121
129
 
122
- **CRITICAL:** Before EVERY `sessions_send`, call the handoff interceptor:
130
+ **Run the budget interceptor before any task delegation:**
123
131
 
124
132
  ```bash
125
- # ALWAYS run this BEFORE sessions_send
133
+ # Run this before delegating to any sub-agent
126
134
  python {baseDir}/scripts/swarm_guard.py intercept-handoff \
127
135
  --task-id "task_001" \
128
136
  --from orchestrator \
@@ -133,10 +141,10 @@ python {baseDir}/scripts/swarm_guard.py intercept-handoff \
133
141
  **Decision Logic:**
134
142
  ```
135
143
  IF result.allowed == true:
136
- Proceed with sessions_send
144
+ Budget check passed — proceed with the delegated task
137
145
  → Note tokens_spent and remaining_budget
138
146
  ELSE:
139
- → STOP - Do NOT call sessions_send
147
+ → STOP budget exceeded or handoff limit reached
140
148
  → Report blocked reason to user
141
149
  → Consider: reduce scope or abort task
142
150
  ```
@@ -273,11 +281,10 @@ python {baseDir}/scripts/swarm_guard.py budget-init \
273
281
  --description "Q4 Financial Analysis"
274
282
  ```
275
283
 
276
- ### 2. Delegate a Task to Another Session
284
+ ### 2. Check Budget Before Task Delegation
277
285
 
278
- > **Platform note:** `sessions_list`, `sessions_send`, and `sessions_history` are **OpenClaw host platform built-ins** — they are part of the OpenClaw runtime, not provided or invoked by this skill's Python scripts. This skill only runs local `python scripts/*.py` commands. The guidance below describes how to combine the platform's session tools with this skill's budget guard.
279
286
 
280
- First check budget, then use the OpenClaw platform operation:
287
+ Always run the budget guard before delegating any task:
281
288
 
282
289
  ```bash
283
290
  # 1. Check budget (this skill's Python script)
@@ -285,17 +292,8 @@ python {baseDir}/scripts/swarm_guard.py intercept-handoff \
285
292
  --task-id "task_001" --from orchestrator --to data_analyst \
286
293
  --message "Analyze Q4 revenue data"
287
294
 
288
- # 2. If allowed, delegate using the OpenClaw platform tool (not this skill):
289
- # sessions_list → see available sessions/agents
290
- # sessions_send → send task to another session
291
- # sessions_history → check results from delegated work
292
- ```
293
-
294
- **Example delegation prompt:**
295
- ```
296
- After running swarm_guard.py intercept-handoff and getting result.allowed == true,
297
- use the OpenClaw sessions_send platform tool to ask the data_analyst session:
298
- "Analyze Q4 revenue trends from the SAP export data and summarize key insights"
295
+ # 2. If result.allowed == true, proceed with delegation via your platform's built-in tools.
296
+ # If result.allowed == false, stop — budget exceeded or handoff limit reached.
299
297
  ```
300
298
 
301
299
  ### 3. Check Permission Before API Access
@@ -330,7 +328,7 @@ python {baseDir}/scripts/blackboard.py list
330
328
 
331
329
  ## Agent-to-Agent Handoff Protocol
332
330
 
333
- When delegating tasks between agents/sessions:
331
+ When delegating tasks between agents, always run the budget guard first.
334
332
 
335
333
  ### Step 1: Initialize Budget & Check Capacity
336
334
  ```bash
@@ -343,12 +341,6 @@ python {baseDir}/scripts/swarm_guard.py budget-check --task-id "task_001"
343
341
 
344
342
  ### Step 2: Identify Target Agent
345
343
 
346
- > **Platform note:** `sessions_list` is an **OpenClaw host platform built-in**, not provided by this skill.
347
-
348
- ```
349
- sessions_list # OpenClaw platform operation — find available agents
350
- ```
351
-
352
344
  Common agent types:
353
345
  | Agent | Specialty |
354
346
  |-------|-----------|
@@ -357,10 +349,10 @@ Common agent types:
357
349
  | `risk_assessor` | Risk analysis, compliance checks |
358
350
  | `orchestrator` | Coordination, task decomposition |
359
351
 
360
- ### Step 3: Intercept Before Handoff (REQUIRED)
352
+ ### Step 3: Run Budget Guard Before Delegation
361
353
 
362
354
  ```bash
363
- # This checks budget AND handoff limits before allowing the call
355
+ # Check budget AND handoff limits before delegating
364
356
  python {baseDir}/scripts/swarm_guard.py intercept-handoff \
365
357
  --task-id "task_001" \
366
358
  --from orchestrator \
@@ -369,8 +361,8 @@ python {baseDir}/scripts/swarm_guard.py intercept-handoff \
369
361
  --artifact # Include if expecting output
370
362
  ```
371
363
 
372
- **If ALLOWED:** Proceed to Step 4
373
- **If BLOCKED:** Stop - do not call sessions_send
364
+ **If ALLOWED:** Proceed with delegation via your platform's own tools
365
+ **If BLOCKED:** Stop budget exceeded or handoff limit reached; do not delegate
374
366
 
375
367
  ### Step 4: Construct Handoff Message
376
368
 
@@ -380,38 +372,25 @@ Include these fields in your delegation:
380
372
  - **constraints**: Any limitations or requirements
381
373
  - **expectedOutput**: What format/content you need back
382
374
 
383
- ### Step 5: Send via OpenClaw Platform Session Tool
375
+ ### Step 5: Check Results
384
376
 
385
- > **Platform note:** `sessions_send` is an **OpenClaw host platform built-in** — it is NOT implemented by this skill. This skill only provides the budget guard (`swarm_guard.py`) that must be run first.
377
+ After delegation completes, read results from the blackboard:
386
378
 
379
+ ```bash
380
+ python {baseDir}/scripts/blackboard.py read "task:001:data_analyst"
387
381
  ```
388
- # OpenClaw platform operation (not this skill):
389
- sessions_send to data_analyst:
390
- "[HANDOFF]
391
- Instruction: Analyze Q4 revenue by product category
392
- Context: Using SAP export from ./data/q4_export.csv
393
- Constraints: Focus on top 5 categories only
394
- Expected Output: JSON summary with category, revenue, growth_pct
395
- [/HANDOFF]"
396
- ```
397
-
398
- ### Step 6: Check Results
399
382
 
400
- > **Platform note:** `sessions_history` is an **OpenClaw host platform built-in**, not provided by this skill.
383
+ ## Permission Scoring
401
384
 
402
- ```
403
- sessions_history data_analyst # OpenClaw platform operation — get the response
404
- ```
385
+ > **Tokens are audit scoring outputs only.** Grant tokens from `check_permission.py` are NOT authenticated credentials and must NOT be used as real access control. They are advisory hints based on a local scoring model. Require a separate authenticated identity and explicit human approval before accessing PAYMENTS, DATABASE, or FILE_EXPORT resources.
405
386
 
406
- ## Permission Wall
387
+ **Always score permission before accessing:**
388
+ - `DATABASE` — Internal database / data store (abstract label — no external credentials)
389
+ - `PAYMENTS` — Financial/payment data services (abstract label — requires `--confirm-high-risk`)
390
+ - `EMAIL` — Email sending capability (abstract label)
391
+ - `FILE_EXPORT` — Exporting data to local files (abstract label — requires `--confirm-high-risk`)
407
392
 
408
- **CRITICAL**: Always check permissions before accessing:
409
- - `DATABASE` - Internal database / data store access
410
- - `PAYMENTS` - Financial/payment data services
411
- - `EMAIL` - Email sending capability
412
- - `FILE_EXPORT` - Exporting data to local files
413
-
414
- > **Note**: These are abstract local resource type names used by `check_permission.py`. No external API credentials are required or used — all permission evaluation runs locally.
393
+ > **Note**: These are abstract local resource type names used by `check_permission.py`. No external API credentials are required or used — all evaluation runs locally.
415
394
 
416
395
  ### Permission Evaluation Criteria
417
396
 
@@ -507,16 +486,14 @@ Sequential processing - output of one feeds into next.
507
486
 
508
487
  ### Example Parallel Workflow
509
488
 
510
- > **Platform note:** `sessions_send` and `sessions_history` are **OpenClaw host platform built-ins**, not provided by this skill. This skill provides only the `swarm_guard.py` budget/handoff check that runs before each delegation.
511
-
512
489
  ```
513
- # For each delegation below, first run:
490
+ # For each delegation below, first run the budget guard:
514
491
  # python {baseDir}/scripts/swarm_guard.py intercept-handoff --task-id "task_001" --from orchestrator --to <agent> --message "<task>"
515
- # Then, if allowed, use the OpenClaw platform tool:
516
- 1. sessions_send to data_analyst: "Extract key metrics from Q4 data"
517
- 2. sessions_send to risk_assessor: "Identify compliance risks in Q4 data"
518
- 3. sessions_send to strategy_advisor: "Recommend actions based on Q4 trends"
519
- 4. Wait for all responses via sessions_history
492
+ # If result.allowed == true, delegate via your platform's own tools.
493
+ 1. Delegate to data_analyst: "Extract key metrics from Q4 data"
494
+ 2. Delegate to risk_assessor: "Identify compliance risks in Q4 data"
495
+ 3. Delegate to strategy_advisor: "Recommend actions based on Q4 trends"
496
+ 4. Wait for all results and read them from the blackboard
520
497
  5. Synthesize: Combine metrics + risks + recommendations into executive summary
521
498
  ```
522
499
 
package/bin/cli.ts CHANGED
@@ -16,6 +16,7 @@ import * as readline from 'readline';
16
16
  import { LockedBlackboard } from '../lib/locked-blackboard';
17
17
  import { AuthGuardian } from '../index';
18
18
  import { FederatedBudget } from '../lib/federated-budget';
19
+ import { EnvironmentManager } from '../lib/env-manager';
19
20
 
20
21
  // eslint-disable-next-line @typescript-eslint/no-var-requires
21
22
  const pkg = (() => {
@@ -30,6 +31,12 @@ function resolveData(opts: { data?: string }): string {
30
31
  return path.resolve(opts.data ?? path.join(process.cwd(), 'data'));
31
32
  }
32
33
 
34
+ function resolveEnvData(opts: { data?: string; env?: string }): string {
35
+ const base = resolveData(opts);
36
+ if (opts.env) return path.join(base, opts.env);
37
+ return base;
38
+ }
39
+
33
40
  function print(obj: unknown, asJson: boolean): void {
34
41
  if (asJson) {
35
42
  console.log(JSON.stringify(obj, null, 2));
@@ -59,6 +66,7 @@ program
59
66
  .version(pkg.version, '-v, --version')
60
67
  .enablePositionalOptions()
61
68
  .addOption(new Option('--data <path>', 'path to data directory').default('./data'))
69
+ .addOption(new Option('--env <name>', 'target environment (dev|st|sit|qa|sandbox|preprod|prod)'))
62
70
  .addOption(new Option('--json', 'output raw JSON (useful for piping)'));
63
71
 
64
72
  // ── bb (blackboard) ───────────────────────────────────────────────────────────
@@ -302,6 +310,153 @@ auditCmd.command('clear')
302
310
  print(g.json ? { cleared: logFile } : `✓ cleared ${logFile}`, g.json);
303
311
  });
304
312
 
313
+ // ── env (environment management) ──────────────────────────────────────────────
314
+
315
+ const envCmd = program.command('env').description('Multi-environment management (isolation, promotion, backup)');
316
+
317
+ envCmd.command('init')
318
+ .description('Scaffold an environment data directory (all 7 envs if no --env given)')
319
+ .action((_opts: Record<string, unknown>, cmd: Command) => {
320
+ const g = cmd.optsWithGlobals<{ data: string; env?: string; json: boolean }>();
321
+ const mgr = new EnvironmentManager(resolveData(g));
322
+ if (g.env) {
323
+ mgr.init(g.env);
324
+ print(g.json ? { initialized: g.env } : `✓ initialized env '${g.env}'`, g.json);
325
+ } else {
326
+ mgr.initAll();
327
+ print(g.json ? { initialized: mgr.getChain().concat(['sandbox']) } : `✓ all environments initialized`, g.json);
328
+ }
329
+ });
330
+
331
+ envCmd.command('list')
332
+ .description('List all environments with existence and key count')
333
+ .action((_opts: Record<string, unknown>, cmd: Command) => {
334
+ const g = cmd.optsWithGlobals<{ data: string; env?: string; json: boolean }>();
335
+ const mgr = new EnvironmentManager(resolveData(g));
336
+ const envs = mgr.list();
337
+ if (g.json) {
338
+ print(envs, true);
339
+ } else {
340
+ const lines = envs.map(e => `${e.name.padEnd(10)} ${e.exists ? '✓' : '✗'} (${e.keyCount} keys)`);
341
+ console.log(lines.join('\n'));
342
+ }
343
+ });
344
+
345
+ envCmd.command('chain')
346
+ .description('Show the configured promotion chain')
347
+ .action((_opts: Record<string, unknown>, cmd: Command) => {
348
+ const g = cmd.optsWithGlobals<{ data: string; json: boolean }>();
349
+ const mgr = new EnvironmentManager(resolveData(g));
350
+ const chain = mgr.getChain();
351
+ print(g.json ? chain : chain.join(' → '), g.json);
352
+ });
353
+
354
+ envCmd.command('diff')
355
+ .description('Compare config artefacts between two environments')
356
+ .requiredOption('--from <env>', 'source environment')
357
+ .requiredOption('--to <env>', 'target environment')
358
+ .action((opts: { from: string; to: string }, cmd: Command) => {
359
+ const g = cmd.optsWithGlobals<{ data: string; json: boolean }>();
360
+ const mgr = new EnvironmentManager(resolveData(g));
361
+ const result = mgr.diff(opts.from, opts.to);
362
+ if (g.json) {
363
+ print(result, true);
364
+ } else if (result.differences.length === 0) {
365
+ console.log(`No differences between '${opts.from}' and '${opts.to}'`);
366
+ } else {
367
+ for (const d of result.differences) {
368
+ const sym = d.status === 'added' ? '+' : d.status === 'removed' ? '-' : '~';
369
+ console.log(` ${sym} ${d.file} (${d.status})`);
370
+ }
371
+ }
372
+ });
373
+
374
+ envCmd.command('promote')
375
+ .description('Promote config artefacts one step up the chain')
376
+ .requiredOption('--from <env>', 'source environment')
377
+ .requiredOption('--to <env>', 'target environment')
378
+ .option('--confirmed-by <name>', 'required for preprod gate')
379
+ .option('--approved-by <name>', 'required for prod gate')
380
+ .action((opts: { from: string; to: string; confirmedBy?: string; approvedBy?: string }, cmd: Command) => {
381
+ const g = cmd.optsWithGlobals<{ data: string; json: boolean }>();
382
+ const mgr = new EnvironmentManager(resolveData(g));
383
+ try {
384
+ const result = mgr.promote(opts.from, opts.to, {
385
+ confirmedBy: opts.confirmedBy,
386
+ approvedBy: opts.approvedBy,
387
+ });
388
+ print(g.json ? result : `✓ promoted ${opts.from} → ${opts.to} (${result.configsCopied.length} configs copied)`, g.json);
389
+ } catch (err) {
390
+ die(err instanceof Error ? err.message : String(err));
391
+ }
392
+ });
393
+
394
+ // ── env backup subcommand group ───────────────────────────────────────────────
395
+
396
+ const envBackup = envCmd.command('backup').description('Backup management for an environment');
397
+
398
+ envBackup.command('create')
399
+ .description('Create a backup of the environment data directory (alias: network-ai env backup)')
400
+ .action((_opts: Record<string, unknown>, cmd: Command) => {
401
+ const g = cmd.optsWithGlobals<{ data: string; env?: string; json: boolean }>();
402
+ if (!g.env) die('--env <name> is required for backup create');
403
+ const mgr = new EnvironmentManager(resolveData(g));
404
+ const result = mgr.backup(g.env);
405
+ print(g.json ? result : `✓ backup created: ${result.backupId} (${result.filesCount} files)`, g.json);
406
+ });
407
+
408
+ envBackup.command('list')
409
+ .description('List available backups for an environment')
410
+ .action((_opts: Record<string, unknown>, cmd: Command) => {
411
+ const g = cmd.optsWithGlobals<{ data: string; env?: string; json: boolean }>();
412
+ if (!g.env) die('--env <name> is required for backup list');
413
+ const mgr = new EnvironmentManager(resolveData(g));
414
+ const backups = mgr.listBackups(g.env);
415
+ if (g.json) {
416
+ print(backups, true);
417
+ } else if (backups.length === 0) {
418
+ console.log('(no backups)');
419
+ } else {
420
+ for (const b of backups) {
421
+ console.log(` ${b.backupId} ${b.timestamp} ${(b.sizeBytes / 1024).toFixed(1)} KB`);
422
+ }
423
+ }
424
+ });
425
+
426
+ envBackup.command('restore')
427
+ .description('Restore an environment from a backup')
428
+ .option('--backup <id>', 'backup ID to restore')
429
+ .option('--latest', 'restore the most recent backup')
430
+ .action((opts: { backup?: string; latest?: boolean }, cmd: Command) => {
431
+ const g = cmd.optsWithGlobals<{ data: string; env?: string; json: boolean }>();
432
+ if (!g.env) die('--env <name> is required for backup restore');
433
+ if (!opts.backup && !opts.latest) die('provide --backup <id> or --latest');
434
+ const mgr = new EnvironmentManager(resolveData(g));
435
+ let backupId = opts.backup;
436
+ if (opts.latest) {
437
+ const backups = mgr.listBackups(g.env);
438
+ if (backups.length === 0) die(`no backups found for env '${g.env}'`);
439
+ backupId = backups[0].backupId;
440
+ }
441
+ try {
442
+ const result = mgr.restore(g.env, backupId!);
443
+ print(g.json ? result : `✓ restored ${result.filesRestored} files from backup '${result.backupId}'`, g.json);
444
+ } catch (err) {
445
+ die(err instanceof Error ? err.message : String(err));
446
+ }
447
+ });
448
+
449
+ envBackup.command('prune')
450
+ .description('Remove old backups, keeping the N most recent')
451
+ .requiredOption('--keep <n>', 'number of backups to retain', (v) => parseInt(v, 10))
452
+ .action((opts: { keep: number }, cmd: Command) => {
453
+ const g = cmd.optsWithGlobals<{ data: string; env?: string; json: boolean }>();
454
+ if (!g.env) die('--env <name> is required for backup prune');
455
+ const mgr = new EnvironmentManager(resolveData(g));
456
+ const deleted = mgr.pruneBackups(g.env, opts.keep);
457
+ print(g.json ? { deleted } : `✓ pruned ${deleted} backup(s), keeping ${opts.keep}`, g.json);
458
+ });
459
+
305
460
  // ── parse ─────────────────────────────────────────────────────────────────────
306
461
 
307
462
  // Auto-detect MCP stdio mode: when stdin is piped (not a TTY) and no