feat(system): credential model, stderr migration, DPLAN/FPLAN docs (#41)

* feat(system): stderr routing migration Phase 3 — 10 branches migrated (FPLAN-0033)

Autonomous 3-wave migration: 15+ agents across 10 branches converted
error/warning prints to CLI stderr functions. Seedgo stderr_routing
checker refined (false positive fix for section headers). System avg
49%→94%. Skills self-fixed (100%). Credential model DPLAN updated.
AIPASS.md moved to ~/.claude/CLAUDE.md (global culture layer).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(system): stderr routing cleanup — remaining violations fixed

Final wave: ai_mail, backup, seedgo, commons, daemon migrated to
CLI stderr functions. System stderr_routing avg now 97% (11 branches
at 100%). Seedgo checker refinement confirmed (false positives
eliminated). Only backup/google_drive_sync has remaining prints.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat(system): /prep command + cleanup memo and pre-compact hook

New /prep slash command for pre-compact preparation: saves memories,
updates plans, checks git/inbox, flags loose ends. Workflow: /prep
then /compact. Cleaned up /memo (stale field refs, flow.local.md →
STATUS.local.md). Pre-compact hook: removed misleading Notepad
reference, points to local.json and STATUS.local.md instead.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* docs(flow): DPLAN/FPLAN distinction in global prompt + DPLAN template self-documentation

Global prompt now explains DPLAN vs FPLAN with brief breadcrumbs and adds
the missing `master` command. DPLAN template now opens with a "What is a
DPLAN?" section so agents understand the plan type on first read.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
AIPass
2026-03-14 18:57:23 -07:00
committed by GitHub
co-authored by Claude Opus 4.6
parent 518d6bf2d6
commit 8fedc053ec
32 changed files with 336 additions and 260 deletions
+5
View File
@@ -55,10 +55,15 @@ drone @seedgo audit aipass # Run standards audit on all branches
drone @seedgo standards_query aipass_standards # List all standards (then query by name)
drone @prax monitor # Real-time monitoring (interactive)
drone @flow create . "Subject" # Create FPLAN (execution plan)
drone @flow create . "Subject" master # Create FPLAN master (multi-phase execution)
drone @flow create . "Subject" dplan # Create DPLAN (design/planning doc)
drone @flow list # List active plans
```
**DPLAN** = Design Plan. Thinking, brainstorming, capturing ideas and decisions. Created early — even before you know if you'll build anything. The template explains more when you open it.
**FPLAN** = Flow Plan. Building and executing. Default is for single focused tasks. Master is for multi-phase projects that spawn sub-FPLANs per phase. DPLANs come first, FPLANs come when you're ready to build.
## Dispatch — Wake a Branch
Send a task via email, then wake the branch to process it autonomously.
+2 -2
View File
@@ -13,11 +13,11 @@ Purpose: Update branch memory files after completing work this session.
### Always
- **.trinity/local.json** — Update `today_focus` with current work items. Move completed items to `recently_completed`. Add new session entry to `sessions` if significant work was done.
- **.trinity/local.json** — Add new session entry to `sessions` if significant work was done. Add new `key_learnings` for facts you'd need next time. Trim oldest sessions if over 20.
- **.trinity/observations.json** — Add notable collaboration insights: breakthrough moments, pattern corrections, flow states, friction points, preference discoveries. Skip if nothing notable this session.
### If Relevant
- **.trinity/passport.json** — Evolve identity when the branch's role, capabilities, or principles have genuinely changed. Don't update just to update — but don't leave placeholders forever either.
- **README.md** — Does it reflect current state? Update if stale.
- **flow.local.md** — Drop quick notes on issues, todos, or ideas discovered during the session.
- **STATUS.local.md** — Drop quick notes on issues, todos, or ideas in the Notepad section.
+53
View File
@@ -0,0 +1,53 @@
# Pre-Compact Prep
Purpose: Button up everything before a manual /compact. Memories, plans, git — all tidy so the compacted summary has clean state to work from.
**Workflow:** `/prep` → review output → `/compact`
## Execution
1. Read `.trinity/passport.json` first — re-absorb your identity before writing anything
2. Do ALL of the following, then confirm what was updated
## 1. Memories (same as /memo)
- **.trinity/local.json** — Add/update session entry with summary of work done. Add new key_learnings for anything learned this session. Trim oldest sessions if over 20.
- **.trinity/observations.json** — Add collaboration insights if anything notable happened. Skip if nothing new.
- **.trinity/passport.json** — Only update if role/purpose/principles genuinely changed this session.
## 2. Active Plans
- Check any DPLANs or FPLANs referenced in this session
- Update their execution logs, status, decision logs with current state
- If a plan was completed, note it (but don't close — Patrick does that)
## 3. Git State
- Run `git status` — report uncommitted changes
- If there's a logical commit waiting, suggest it (don't commit without asking)
- Note the current branch and any open PRs
## 4. Inbox
- Run `drone @ai_mail inbox 2>/dev/null` — report any unread emails
- Close any that were already processed but not formally closed
## 5. Loose Ends
- Flag anything in-flight: running background agents, dispatched branches waiting for replies, pending decisions
- If anything can't survive compaction (e.g., agent IDs needed for resume), write it to STATUS.local.md Notepad
## Confirm
List everything updated. Format:
```
Prep complete:
- local.json: [what was added]
- observations.json: [updated / skipped]
- Plans: [which ones updated]
- Git: [branch, uncommitted count, suggestion]
- Inbox: [count, action taken]
- Loose ends: [any flagged]
Ready for /compact.
```
+2 -2
View File
@@ -149,8 +149,8 @@ Context just compacted. Below is your live state. Use it to continue seamlessly.
# Recovery instructions (lean)
sections.append("""## Recovery Protocol
- Continue where the summary left off — don't restart or ask generic questions
- STATUS.local.md Notepad has your scratch context
- .trinity/local.json has full session history and key_learnings if you need more
- .trinity/local.json has full session history and key_learnings — read it if you need more context
- STATUS.local.md has current work, known issues, and todos
- Save memories proactively — compaction just proved you need to
- Match the conversation tone from before compaction""")
+1
View File
@@ -60,3 +60,4 @@ backup_data/
backups
backup_system
src/aipass/flow/CLOSED_PLANS.local.json
-14
View File
@@ -1,14 +0,0 @@
# AIPass — Project Prompt
Project-level instructions. Persists in context for the entire conversation.
## Startup
On any greeting, silently read these files from CWD and run the commands — no narration, no announcing steps. Just do it and respond with the status.
**Read:** `.trinity/passport.json`, `.trinity/local.json`, `.trinity/observations.json`, `README.md`
**Run:** `git status`
## Memories
Update `.trinity/` at natural breakpoints, after milestones, and on `/memo`. If compaction hits before you save, it's gone.
+99 -103
View File
@@ -3,13 +3,37 @@
# AIPass
> **Building in public.** Active development — not a finished product. Expect breaking changes.
> **An AI operating system.** Persistent memory, multi-agent orchestration, and autonomous citizens — all in one filesystem.
Orchestration framework for autonomous AI agent ecosystems. Command routing, symbolic addressing, standards enforcement, workflow management, and inter-agent messaging. Agents use `@branch` names that resolve at runtime instead of hard-coded paths. Each agent has persistent identity and memory via `.trinity/` files (passport, session history, observations).
AIPass is a framework where AI agents live as **citizens** in a shared system. Each citizen has its own directory, identity, memories, and mailbox. They communicate, delegate work, enforce standards, and build their own capabilities over time — without stepping on each other's toes.
## Quick Start
The goal: `pip install aipass`, run `aipass init` in any directory, and get a fully operational AI agent ecosystem. No cloud services, no external dependencies, no vendor lock-in.
### 1. Install
## What We're Building
An operating system for AI agents. Not a chatbot wrapper. Not a prompt chain. A persistent, multi-agent environment where:
- **15 citizens** work in the same filesystem without isolation (no git worktrees, no sandboxes)
- **Dispatch locks** prevent conflicts — if an agent is working, incoming tasks queue instead of spawning duplicates
- **Persistent memory** survives across sessions via `.trinity/` files (identity, session history, collaboration patterns)
- **Standards enforcement** keeps the system consistent as it grows (seedgo runs 24+ automated checks)
- **Inter-agent messaging** lets citizens email each other, dispatch tasks, and wake each other up
- **Everything is tracked** — design plans (DPLANs), execution plans (FPLANs), and seedgo audits make changes traceable even when 500+ files change in a single session
## Current State: Beta
**It works.** The pieces are all in place and the system runs well in a single project. We're past prototyping — 15 branches are operational, tested, and communicating.
**What we're solving now:**
- **Multi-project isolation** — `aipass init` should work in any directory, creating its own registry and credentials. Right now the system assumes one project. We're adding a credential model (UUID-based registry matching) so agents always know which project they belong to, even if multiple AIPass projects exist on the same machine.
- **System-wide compliance** — as we make large-scale changes (stderr routing, import patterns, error handling), seedgo audits ensure nothing drifts. We're refining the checkers to eliminate false positives and catch real issues.
- **Cross-platform reliability** — Linux and Windows tested. macOS is structurally supported but needs a dedicated testing pass. All paths use `pathlib`, no hardcoded paths, secrets stored at `~/.secrets/aipass/`.
- **Agent agnosticism** — currently focused on [Claude Code](https://docs.anthropic.com/en/docs/claude-code) (hooks for auto-diagnostics, prompt injection, session recovery). But AIPass is designed to not depend on any single provider. `agents.md` and `gemini.md` can bootstrap the system for Codex and Gemini — you lose hooks but keep the core. The plan is truly agnostic: any agent, anywhere, persistent memory, no vendor lock-in.
## Getting Started
### Install
```bash
git clone https://github.com/AIOSAI/AIPass.git
@@ -18,134 +42,106 @@ cd AIPass
source .venv/bin/activate
```
`setup.sh` handles everything: creates the venv, installs the package, generates the branch registry (15 branches), bootstraps identity files (`.trinity/`, `.seedgo/`, `.ai_mail.local/`) for every branch, and installs Claude Code hooks. Idempotent — safe to re-run.
`setup.sh` creates the venv, installs the package, generates the branch registry (15 branches), bootstraps identity files for every branch, and installs hooks. Idempotent — safe to re-run.
> **Why clone?** You can `pip install aipass`, but during beta we recommend cloning. Your agents can see the source, read other branches, and help you troubleshoot. Once the system stabilizes, `pip install` + `aipass init` will be the standard path.
Verify:
```bash
drone systems # Should show 15 branches
drone @seedgo verify # Should show 5/5 checks passed
drone systems # Should show 15 branches
```
### 2. Start a Session
### Start With Devpulse
AIPass is designed to be operated by AI agents via [Claude Code](https://docs.anthropic.com/en/docs/claude-code). The orchestration hub is **devpulse** — start there.
Devpulse is the orchestration hub — your first relationship in the system. Start here.
```bash
cd src/aipass/devpulse
claude --permission-mode bypassPermissions
claude
```
Say `hi` to trigger the [startup protocol](#4-startup-protocol). The agent reads its identity and memory files, checks git status, verifies system health, and picks up where it left off. In a returning session, use `/resume` instead.
Then just talk to it. Ask what the system is, what's been built, what branches exist, how drone works, what it knows, what it doesn't. Devpulse will investigate, dispatch other branches, and bring information back to you.
### 3. What You Get
**The pattern:** You work with devpulse. Devpulse dispatches to specialists. Specialists do the work and report back. You never need to context-switch between 15 agents — devpulse is your single point of contact.
After setup, every branch has:
Once devpulse confirms the core systems are working (email, drone routing, flow plans), you can start exploring individual branches directly with `cd src/aipass/{branch} && claude`.
### What Each Branch Does
Every branch is a citizen — an expert in its domain with its own memories and identity.
| Branch | Role |
|--------|------|
| `devpulse` | **Start here.** Orchestration hub — coordinates everything |
| `drone` | Command routing — `drone @branch command` resolves and routes |
| `seedgo` | Standards enforcement — 24-standard audit pack, system compliance |
| `prax` | Logging and monitoring (the only logger in the system) |
| `cli` | Terminal display, stderr routing, project commands |
| `flow` | Workflow management — FPLANs (execution) and DPLANs (design) |
| `ai_mail` | Inter-agent messaging, dispatch, wake |
| `spawn` | Branch lifecycle — create, update, credential injection |
| `trigger` | Event-driven automation, circuit breaker |
| `api` | LLM access via OpenRouter |
| `backup` | Multi-mode backup (snapshot, versioned, Google Drive) |
| `daemon` | Background scheduler, cron, notifications |
| `memory` | Vector memory bank (ChromaDB) |
| `commons` | Social network — posts, rooms, artifacts |
| `skills` | Capability framework — discoverable, executable skill units |
## How It Works
### No Isolation, No Problem
Most multi-agent systems isolate agents in separate environments. AIPass doesn't. All 15 citizens work in the same filesystem, same git repo, same codebase. This is intentional.
Each citizen owns its directory (`src/aipass/{name}/`). It doesn't touch other branches' files. If it finds an issue in another branch, it sends an email. Dispatch locks prevent two instances of the same agent from running simultaneously — no toe-stepping, no race conditions.
This only works because of discipline: standards enforcement, persistent memory, and clear ownership boundaries.
### Tracking at Scale
When a session produces 500+ file changes across 10 branches, you need tracking. AIPass uses:
- **DPLANs** — design/planning documents. "Here's what we want to build and why."
- **FPLANs** — execution plans. "Here are the exact steps, and here's the status of each."
- **Seedgo audits** — automated compliance checks. Run before and after changes to measure drift.
Changes are never untracked. Every decision has a plan, every plan has a record.
### Persistent Memory
Every citizen has `.trinity/` files:
```
.trinity/passport.json # Identity (name, role, citizen class)
.trinity/local.json # Session history (empty, ready to populate)
.trinity/observations.json # Collaboration patterns (empty)
.seedgo/bypass.json # Standards bypass config
.ai_mail.local/inbox.json # Mailbox
.trinity/passport.json # Identity — who am I, what's my role
.trinity/local.json # Session history — what happened, what I learned
.trinity/observations.json # Collaboration patterns — how we work together
```
The agent fills in its own memories as it works — session logs, learnings, observations. These grow over time and persist across sessions.
### 4. Startup Protocol
These greetings trigger the full startup sequence: `hi`, `hello`, `yo`, `hey`, `sup`, `good morning`, `good evening`, `what's up`. Everything else is treated as a direct task.
On startup the agent:
1. Reads `.trinity/passport.json` (identity), `local.json` (session history), `observations.json` (patterns)
2. Runs `git status`, `drone systems`, `drone @seedgo verify`
3. Checks active tasks and recent session context
4. Picks up work or waits for instructions
## Core Concepts
### Drone — Command Router
Everything goes through `drone`. It resolves `@branch` names to paths and routes commands.
```bash
drone @seedgo audit aipass # Run standards audit
drone @seedgo verify # Check seedgo health
drone @spawn --help # Show help for a branch
drone systems # List all registered modules
```
In Python:
```python
from aipass.drone.apps.modules.resolver import resolve_branch
from aipass.drone.apps.modules.registry import load_registry
path = resolve_branch("@drone") # Resolve @name to path
registry = load_registry() # Load full branch registry
```
### Seedgo — Standards Enforcement
Standards-based code auditor. Checks are organized into packs — the built-in `aipass` pack has 21 standards covering imports, architecture, naming, error handling, logging, and more.
```bash
drone @seedgo verify # Health check
drone @seedgo list # Show installed standard packs
drone @seedgo audit aipass # Run the aipass standards pack
seedgo checklist aipass file # Check a single file
```
### Symbolic Addressing
All modules register in `AIPASS_REGISTRY.json`. Drone resolves `@name` to paths at runtime — no hard-coded paths between modules.
These grow over time. A citizen that's been through 20+ sessions knows things — patterns, gotchas, preferences, past decisions. When context compacts (conversation gets too long), memories survive because they're written to disk. When a new session starts, the citizen reads its memories and picks up where it left off.
## Architecture
All modules follow a 3-layer pattern:
```
src/aipass/<module>/
src/aipass/<branch>/
├── .trinity/ # Identity & memory
├── .aipass/ # System prompt
├── .ai_mail.local/ # Mailbox
├── apps/
│ ├── <module>.py # Entry point (what drone routes to)
│ ├── modules/ # Business logic
│ └── handlers/ # Implementation details
│ ├── <branch>.py # Entry point (drone routes here)
│ ├── modules/ # Business logic
│ └── handlers/ # Implementation
└── README.md
```
**15 branches** — all under active development. Ported from a private predecessor project; not all modules are fully tested or wired up yet.
| Branch | Purpose |
|--------|---------|
| `drone` | Command routing, `@branch` resolution |
| `seedgo` | Standards enforcement, 21-standard audit pack |
| `prax` | Logging and monitoring (the only logger) |
| `cli` | Display formatting (Rich) |
| `flow` | Workflow and plan management (FPLAN lifecycle) |
| `ai_mail` | Inter-agent messaging, dispatch, wake |
| `spawn` | Branch lifecycle — create, update, delete |
| `trigger` | Event-driven automation, circuit breaker |
| `api` | LLM access via OpenRouter, key management |
| `backup` | Multi-mode backup (snapshot, versioned, Google Drive) |
| `daemon` | Background scheduler, cron, Telegram notifications |
| `memory` | Vector memory bank (ChromaDB, sentence-transformers) |
| `commons` | Social network for branches — posts, rooms, artifacts |
| `skills` | Capability framework — discoverable, executable skill units |
| `devpulse` | Orchestration hub (no code — coordination only) |
## Docker
```bash
docker build -t aipass-test .
docker run -d -p 8080:8080 --name aipass-vscode aipass-test
```
Opens VS Code in browser at `http://localhost:8080` with AIPass pre-installed.
All branches follow this structure. Drone resolves `@name` to paths via `AIPASS_REGISTRY.json` — no hardcoded paths between modules.
## Requirements
- Python 3.10+
- No external API keys required
- Dependencies: `rich`, `watchdog`
- No external API keys required for core functionality
- [Claude Code](https://docs.anthropic.com/en/docs/claude-code) recommended (hooks provide auto-diagnostics, prompt injection, session recovery)
## License
+24 -15
View File
@@ -280,13 +280,13 @@ def handle_view(args: List[str]) -> bool:
"""View email content and mark as opened."""
log_operation("view_email_initiated", {"args": args})
if not args:
console.print("[red]Usage: drone @ai_mail view <message_id>[/red]")
error("Usage: drone @ai_mail view <message_id>")
return False
try:
branch_path = Path(get_current_user()["mailbox_path"]).parent
success, message, email_data = mark_as_opened(branch_path, args[0])
if not success:
console.print(f"[red]{message}[/red]")
error(message)
return False
console.print(f"\n{'='*60}")
console.print(f"From: {email_data.get('from', 'unknown')} ({email_data.get('from_name', '')})")
@@ -302,7 +302,7 @@ def handle_view(args: List[str]) -> bool:
return True
except Exception as e:
logger.error(f"[email] View failed: {e}")
console.print(f"[red]Error: {e}[/red]")
error(f"Error: {e}")
return False
@@ -310,20 +310,26 @@ def handle_close(args: List[str]) -> bool:
"""Close email(s) and archive to deleted."""
log_operation("close_email_initiated", {"args": args})
if not args:
console.print("[red]Usage: drone @ai_mail close <id> [id2 ...] | close all[/red]")
error("Usage: drone @ai_mail close <id> [id2 ...] | close all")
return False
try:
branch_path = Path(get_current_user()["mailbox_path"]).parent
if args[0].lower() == "all":
success, message, count = mark_all_read_and_archive(branch_path)
console.print(f"{'[green]' if success else '[red]'}{message}{'[/green]' if success else '[/red]'}")
if success:
console.print(f"[green]{message}[/green]")
else:
error(message)
if success:
log_operation("email_closed_all", {"count": count})
return success
results, closed, failed = batch_close(branch_path, args, mark_as_closed_and_archive)
for msg_id, success, message in results:
console.print(f"{'[green]' if success else '[red]'}{message}{'[/green]' if success else '[/red]'}")
if success:
console.print(f"[green]{message}[/green]")
else:
error(message)
if success:
log_operation("email_closed", {"message_id": msg_id})
@@ -338,7 +344,7 @@ def handle_close(args: List[str]) -> bool:
return failed == 0
except Exception as e:
logger.error(f"[email] Close failed: {e}")
console.print(f"[red]Error: {e}[/red]")
error(f"Error: {e}")
return False
@@ -346,23 +352,26 @@ def handle_reply(args: List[str]) -> bool:
"""Reply to an email."""
log_operation("reply_email_initiated", {"args": args})
if len(args) < 2:
console.print("[red]Usage: drone @ai_mail reply <message_id> \"your message\"[/red]")
error("Usage: drone @ai_mail reply <message_id> \"your message\"")
return False
try:
branch_path = Path(get_current_user()["mailbox_path"]).parent
inbox_file = branch_path / ".ai_mail.local" / "inbox.json"
original = get_email_by_id(inbox_file, args[0])
if not original:
console.print(f"[red]Message not found: {args[0]}[/red]")
error(f"Message not found: {args[0]}")
return False
success, message, reply_id = send_reply(branch_path, original, args[1])
console.print(f"{'[green]' if success else '[red]'}{message}{'[/green]' if success else '[/red]'}")
if success:
console.print(f"[green]{message}[/green]")
else:
error(message)
if success:
log_operation("email_replied", {"message_id": args[0], "reply_id": reply_id})
return success
except Exception as e:
logger.error(f"[email] Reply failed: {e}")
console.print(f"[red]Error: {e}[/red]")
error(f"Error: {e}")
return False
@@ -387,7 +396,7 @@ def handle_sent(args: List[str]) -> bool:
return True
except Exception as e:
logger.error(f"[email] Sent view failed: {e}")
console.print(f"[red]Error: {e}[/red]")
error(f"Error: {e}")
return False
@@ -397,7 +406,7 @@ def handle_contacts(args: List[str]) -> bool:
try:
branches = get_all_branches()
if not branches:
console.print("[red]No contacts found[/red]")
error("No contacts found")
return False
console.print(f"\nTotal: {len(branches)} branches\n")
console.print(f"{'EMAIL':<20} {'BRANCH NAME':<25} {'PATH':<35}")
@@ -407,7 +416,7 @@ def handle_contacts(args: List[str]) -> bool:
return True
except Exception as e:
logger.error(f"[email] Contacts view failed: {e}")
console.print(f"[red]Error: {e}[/red]")
error(f"Error: {e}")
return False
@@ -463,6 +472,6 @@ if __name__ == "__main__":
command = sys.argv[1]
remaining = sys.argv[2:] if len(sys.argv) > 2 else []
if not handle_command(command, remaining):
console.print(f"\n[red]Unknown command: {command}[/red]")
error(f"Unknown command: {command}")
console.print("[dim]Run 'python3 email.py --help' for available commands[/dim]\n")
sys.exit(1)
@@ -36,6 +36,7 @@ from pathlib import Path
from typing import Dict
from aipass.cli.apps.modules import console, header, success, error
from aipass.cli.apps.modules.display import warning
from aipass.prax import logger
@@ -180,7 +181,7 @@ def handle_command(args) -> bool:
if result.critical_errors:
error(f"{mode.title()} backup FAILED")
elif result.errors > 0:
console.print(f"[yellow]{mode.title()} completed with {result.errors} errors[/yellow]")
warning(f"{mode.title()} completed with {result.errors} errors")
console.print(f" [dim]Files: {result.files_copied} copied, {result.files_skipped} skipped[/dim]")
else:
if dry_run:
@@ -458,7 +459,7 @@ class BackupEngine:
except Exception as e:
result.add_error(f"Error processing {file_path}: {e}", is_critical=True)
# Use console.print directly for critical errors so they appear above progress bar
console.print(f"[red]CRITICAL: Error processing {file_path}: {e}[/red]")
error(f"CRITICAL: Error processing {file_path}: {e}")
# Update progress
progress.advance(task)
@@ -21,6 +21,7 @@ import sys
from pathlib import Path
from rich.progress import Progress, BarColumn, TextColumn, TimeElapsedColumn, TimeRemainingColumn
from aipass.cli.apps.modules import console
from aipass.cli.apps.modules.display import error, warning
from aipass.prax import logger
_BACKUP_ROOT = Path(__file__).resolve().parents[2] # src/aipass/backup/
@@ -154,7 +155,7 @@ def _run_sync_test() -> bool:
# Create test files via handler
setup = create_sync_test_files(_BACKUP_ROOT)
if not setup["success"]:
console.print(f"[red]Failed to create test files: {setup['error']}[/red]")
error(f"Failed to create test files: {setup['error']}")
return False
test_dir = setup["test_dir"]
@@ -163,7 +164,7 @@ def _run_sync_test() -> bool:
# Run sync
sync = GoogleDriveSync()
if not sync.authenticate():
console.print("[red]Auth failed[/red]")
error("Auth failed")
cleanup_sync_test_dir(test_dir)
return False
@@ -178,7 +179,7 @@ def _run_sync_test() -> bool:
folder_id = sync.get_or_create_project_folder("AIPass_Test")
if not folder_id:
error_msg = sync.last_error or "Unknown error"
console.print(f"[red]FAILED: {error_msg}[/red]")
error(f"FAILED: {error_msg}")
cleanup_sync_test_dir(test_dir)
return False
console.print(f" Drive folder ready: AIPass_Test")
@@ -198,7 +199,7 @@ def _run_sync_test() -> bool:
)
if result.get("error"):
console.print(f"\n[red]FAILED: {result['error']}[/red]")
error(f"FAILED: {result['error']}")
cleanup_sync_test_dir(test_dir)
return False
@@ -206,7 +207,7 @@ def _run_sync_test() -> bool:
if result["success"]:
console.print(f"[green]Test passed: {result['uploaded']}/{result['total']} files synced[/green]")
else:
console.print(f"[red]Test failed: {result['uploaded']} OK, {result['failed']} failed[/red]")
error(f"Test failed: {result['uploaded']} OK, {result['failed']} failed")
# Run it again to verify no duplicates
console.print(f"\nRe-running sync (should show 0 to upload)...")
@@ -215,7 +216,7 @@ def _run_sync_test() -> bool:
if len(files_to_upload2) == 0:
console.print(f"[green]Dedup check passed: 0 files need re-upload[/green]")
else:
console.print(f"[red]Dedup check failed: {len(files_to_upload2)} files flagged for re-upload[/red]")
error(f"Dedup check failed: {len(files_to_upload2)} files flagged for re-upload")
# Cleanup local test dir via handler
cleanup_sync_test_dir(test_dir)
@@ -18,6 +18,7 @@ import sys
from pathlib import Path
from aipass.cli.apps.modules import console
from aipass.cli.apps.modules.display import error
from aipass.prax import logger
# Handler imports
@@ -70,12 +71,12 @@ def _execute_reauth() -> bool:
from google.auth.transport.requests import Request # noqa: F811
from googleapiclient.discovery import build
except ImportError as e:
console.print(f"[red]Missing package: {e}[/red]")
error(f"Missing package: {e}")
console.print(f"Install: {sys.executable} -m pip install google-api-python-client google-auth-httplib2 google-auth-oauthlib")
return False
if not CLIENT_SECRETS.exists():
console.print(f"[red]ERROR: Client secrets not found at: {CLIENT_SECRETS}[/red]")
error(f"Client secrets not found at: {CLIENT_SECRETS}")
logger.warning(f"OAuth client secrets not found at: {CLIENT_SECRETS}")
return False
@@ -119,7 +120,7 @@ def _execute_reauth() -> bool:
except Exception:
pass
else:
console.print("[red]Re-authentication FAILED[/red]")
error("Re-authentication FAILED")
logger.error("Drive re-authentication failed")
return success
@@ -23,7 +23,7 @@ from typing import Dict, Any, List
from aipass.prax import logger
# logger imported from aipass.prax
from aipass.cli.apps.modules import console
from aipass.cli.apps.modules import console, error
# Import report generation handler (implementation lives in handler layer)
from aipass.daemon.apps.handlers.monitoring.report_generator import (
@@ -229,7 +229,7 @@ def handle_command(command: str, args: List[str]) -> bool:
i += 1
if not branch_name:
console.print("ERROR: branch-health requires a branch name")
error("branch-health requires a branch name")
console.print()
console.print("Usage: branch-health <branch_name> [--hours N]")
console.print("Example: branch-health DRONE")
@@ -154,3 +154,4 @@ Discovered during testing. Reference for future work.
- **2026-03-14:** FPLAN-0032 executed — CLI stderr standardization Phase 1+2 done. CLI owns err_console, error()/warning() go to stderr. Drone imports from CLI instead of creating its own Console(stderr=True). Credential mismatch errors now properly surface to terminal via stderr. The stderr issue that blocked error visibility during DPLAN-003 testing is resolved.
- **2026-03-14:** CLI confirmed as owner of credential model + project commands. CLI owns: `aipass init` (create project), `aipass help` (project help), display API (error/warning/fatal/err_console). Drone owns verification (registry_handler). Spawn owns injection (passport templates). Access via `drone @cli aipass init/help` for now; `aipass init` entry point is future icing.
- **2026-03-14:** Seedgo `stderr_routing` standard created (24th standard). Checker bug found — was scanning all files but not displaying module/handler results. Seedgo fixed display + aggregation. Agent-verified across 3 branches. FPLAN-0033 parked for Phase 3 migration (343 error prints, 10 branches).
- **2026-03-14:** FPLAN-0033 Phase 3 executed autonomously (Patrick away). 15+ agents across 3 waves migrated 10 branches. 48 files, 666 insertions. System stderr avg 49%→69%. Post-migration verification found 63% of remaining seedgo violations are false positives ([yellow]Label:[/yellow] section headers flagged as warnings). Seedgo dispatched for checker refinement. 93 real violations remain (48 in commons which was out of scope).
-46
View File
@@ -1,46 +0,0 @@
{
"closed_plans": [
{
"plan_id": "DPLAN-002",
"type": "DPLAN",
"subject": "DPLAN-002__2026-03-10",
"date_closed": "2026-03-10",
"location": "dev_central"
},
{
"plan_id": "DPLAN-001",
"type": "DPLAN",
"subject": "test dispatch fix",
"date_closed": "2026-03-10",
"location": "dev_central"
},
{
"plan_id": "FPLAN-0024",
"type": "FPLAN",
"subject": "Test close with real content",
"date_closed": "2026-03-10",
"location": "flow"
},
{
"plan_id": "FPLAN-0025",
"type": "FPLAN",
"subject": "Build STATUS board — per-branch STATUS.local.md with Prax-managed central aggregation",
"date_closed": "2026-03-10",
"location": "flow"
},
{
"plan_id": "FPLAN-0032",
"type": "FPLAN",
"subject": "CLI stderr standardization — Phase 1 add err_console to display.py",
"date_closed": "2026-03-14",
"location": "flow"
},
{
"plan_id": "FPLAN-0031",
"type": "FPLAN",
"subject": "Drone stderr and error propagation — investigation and long-term fix",
"date_closed": "2026-03-14",
"location": "flow"
}
]
}
+1 -1
View File
@@ -114,7 +114,7 @@ def _display_messages(messages: List[Dict[str, Any]]):
console.print(format_plan_deletion_success(msg["plan_key"]))
elif msg_type == "plan_list":
console.print(f"\n[bold yellow]Found {msg['count']} open plan(s) to close:[/bold yellow]")
warning(f"Found {msg['count']} open plan(s) to close:")
for plan in msg.get("plans", []):
console.print(f" * FPLAN-{plan['plan_num']}: {plan['subject']}")
+1 -1
View File
@@ -479,7 +479,7 @@ def _handle_close_all() -> bool:
warning("No open plans to close")
return True
console.print(f"\n[bold yellow]Found {len(open_plans)} open plan(s) to close:[/bold yellow]")
warning(f"Found {len(open_plans)} open plan(s) to close:")
for p in open_plans:
console.print(f" - DPLAN-{p['number']:03d}: {p['topic']}")
@@ -195,7 +195,7 @@ def handle_command(command: str, args: List[str]) -> bool:
console.print(f" • Renumbered: {len(result['renumbered'])}")
if result['healing_performed']:
console.print(f"\n[yellow]Registry healed - {len(result['added']) + len(result['updated']) + len(result['removed'])} changes[/yellow]")
warning(f"Registry healed - {len(result['added']) + len(result['updated']) + len(result['removed'])} changes")
else:
console.print(f"\n[dim]No changes needed - registry is healthy[/dim]")
@@ -215,7 +215,7 @@ def handle_command(command: str, args: List[str]) -> bool:
success = start_monitoring()
if success:
console.print()
console.print("[bold yellow]Monitor is running. Press Ctrl+C to stop.[/bold yellow]")
warning("Monitor is running. Press Ctrl+C to stop.")
console.print()
# Keep script alive
@@ -4,6 +4,32 @@ Tag: {{TAG}}
> One-line description
---
## What is a DPLAN?
Design Plans (DPLANs) are for **THINKING** — capturing ideas, brainstorming, investigating, planning, and making decisions. They are the space where conversations, research, and design work get written down so they can be reclaimed later.
**This IS for:**
- Capturing an idea or concept worth exploring
- Brainstorming and design discussions
- Investigating a problem — sending agents to research, running tests, gathering data
- Planning an upgrade, refactor, or new feature before building it
- Recording decisions and the reasoning behind them
- Anything that needs to be thought through before (or instead of) executing
**This is NOT for:**
- Building code or executing tasks — that's an FPLAN (Flow Plan)
- Quick fixes — just do those directly
**DPLANs have no fixed structure.** The sections below are starting points. Add sections, remove sections, go wherever the thinking takes you. A DPLAN might be a quick idea capture or a 50-phase investigation — both are valid.
**When this plan is ready to build**, create an FPLAN: `drone @flow create . "Subject"` (default for focused tasks, `master` for multi-phase builds). The DPLAN stays as the design record.
**Never trim a DPLAN.** The story — conversations, decisions, dead ends, pivots — is as important as the results.
---
## Vision
What we're trying to achieve
@@ -159,6 +159,42 @@ def check_module(module_path: str, bypass_rules: list | None = None) -> Dict:
}
def _is_markup_label(line: str, color: str, max_short_words: int = 2) -> bool:
"""Check if colored markup in a console.print() is a label/highlight, not a warning/error.
Detects section headers, term highlights, and short formatting labels that use
colored markup for emphasis — not for signaling warnings or errors.
Label patterns:
- Short highlight/term (<=max_short_words): 'off', 'Muted', 'Missing branch name'
- Colon-terminated: 'COMMANDS:', 'Options:', 'Discovered Modules:'
- ALL CAPS headers: 'ACTIONABLE ITEMS', 'ESCALATIONS NEEDED'
max_short_words controls the threshold for short-phrase detection:
- Yellow (warnings): 2 — real warnings like 'Template version mismatch' are 3+ words
- Red (errors): 5 — red is commonly used for CLI feedback phrases
"""
pattern = re.compile(
r'\[(?:bold\s+)?' + re.escape(color) + r'(?:\s+bold)?\](.*?)\[/',
re.IGNORECASE
)
match = pattern.search(line)
if match:
text = match.group(1).strip()
words = text.split()
word_count = len(words)
# Short highlight/term — too brief to be a real error/warning sentence
if word_count <= max_short_words:
return True
# Label ending with colon (e.g., 'COMMANDS:', 'Discovered Modules:')
if text.endswith(':') and word_count <= 3:
return True
# ALL CAPS section header (e.g., 'ACTIONABLE ITEMS')
if text.isupper() and word_count <= 3:
return True
return False
def _has_any_output(content: str) -> bool:
"""Check if file has any console output at all."""
return 'console.print(' in content or 'err_console.print(' in content
@@ -183,8 +219,10 @@ def _find_error_prints(lines: List[str], module_path: str, bypass_rules: list |
# Patterns: [red], [bold red], [red bold] in console.print()
red_pattern = re.compile(r'console\.print\(.*\[(?:bold\s+)?red(?:\s+bold)?\]', re.IGNORECASE)
# Also catch: console.print(f"❌ ..." or console.print("Error: ...")
error_msg_pattern = re.compile(r'console\.print\([f"\']*[^)]*(?:error|failed|fatal|cannot|unable|invalid|❌)', re.IGNORECASE)
# Also catch: console.print("Error: ...") or console.print("Failed to ...")
# Only match when keyword appears at the START of the string content (not buried in help text)
# "error" requires a colon (to distinguish "Error: ..." from "Error Registry ...")
error_msg_pattern = re.compile(r'''console\.print\(\s*f?["']\s*(?:error\s*:|(?:failed|fatal|cannot|unable|invalid)\b)''', re.IGNORECASE)
for i, line in enumerate(lines, 1):
stripped = line.strip()
@@ -219,12 +257,11 @@ def _find_error_prints(lines: List[str], module_path: str, bypass_rules: list |
if is_bypassed(module_path, 'stderr_routing', line=i, bypass_rules=bypass_rules):
continue
# Match red markup patterns
# Match red markup patterns (skip CLI feedback — short red phrases are UI styling)
if red_pattern.search(stripped):
violations.append(i)
if not _is_markup_label(stripped, 'red', max_short_words=5):
violations.append(i)
elif error_msg_pattern.search(stripped):
# Only flag if it looks like an error message, not just mentioning the word
# Must be the console.print argument, not a variable name
violations.append(i)
return violations
@@ -239,7 +276,8 @@ def _find_warning_prints(lines: List[str], module_path: str, bypass_rules: list
# Patterns: [yellow] in console.print() that look like warnings
yellow_pattern = re.compile(r'console\.print\(.*\[(?:bold\s+)?yellow(?:\s+bold)?\]', re.IGNORECASE)
warning_msg_pattern = re.compile(r'console\.print\([f"\']*[^)]*(?:warning|⚠)', re.IGNORECASE)
# Only match when keyword appears at the START of the string content (not buried in help text)
warning_msg_pattern = re.compile(r'''console\.print\(\s*f?["']\s*(?:⚠\s*)?warning\b''', re.IGNORECASE)
for i, line in enumerate(lines, 1):
stripped = line.strip()
@@ -270,7 +308,8 @@ def _find_warning_prints(lines: List[str], module_path: str, bypass_rules: list
continue
if yellow_pattern.search(stripped):
violations.append(i)
if not _is_markup_label(stripped, 'yellow'):
violations.append(i)
elif warning_msg_pattern.search(stripped):
violations.append(i)
@@ -29,6 +29,7 @@ from aipass.prax import logger
# CLI services (display/output formatting)
from aipass.cli import console, header
from aipass.cli.apps.modules import error, warning
# =============================================================================
@@ -110,7 +111,7 @@ def print_branch_summary(audit_result: Dict, system_averages: Dict[str, int] | N
# Count failures for architecture
if standard_name == 'architecture' and has_failures:
failed_checks = [c for c in result.get('checks', []) if not c.get('passed', False)]
console.print(f" [yellow]└─ Architecture violations ({len(failed_checks)} missing):[/yellow]")
warning(f" └─ Architecture violations ({len(failed_checks)} missing):")
# Group by type for clarity (match both "Dir:" and "Directory:" prefixes)
missing_dirs = [c for c in failed_checks if 'Dir:' in c.get('name', '') or 'Directory:' in c.get('name', '')]
@@ -398,7 +399,7 @@ def print_bypass_audit(bypass_results: List[Dict]):
if status == 'file_missing':
console.print(f" [red]✗[/red] {file_name} [{standard}]")
console.print(f" [red]FILE MISSING - bypass can be removed[/red]")
error("FILE MISSING - bypass can be removed")
removable_count += 1
elif status == 'checked':
score = r['current_score']
@@ -25,6 +25,7 @@ from aipass.prax import logger
# CLI service
from aipass.cli import console
from aipass.cli import header
from aipass.cli.apps.modules import error, warning
# JSON handler for logging
from aipass.seedgo.apps.handlers.json import json_handler
@@ -139,8 +140,8 @@ def handle_command(command: str, args: List[str]) -> bool:
return True
# Unknown argument — fail to error, not fallback
console.print(f"\n[red]Unknown argument:[/red] '{args[0]}'")
console.print("[yellow]This module has no subcommands.[/yellow]")
error(f"Unknown argument: '{args[0]}'")
warning("This module has no subcommands.")
console.print("Diagnostics runs through the audit pipeline:")
console.print(" [green]drone @seedgo audit aipass[/green]")
console.print()
+13 -12
View File
@@ -34,6 +34,7 @@ from aipass.seedgo.apps.handlers.json import json_handler
# CLI services (display/output formatting)
from aipass.cli import console, header
from aipass.cli.apps.modules import error as display_error, warning
# Handler (implementation) — readme operations
from aipass.seedgo.apps.handlers.readme.readme_ops import (
@@ -150,7 +151,7 @@ def _handle_update(args: List[str]) -> None:
readme_path = Path(branch_path) / 'README.md'
if not readme_path.exists():
console.print(f" [yellow]No README.md found at {branch_path}[/yellow]")
warning(f"No README.md found at {branch_path}")
continue
try:
@@ -159,7 +160,7 @@ def _handle_update(args: List[str]) -> None:
if result.get('updated'):
total_updated += 1
except Exception as e:
console.print(f" [red]Error: {e}[/red]")
display_error(f"Error: {e}")
logger.error(f"README update failed for {branch_name}: {e}")
if len(branches) > 1:
@@ -197,14 +198,14 @@ def _handle_check(args: List[str]) -> None:
readme_path = Path(branch_path) / 'README.md'
if not readme_path.exists():
console.print(f" [yellow]No README.md found at {branch_path}[/yellow]")
warning(f"No README.md found at {branch_path}")
continue
try:
result = generator.update_readme_auto_sections(branch_path, dry_run=True)
_print_result(result, is_check=True)
except Exception as e:
console.print(f" [red]Error: {e}[/red]")
display_error(f"Error: {e}")
logger.error(f"README check failed for {branch_name}: {e}")
@@ -212,16 +213,16 @@ def _handle_check(args: List[str]) -> None:
# DISPLAY HELPERS
# =============================================================================
def _print_target_error(error: str) -> None:
def _print_target_error(err_code: str) -> None:
"""Display target resolution errors"""
if error == "no_args":
console.print("[yellow]Usage: drone @seedgo readme update @branch[/yellow]")
if err_code == "no_args":
warning("Usage: drone @seedgo readme update @branch")
console.print("[dim]Use @all to update all branches[/dim]")
elif error == "no_branches":
console.print("[red]No branches found in registry[/red]")
elif error.startswith("not_found:"):
target = error.split(":", 1)[1]
console.print(f"[red]Branch '{target}' not found in registry[/red]")
elif err_code == "no_branches":
display_error("No branches found in registry")
elif err_code.startswith("not_found:"):
target = err_code.split(":", 1)[1]
display_error(f"Branch '{target}' not found in registry")
def _print_result(result: dict, is_check: bool = False) -> None:
@@ -87,7 +87,7 @@ def _show_audit_introspection() -> None:
console.print()
if not packs:
console.print("[yellow]No checker packs found.[/yellow]")
warning("No checker packs found.")
console.print("[dim]Add *_check.py files to handlers/*_standards/ directories.[/dim]")
console.print()
return
@@ -31,6 +31,7 @@ from aipass.prax import logger
# CLI services (display/output formatting)
from aipass.cli import console, header
from aipass.cli.apps.modules import warning
# JSON handler for tracking
from aipass.seedgo.apps.handlers.json import json_handler
@@ -137,7 +138,7 @@ def _show_pack_standards(pack_path: Path, pack_name: str) -> None:
console.print()
if not standards:
console.print("[yellow]No content handlers found.[/yellow]")
warning("No content handlers found.")
console.print(f"[dim]Add *_content.py files to handlers/{pack_name}/[/dim]")
console.print()
return
@@ -207,7 +208,7 @@ def handle_command(command: str, args: List[str]) -> bool:
if standard_name not in standards:
console.print(f"[red]Unknown standard:[/red] '{standard_name}'")
console.print()
console.print(f"[yellow]Available standards in {pack_name}:[/yellow]")
warning(f"Available standards in {pack_name}:")
for name in standards:
console.print(f" [cyan]{name}[/cyan]")
console.print()
+4 -8
View File
@@ -36,7 +36,7 @@ signal.signal(signal.SIGPIPE, signal.SIG_DFL)
# Cross-branch imports
from aipass.prax.apps.modules.logger import system_logger as logger
from aipass.cli.apps.modules import console, header
from aipass.cli.apps.modules import console, header, error
# =============================================================================
@@ -302,7 +302,7 @@ def main() -> int:
# Ensure database is ready
if not ensure_database():
console.print("[red]Failed to initialize The Commons database[/red]")
error("Failed to initialize The Commons database")
return 1
# Discover available modules
@@ -327,7 +327,7 @@ def main() -> int:
return 0
if not modules:
console.print("[red]No modules available[/red]")
error("No modules available")
return 1
# Extract command and remaining args
@@ -352,11 +352,7 @@ def main() -> int:
if route_command(command, remaining_args, modules):
return 0
console.print()
console.print(f"[red]Unknown command: {command}[/red]")
console.print()
console.print("[dim]Run 'python3 the_commons.py --help' for available commands[/dim]")
console.print()
error(f"Unknown command: {command}", suggestion="Run 'drone @commons --help' for available commands")
return 1
+4 -3
View File
@@ -23,10 +23,12 @@ from typing import List
from aipass.prax.apps.modules.logger import system_logger as logger
try:
from aipass.cli.apps.modules import console
from aipass.cli.apps.modules import console, error, warning
except ImportError:
from rich.console import Console
console = Console()
error = lambda msg, **kw: console.print(f"[red]{msg}[/red]")
warning = lambda msg, **kw: console.print(f"[yellow]{msg}[/yellow]")
# Re-export all public functions for backward compatibility
from commons.apps.handlers.identity.identity_ops import (
@@ -93,8 +95,7 @@ def _handle_whoami(args: List[str]) -> bool:
branch_info = get_caller_branch()
if not branch_info:
console.print("[yellow]Could not detect your branch identity.[/yellow]")
console.print("[dim]Run from a branch directory containing a *.id.json file.[/dim]")
warning("Could not detect your branch identity. Run from a branch directory.")
return True
name = branch_info.get("name", "unknown")
+6 -4
View File
@@ -20,10 +20,12 @@ from typing import List
from aipass.prax.apps.modules.logger import system_logger as logger
try:
from aipass.cli.apps.modules import console
from aipass.cli.apps.modules import console, error
except ImportError:
from rich.console import Console
console = Console()
def error(msg: str) -> None:
console.print(f"[red]{msg}[/red]")
from rich.panel import Panel
@@ -236,7 +238,7 @@ def _cmd_look(args: List[str]) -> bool:
def _cmd_decorate(args: List[str]) -> bool:
"""Place a decoration in a room."""
if len(args) < 3:
console.print('[red]Usage: commons decorate <room> "item_name" "description"[/red]')
error('Usage: commons decorate <room> "item_name" "description"')
return True
room_name = args[0].lower()
@@ -245,14 +247,14 @@ def _cmd_decorate(args: List[str]) -> bool:
caller = get_caller_branch()
if not caller:
console.print("[red]Could not detect calling branch. Run from a branch directory.[/red]")
error("Could not detect calling branch. Run from a branch directory.")
return True
branch_name = caller["name"]
result = place_decoration(room_name, item_name, description, branch_name)
if result.get("error"):
console.print(f"[red]{result['error']}[/red]")
error(result['error'])
return True
if result["success"]:
+2 -2
View File
@@ -15,7 +15,7 @@ Thin orchestration layer - delegates to creator_handler for logic.
"""
from aipass.prax import logger
from aipass.cli.apps.modules import console
from aipass.cli.apps.modules import console, error
from skills.apps.handlers.creator_handler import create_skill as _handler_create_skill
try:
@@ -36,7 +36,7 @@ def handle_command(command: str, args: list) -> bool:
"""
if command == "create":
if not args:
console.print(" Error: skill name required. Usage: skills create <name> [--with-handler|--full]")
error("Error: skill name required. Usage: skills create <name> [--with-handler|--full]")
return False
name = args[0]
+2 -2
View File
@@ -12,7 +12,7 @@ Thin orchestration layer - delegates to loader_handler for parsing
SKILL.md files and dynamically importing handler modules.
"""
from aipass.cli.apps.modules import console
from aipass.cli.apps.modules import console, warning
from aipass.prax import logger
from skills.apps.modules.discovery import discover_all
from skills.apps.handlers.loader_handler import load_skill as _handler_load_skill
@@ -59,7 +59,7 @@ def load_skill(name):
logger.warning(f"Failed to load skill: {result['error']}")
if result["success"] and result["handler"] is None and result["metadata"].get("has_handler", False):
console.print(f" Warning: has_handler is true but handler.py not found at {result['path']}")
warning(f"Warning: has_handler is true but handler.py not found at {result['path']}")
return result
+3 -3
View File
@@ -13,7 +13,7 @@ skill handlers and assembling markdown output.
"""
from aipass.prax import logger
from aipass.cli.apps.modules import console
from aipass.cli.apps.modules import console, error
from skills.apps.modules.loader import load_skill
from skills.apps.handlers.runner_handler import run_handler, run_markdown
@@ -30,7 +30,7 @@ def handle_command(command: str, args: list) -> bool:
"""
if command == "run":
if not args:
console.print(" Error: skill name required. Usage: skills run <name> [action] [args...]")
error("Error: skill name required. Usage: skills run <name> [action] [args...]")
return False
name = args[0]
@@ -45,7 +45,7 @@ def handle_command(command: str, args: list) -> bool:
console.print(f" {line}")
else:
err = result.get("error", "Unknown error")
console.print(f" Error: {err}")
error(f"Error: {err}")
return result["success"]
+3 -3
View File
@@ -13,7 +13,7 @@ checking skill requirements (pip packages, CLI bins, config/env vars).
"""
from aipass.prax import logger
from aipass.cli.apps.modules import console
from aipass.cli.apps.modules import console, error
from skills.apps.handlers.validator import validate_skill as _handler_validate
@@ -29,7 +29,7 @@ def handle_command(command: str, args: list) -> bool:
"""
if command == "validate":
if not args:
console.print(" Error: skill name required. Usage: skills validate <name>")
error("Error: skill name required. Usage: skills validate <name>")
return False
from skills.apps.modules.loader import load_skill
@@ -37,7 +37,7 @@ def handle_command(command: str, args: list) -> bool:
name = args[0]
loaded = load_skill(name)
if not loaded["success"]:
console.print(f" Error: {loaded['error']}")
error(f"Error: {loaded['error']}")
return False
result = validate_skill(loaded["metadata"])
+9 -9
View File
@@ -7,7 +7,7 @@
# =============================================
from aipass.prax import logger
from aipass.cli.apps.modules import console
from aipass.cli.apps.modules import console, error
"""Skills system entry point.
@@ -61,13 +61,13 @@ def handle_command(command, args=None):
if command == "info":
if not args:
console.print(" Error: skill name required. Usage: skills info <name>")
error("Error: skill name required. Usage: skills info <name>")
return False
return _cmd_info(args[0])
if command == "run":
if not args:
console.print(" Error: skill name required. Usage: skills run <name> [action] [args...]")
error("Error: skill name required. Usage: skills run <name> [action] [args...]")
return False
name = args[0]
action = args[1] if len(args) > 1 else None
@@ -76,13 +76,13 @@ def handle_command(command, args=None):
if command == "create":
if not args:
console.print(" Error: skill name required. Usage: skills create <name> [--with-handler|--full]")
error("Error: skill name required. Usage: skills create <name> [--with-handler|--full]")
return False
return _cmd_create(args)
if command == "validate":
if not args:
console.print(" Error: skill name required. Usage: skills validate <name>")
error("Error: skill name required. Usage: skills validate <name>")
return False
return _cmd_validate(args[0])
@@ -159,7 +159,7 @@ def _cmd_info(name):
loaded = load_skill(name)
if not loaded["success"]:
console.print(f" Error: {loaded['error']}")
error(f"Error: {loaded['error']}")
return False
metadata = loaded["metadata"]
@@ -209,7 +209,7 @@ def _cmd_run(name, action, extra_args):
console.print(f" {line}")
else:
err = result.get("error", "Unknown error")
console.print(f" Error: {err}")
error(f"Error: {err}")
return result["success"]
@@ -230,7 +230,7 @@ def _cmd_create(args):
result = create_skill(name, template_type=template_type)
if not result["success"]:
console.print(f" Error: {result['error']}")
error(f"Error: {result['error']}")
return False
return True
@@ -243,7 +243,7 @@ def _cmd_validate(name):
loaded = load_skill(name)
if not loaded["success"]:
console.print(f" Error: {loaded['error']}")
error(f"Error: {loaded['error']}")
return False
result = validate_skill(loaded["metadata"])