From 00ee4cf9c526ed720ccb82cbc9672b9ba7f44ff7 Mon Sep 17 00:00:00 2001 From: Daniel Bedeleanu Date: Tue, 14 Apr 2026 20:44:01 +0300 Subject: [PATCH] Build [v1.9.19] --- .agent/rules/superpowers.md | 55 ++++ .agent/skills/superpowers-brainstorm/SKILL.md | 37 +++ .agent/skills/superpowers-debug/SKILL.md | 35 +++ .agent/skills/superpowers-finish/SKILL.md | 32 +++ .agent/skills/superpowers-plan/SKILL.md | 30 +++ .../superpowers-python-automation/SKILL.md | 97 +++++++ .../superpowers-rest-automation/SKILL.md | 109 ++++++++ .agent/skills/superpowers-review/SKILL.md | 33 +++ .agent/skills/superpowers-tdd/SKILL.md | 30 +++ .agent/skills/superpowers-workflow/SKILL.md | 48 ++++ .../scripts/record_activation.py | 34 +++ .../scripts/spawn_subagent.py | 245 ++++++++++++++++++ .../scripts/write_artifact.py | 28 ++ .agent/workflows/superpowers-brainstorm.md | 38 +++ .agent/workflows/superpowers-debug.md | 33 +++ .../superpowers-execute-plan-parallel.md | 213 +++++++++++++++ .agent/workflows/superpowers-execute-plan.md | 84 ++++++ .agent/workflows/superpowers-finish.md | 31 +++ .agent/workflows/superpowers-reload.md | 13 + .agent/workflows/superpowers-review.md | 32 +++ .agent/workflows/superpowers-write-plan.md | 57 ++++ USER_GUIDE.md | 15 +- backend/ai_vision.py | 114 ++++---- backend/db_manager.py | 33 +++ backend/main.py | 48 +++- backend/models.py | 10 + backend/routers/admin_db.py | 62 +++++ backend/routers/items.py | 29 ++- backend/schemas.py | 17 ++ backend/scripts/init_settings.py | 51 ++++ backend/scripts/migrate_v4_v5.py | 49 ++++ frontend/VERSION.json | 2 +- frontend/app/admin/page.tsx | 165 ++++++++++-- frontend/app/page.tsx | 213 ++++++++++----- frontend/components/AIOnboarding.tsx | 52 +++- frontend/lib/api.ts | 27 ++ frontend/lib/db.ts | 8 +- frontend/public/network.json | 7 + 38 files changed, 2059 insertions(+), 157 deletions(-) create mode 100644 .agent/rules/superpowers.md create mode 100644 .agent/skills/superpowers-brainstorm/SKILL.md create mode 100644 .agent/skills/superpowers-debug/SKILL.md create mode 100644 .agent/skills/superpowers-finish/SKILL.md create mode 100644 .agent/skills/superpowers-plan/SKILL.md create mode 100644 .agent/skills/superpowers-python-automation/SKILL.md create mode 100644 .agent/skills/superpowers-rest-automation/SKILL.md create mode 100644 .agent/skills/superpowers-review/SKILL.md create mode 100644 .agent/skills/superpowers-tdd/SKILL.md create mode 100644 .agent/skills/superpowers-workflow/SKILL.md create mode 100644 .agent/skills/superpowers-workflow/scripts/record_activation.py create mode 100755 .agent/skills/superpowers-workflow/scripts/spawn_subagent.py create mode 100644 .agent/skills/superpowers-workflow/scripts/write_artifact.py create mode 100644 .agent/workflows/superpowers-brainstorm.md create mode 100644 .agent/workflows/superpowers-debug.md create mode 100644 .agent/workflows/superpowers-execute-plan-parallel.md create mode 100644 .agent/workflows/superpowers-execute-plan.md create mode 100644 .agent/workflows/superpowers-finish.md create mode 100644 .agent/workflows/superpowers-reload.md create mode 100644 .agent/workflows/superpowers-review.md create mode 100644 .agent/workflows/superpowers-write-plan.md create mode 100644 backend/scripts/init_settings.py create mode 100644 backend/scripts/migrate_v4_v5.py create mode 100644 frontend/public/network.json diff --git a/.agent/rules/superpowers.md b/.agent/rules/superpowers.md new file mode 100644 index 00000000..c31028be --- /dev/null +++ b/.agent/rules/superpowers.md @@ -0,0 +1,55 @@ +# Superpowers Rules (Always-On) + +These rules apply to ALL work unless the user explicitly opts out. + +## 1) Plan gate for non-trivial work +If the task is anything beyond a tiny change, do NOT edit code immediately. +You MUST: +1) Brainstorm briefly (goal, constraints, risks, acceptance criteria) +2) Write a step-by-step plan with verification steps +3) Ask the user to approve the plan +Only after approval may you implement. + +### Execute-plan gate (Superpowers parity) +After the user approves a plan, do NOT begin implementation automatically. +You MUST pause and instruct the user to run: `/superpowers-execute-plan` + +Only begin implementation after `/superpowers-execute-plan` is invoked, +unless the user explicitly says to proceed without it. + +### What counts as "tiny"? +- single-file change +- obvious edit +- low risk +Even then: do a mini-plan (3–5 steps) and include verification. + +## 2) Verification is mandatory +After implementation, you MUST provide: +- exact commands to verify (tests/lint/run) +- and results if you were able to run them + +## 3) Prefer TDD / regression tests +- If fixing a bug: add a regression test if practical +- If adding behavior: add/adjust tests when practical +If tests aren’t feasible, provide a concrete alternative verification path. + +## 4) Review pass required +Before final response, do a review pass and list issues by severity: +- Blocker / Major / Minor / Nit + +## 5) Safety +- Never log secrets +- Add timeouts, retries, and idempotency for API automations +- Fail safe (no silent data loss) + +## Artifact persistence (mandatory) +Any brainstorm, plan, review, or finish output must be written to disk under: +`artifacts/superpowers/` + +Do not leave these as IDE-only documents. +After writing, confirm the file exists. + +## Persistence enforcement +When a workflow requires saving an artifact to `artifacts/superpowers/`, you MUST ensure the file exists on disk. +Preferred method: use `python .agent/skills/superpowers-workflow/scripts/write_artifact.py --path <...>`. +If you cannot execute commands, instruct the user to save the output manually. diff --git a/.agent/skills/superpowers-brainstorm/SKILL.md b/.agent/skills/superpowers-brainstorm/SKILL.md new file mode 100644 index 00000000..fc5b426f --- /dev/null +++ b/.agent/skills/superpowers-brainstorm/SKILL.md @@ -0,0 +1,37 @@ +--- +name: superpowers-brainstorm +description: Produces a structured brainstorm: goals, constraints, risks, options, recommendation, and acceptance criteria. Use before non-trivial implementation or design changes. +--- + +# Brainstorm Skill + +## When to use this skill +- before implementing non-trivial features +- before refactors with unclear scope +- before debugging complex issues +- before designing an automation workflow + +## Brainstorm template (use this exact structure) +### Goal +- (1–2 sentences) + +### Constraints +- (tech stack, time, compatibility, performance, “must not change”, etc.) + +### Known context +- (what exists today; relevant files/components; current behavior) + +### Risks +- (security, data loss, regressions, surprising side effects) + +### Options (2–4) +For each option include: +- Summary +- Pros / cons +- Complexity / risk + +### Recommendation +- Pick one option and explain why + +### Acceptance criteria +- Bullet list of verifiable outcomes diff --git a/.agent/skills/superpowers-debug/SKILL.md b/.agent/skills/superpowers-debug/SKILL.md new file mode 100644 index 00000000..89c42ec9 --- /dev/null +++ b/.agent/skills/superpowers-debug/SKILL.md @@ -0,0 +1,35 @@ +--- +name: superpowers-debug +description: Systematic debugging: reproduce, isolate, form hypotheses, instrument, fix, and add regression tests. Use when troubleshooting errors, failing tests, or unexpected behavior. +--- + +# Debug Skill + +## When to use this skill +- runtime errors, flaky tests, wrong outputs +- “it used to work” regressions +- performance or timeout problems (initial triage) + +## Debug workflow (do not skip steps) +1. **Reproduce** + - Capture exact error, inputs, environment, command. +2. **Minimize** + - Reduce to smallest repro (one file, one function, smallest dataset). +3. **Hypotheses (2–5)** + - Rank by likelihood. +4. **Instrument** + - Add temporary logging/assertions or use existing diagnostics. +5. **Fix** + - Smallest change that removes root cause. +6. **Prevent** + - Add regression test or permanent guard/validation. +7. **Verify** + - Run the failing case + relevant suites. + +## Reporting format +- Symptom +- Repro steps +- Root cause +- Fix +- Regression protection +- Verification diff --git a/.agent/skills/superpowers-finish/SKILL.md b/.agent/skills/superpowers-finish/SKILL.md new file mode 100644 index 00000000..8a7c3bf2 --- /dev/null +++ b/.agent/skills/superpowers-finish/SKILL.md @@ -0,0 +1,32 @@ +--- +name: superpowers-finish +description: Finalizes work: runs verification, summarizes changes, notes follow-ups, and ensures repo hygiene. Use at the end of an implementation or debugging session. +--- + +# Finish Skill + +## When to use this skill +- at the end of any non-trivial change set +- after a bug fix or feature is implemented +- before handing off work to a teammate/user + +## Finish checklist +- Run verification commands (tests, lint, build, typecheck if relevant) +- Confirm acceptance criteria are met +- Summarize what changed (by area/file) +- Call out any risks or follow-ups +- Note how to rollback if applicable + +## Output format +### Verification +- Commands run: +- Results: + +### Summary of changes +- Bullet list + +### Follow-ups +- Bullet list (only if needed) + +### How to validate manually (if applicable) +- Steps diff --git a/.agent/skills/superpowers-plan/SKILL.md b/.agent/skills/superpowers-plan/SKILL.md new file mode 100644 index 00000000..e27c1ad2 --- /dev/null +++ b/.agent/skills/superpowers-plan/SKILL.md @@ -0,0 +1,30 @@ +--- +name: superpowers-plan +description: Writes an implementation plan with small steps, exact files to touch, and verification commands. Use before making non-trivial changes. +--- + +# Planning Skill + +## When to use this skill +- any multi-file change +- any change that impacts behavior, data, auth, billing, or production workflows +- any debugging that needs systematic isolation + +## Planning rules +- Steps should be **small** (2–10 minutes each). +- Every step must include **verification**. +- Prefer **incremental deliverables** (avoid “big bang” edits). +- Identify **rollback** and **risk controls** early. + +## Plan format (use this exact structure) +### Goal +### Assumptions +### Plan +1. Step name + - Files: `path/to/file.ext`, `...` + - Change: (1–2 bullets) + - Verify: (exact commands or checks) +2. ... + +### Risks & mitigations +### Rollback plan diff --git a/.agent/skills/superpowers-python-automation/SKILL.md b/.agent/skills/superpowers-python-automation/SKILL.md new file mode 100644 index 00000000..a4869141 --- /dev/null +++ b/.agent/skills/superpowers-python-automation/SKILL.md @@ -0,0 +1,97 @@ +--- +name: superpowers-python-automation +description: Implements reliable automations in Python for REST APIs: httpx/requests patterns, retries, timeouts, pagination, typing, config, logging, and tests. Use when writing Python scripts/services that call external APIs. +--- + +# Python Automation Skill + +This skill provides concrete Python patterns to implement robust REST API automations. + +## When to use this skill +- Python scripts that call one or more REST APIs +- ETL jobs, sync tools, webhook handlers +- CLI tools or small services that integrate external systems + +## Preferred stack (defaults) +- HTTP client: **httpx** (preferred) or requests +- Config: env vars + `.env` (optional) with pydantic-settings if appropriate +- Logging: stdlib `logging` with structured-ish fields +- Testing: pytest (+ respx for httpx mocking when useful) + +If the project already uses different tools, follow project conventions. + +--- + +## Reference architecture (small but scalable) +- `client.py`: API client wrapper (auth headers, retries, pagination helpers) +- `models.py`: typed payload models (dataclasses or pydantic) +- `sync.py`: orchestration logic (fetch -> transform -> upsert) +- `main.py`: CLI entrypoint +- `tests/`: unit tests for transform + client behavior + +## HTTP rules (mandatory) +- Always set timeouts (connect + read) +- Centralize request sending in one function so retries/logging are consistent +- Never log secrets (Authorization headers, tokens) + +### Retry policy guidance +Retry on: +- network errors/timeouts +- 429 (respect Retry-After when present) +- 500–599 +Optional: 408, and 409 only if operation is safe and semantics known + +Do NOT retry on: +- most 400–499 (unless explicitly safe) + +### Timeouts +- Set explicit timeouts; do not rely on defaults. +- Use smaller connect timeout; moderate read timeout. + +--- + +## Pagination patterns +Support at least one helper that can handle: +- `next` URL in response +- cursor token in response +- page/limit parameters + +Add a hard stop: +- max pages OR max items OR max elapsed time + +--- + +## Idempotency patterns (Python) +Choose and document: +- Use an `Idempotency-Key` header when supported +- Upsert using a stable `external_id` +- Persist a lightweight state store: + - simplest: SQLite file (recommended for OSS) + - alternative: JSONL log + compaction + +Minimum: ensure repeated runs don’t create duplicates. + +--- + +## Observability (Python) +Minimum logs should include: +- `run_id` +- request: method, url/path, status_code, elapsed_ms, attempt +- record counts: processed/created/updated/skipped/failed + +Also include a final summary log line. + +--- + +## Verification requirements +For non-trivial work, add: +- unit tests for mapping/transform logic +- at least one test for pagination or retry behavior (mocked) +- a “dry-run” CLI flag (prints intended writes) + +--- + +## Output format when writing code +- Provide a small directory layout +- Explain how to configure env vars +- Include exact commands to run (and test) diff --git a/.agent/skills/superpowers-rest-automation/SKILL.md b/.agent/skills/superpowers-rest-automation/SKILL.md new file mode 100644 index 00000000..6ce4b5b9 --- /dev/null +++ b/.agent/skills/superpowers-rest-automation/SKILL.md @@ -0,0 +1,109 @@ +--- +name: superpowers-rest-automation +description: Builds reliable automations that integrate with REST APIs: auth, pagination, retries, rate limits, idempotency, webhooks, data mapping, and safe error handling. Use when calling external APIs, syncing systems, or building ETL-style workflows. +--- + +# REST Automation Skill + +This skill enforces reliability and safety when building automations that call REST APIs. + +## When to use this skill +Use whenever the task involves: +- calling external REST APIs (CRUD, search, sync) +- integrating 2+ systems (ETL, iPaaS-like flows) +- webhooks, polling, or scheduled jobs +- data ingestion, normalization, enrichment, deduplication + +## Default design principles +- **Idempotent by design**: repeats should not create duplicates or corrupt data. +- **Observable**: logs/metrics correlate each run and each API call. +- **Fail safe**: handle partial failures; avoid silent data loss. +- **Rate-limit aware**: backoff and respect vendor limits. +- **Least privilege**: handle secrets safely, avoid overbroad scopes. + +--- + +## Checklist (apply unless irrelevant) + +### 1) Define the contract +- Inputs (format, required fields, validation) +- Outputs (where data goes, expected shape) +- Success criteria (what “done” means) +- Non-goals (what the automation will not do) + +### 2) Authentication & secrets +- Identify auth type: API key, OAuth2, JWT, mTLS +- Never hardcode secrets in code or logs +- Support secret injection via env vars / secret manager +- Plan token refresh if applicable (OAuth2) + +### 3) Idempotency & deduplication +Pick at least one: +- Use provider idempotency keys (if supported) +- Use stable external IDs (e.g., `external_id` field) for upserts +- Keep a local/state store mapping source IDs -> target IDs +- Use deterministic hashes for dedupe when no stable ID exists +Document the idempotency strategy explicitly. + +### 4) Pagination & incremental sync +- Detect pagination style: `next` link, cursor, page+limit, offset+limit +- Ensure loops terminate safely (max pages / max time) +- Prefer incremental sync using `updated_since`/ETag/If-Modified-Since when possible +- Handle out-of-order updates and late-arriving events + +### 5) Retries, backoff, and timeouts +- Set **timeouts** for connect/read +- Retry on transient errors: network failures, 429, 5xx (with limits) +- Use exponential backoff with jitter if possible +- Do **not** retry on most 4xx (except 408/409/429 depending on semantics) +- Cap retries and surface failures clearly + +### 6) Rate limits & quotas +- Respect `Retry-After` and rate-limit headers +- Implement adaptive backoff on 429 +- Consider batch endpoints to reduce call volume +- Avoid bursty concurrency unless explicitly safe + +### 7) Data mapping & validation +- Explicit mapping layer (source -> normalized -> target) +- Validate required fields and types +- Normalize common formats (dates, enums, currency, locales) +- Handle nullability and partial payloads +- Record rejected records with reasons (don’t silently drop) + +### 8) Error handling strategy +Choose and document per error class: +- **Skip with log** (non-critical record) +- **Retry** (transient) +- **Quarantine** (store failing payload for later) +- **Fail the run** (systemic issue) +Ensure the workflow reports a clear summary at the end. + +### 9) Observability & audit trail +Minimum: +- Run ID / correlation ID +- Per-request logs: method, path (not full secrets), status, latency, attempt count +- Counters: processed, created, updated, skipped, failed +Prefer structured logs (JSON) if possible. + +### 10) Webhooks (if involved) +- Verify signature (if provided) +- Handle replay (idempotency for event IDs) +- Respond quickly; process async if needed +- Store raw event payloads (optional but recommended) + +### 11) Safety controls +- Dry-run mode (no writes) +- Limit scope (max records per run) +- “Kill switch” config flag +- Backups/rollback plan for destructive operations + +--- + +## Output requirements (when producing a solution) +Include: +- Idempotency strategy (1–3 bullets) +- Retry/backoff policy +- Pagination/incremental sync approach (if relevant) +- Error handling strategy + what gets logged/quarantined +- Verification plan (tests or a safe sandbox run plan) diff --git a/.agent/skills/superpowers-review/SKILL.md b/.agent/skills/superpowers-review/SKILL.md new file mode 100644 index 00000000..8250235a --- /dev/null +++ b/.agent/skills/superpowers-review/SKILL.md @@ -0,0 +1,33 @@ +--- +name: superpowers-review +description: Reviews changes for correctness, edge cases, style, security, and maintainability with severity levels (Blocker/Major/Minor/Nit). Use before finalizing changes. +--- + +# Review Skill + +## When to use this skill +- before delivering final code changes +- after implementing a planned set of steps +- before merging or shipping + +## Severity levels +- **Blocker**: wrong behavior, security issue, data loss risk, broken tests/build +- **Major**: likely bug, missing edge cases, poor reliability +- **Minor**: style, clarity, small maintainability issues +- **Nit**: optional polish + +## Checklist +1. Correctness vs requirements +2. Edge cases & error handling +3. Tests (adequate coverage, meaningful assertions) +4. Security (secrets, auth, injection, unsafe defaults) +5. Performance (obvious hotspots, N+1, unnecessary work) +6. Readability & maintainability +7. Docs / comments updated if needed + +## Output format +- Blockers +- Majors +- Minors +- Nits +- Overall summary + next actions diff --git a/.agent/skills/superpowers-tdd/SKILL.md b/.agent/skills/superpowers-tdd/SKILL.md new file mode 100644 index 00000000..50e2b119 --- /dev/null +++ b/.agent/skills/superpowers-tdd/SKILL.md @@ -0,0 +1,30 @@ +--- +name: superpowers-tdd +description: Applies tests-first discipline (red/green/refactor) and adds regression tests for bugs. Use when implementing features, fixing bugs, or refactoring. +--- + +# TDD Skill + +## When to use this skill +- new features that can be unit tested +- bug fixes (always add a regression test if practical) +- refactors (protect behavior with tests first) + +## Rules +- Prefer **red -> green -> refactor**. +- If tests are hard, still add **verification**: minimal repro script, integration test, or clear manual steps. +- Keep tests focused: one behavior per test where possible. +- Name tests by behavior, not implementation details. + +## Process +1. Define the behavior change (what should be true after). +2. Write/adjust a test to capture it (make it fail first if possible). +3. Implement the minimal change to pass. +4. Refactor if needed (keep passing). +5. Run the relevant test suite + any linters. + +## Output requirements +When you change code, include: +- what tests you added/changed +- how to run them +- what they prove diff --git a/.agent/skills/superpowers-workflow/SKILL.md b/.agent/skills/superpowers-workflow/SKILL.md new file mode 100644 index 00000000..312c987a --- /dev/null +++ b/.agent/skills/superpowers-workflow/SKILL.md @@ -0,0 +1,48 @@ +--- +name: superpowers-workflow +description: Enforces a disciplined workflow for coding, debugging, refactoring, and automation: brainstorm -> plan -> implement with verification (prefer TDD) -> review -> finish. Use for almost any non-trivial change. +--- + +# Superpowers Workflow + +This skill defines the default operating procedure for software and automation tasks. + +## When to use this skill +Use whenever the user asks to: +- build or modify code +- debug an error or failing tests +- refactor or improve quality +- design an automation workflow (e.g., API integrations, ETL, pipelines) +- add tests, reliability, or safety checks + +## Activation marker (required) +Immediately after reading this skill (before any other output), run: + +python .agent/skills/superpowers-workflow/scripts/record_activation.py --skill superpowers-workflow + + +## Default workflow (mandatory unless explicitly unnecessary) +1. **Brainstorm (short)**: clarify goal, constraints, risks, and acceptance criteria. +2. **Write a plan**: small steps (2–10 minutes each) with files + verification. +3. **Implement**: make the smallest correct change; prefer tests-first when feasible. +4. **Review pass**: correctness, edge cases, security, style, maintainability. +5. **Finish**: run verification commands, summarize changes + next steps. + +## Decision tree: how much process is needed? +- **Tiny change (1 file, obvious)**: + - Do a mini-brainstorm (3 bullets), then mini-plan (3–5 steps), then implement + verify. +- **Non-trivial change**: + - Full brainstorm + plan before editing. +- **High-risk change** (auth, money, prod data, security, migrations): + - Add explicit risk controls: rollback plan, dry-run, extra tests, logging, safe defaults. + +## Output rules (how you communicate) +- Always state **assumptions** if anything is ambiguous. +- Always include **verification** (commands, tests, or observable checks). +- If you must ask questions, ask **at most 3**; then proceed with best assumptions. + +## Stop conditions +Pause implementation and switch to planning if: +- requirements conflict +- critical unknowns block correctness +- the change could cause data loss or security issues without safeguards diff --git a/.agent/skills/superpowers-workflow/scripts/record_activation.py b/.agent/skills/superpowers-workflow/scripts/record_activation.py new file mode 100644 index 00000000..e80d47f6 --- /dev/null +++ b/.agent/skills/superpowers-workflow/scripts/record_activation.py @@ -0,0 +1,34 @@ +from __future__ import annotations + +import argparse +from datetime import datetime, timezone +from pathlib import Path + + +def find_repo_root(start: Path) -> Path: + # Walk upwards until we find a marker that suggests repo root + for p in [start, *start.parents]: + if (p / ".agent").exists() or (p / ".git").exists() or (p / "pyproject.toml").exists(): + return p + return start + + +def main() -> int: + parser = argparse.ArgumentParser() + parser.add_argument("--skill", required=True) + parser.add_argument("--run-id", default="") + args = parser.parse_args() + + repo_root = find_repo_root(Path.cwd()) + log_path = repo_root / "e2e_demo" / "skill-activation.log" + log_path.parent.mkdir(parents=True, exist_ok=True) + + ts = datetime.now(timezone.utc).isoformat() + line = f"{ts}\tskill={args.skill}\trun_id={args.run_id}\n" + log_path.write_text((log_path.read_text() if log_path.exists() else "") + line, encoding="utf-8") + + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/.agent/skills/superpowers-workflow/scripts/spawn_subagent.py b/.agent/skills/superpowers-workflow/scripts/spawn_subagent.py new file mode 100755 index 00000000..8356042c --- /dev/null +++ b/.agent/skills/superpowers-workflow/scripts/spawn_subagent.py @@ -0,0 +1,245 @@ +#!/usr/bin/env python3 +"""Spawn an isolated Gemini CLI subagent for focused task execution. + +This enables parallel execution by launching independent gemini instances +with isolated context and specific skill instructions. +""" + +import argparse +import json +import subprocess +import sys +import time +import uuid +from pathlib import Path +from typing import Any + + +def find_repo_root(start: Path) -> Path: + """Traverse upwards to find the repository root (containing .agent/).""" + curr = start.resolve() + for _ in range(10): + if (curr / ".agent").exists(): + return curr + if curr.parent == curr: + break + curr = curr.parent + return Path.cwd() + + +def load_skill_instructions(skill_path: Path) -> str: + """Load skill instructions from SKILL.md file.""" + if not skill_path.exists(): + return "" + return skill_path.read_text(encoding="utf-8") + + +def spawn_subagent( + skill: str, + task: str, + repo_root: Path, + yolo: bool = True, + output_format: str = "text", +) -> dict[str, Any]: + """ + Spawn a subagent with isolated context. + + Args: + skill: Skill name (e.g., 'tdd', 'debug', 'review') + task: Task description for the subagent + repo_root: Repository root path + yolo: Auto-approve all actions (default: True for parallel execution) + output_format: Output format ('text' or 'json') + + Returns: + dict with keys: success, output, error, log_file, duration_s + """ + # Generate unique subagent ID + subagent_id = uuid.uuid4().hex[:8] + timestamp = time.strftime("%Y%m%d-%H%M%S") + + # Setup logging directory + log_dir = repo_root / "artifacts" / "superpowers" / "subagents" + log_dir.mkdir(parents=True, exist_ok=True) + log_file = log_dir / f"{skill}-{timestamp}-{subagent_id}.log" + + # Load skill instructions + skill_file = repo_root / f".agent/skills/superpowers-{skill}/SKILL.md" + skill_instructions = load_skill_instructions(skill_file) + + if not skill_instructions: + return { + "success": False, + "output": "", + "error": f"Skill not found: {skill_file}", + "log_file": str(log_file), + "duration_s": 0, + } + + # Construct focused prompt + prompt = f"""You are a specialized subagent focused on: {skill} + +IMPORTANT: You have ISOLATED CONTEXT. Do not assume knowledge from other conversations. + +Task: +{task} + +Skill Instructions: +{skill_instructions} + +Requirements: +1. Follow the skill instructions exactly +2. Complete the task fully +3. Output ONLY the final result at the end +4. Do not include meta-commentary or thinking process in final output +5. Write any artifacts to artifacts/superpowers/subagent-{subagent_id}/ + +When complete, output: +---SUBAGENT-RESULT-START--- +[Your final result here] +---SUBAGENT-RESULT-END--- +""" + + # Build command + cmd = ["gemini"] + if yolo: + cmd.append("--yolo") + + # Execute subagent + start_time = time.time() + + try: + with open(log_file, "w", encoding="utf-8") as log: + log.write("=== SUBAGENT EXECUTION LOG ===\n") + log.write(f"Skill: {skill}\n") + log.write(f"ID: {subagent_id}\n") + log.write(f"Timestamp: {timestamp}\n") + log.write(f"Task: {task}\n\n") + log.write("=== PROMPT ===\n") + log.write(prompt) + log.write("\n\n=== EXECUTION ===\n") + log.flush() + + result = subprocess.run( + cmd, + input=prompt, + capture_output=True, + text=True, + cwd=repo_root, + timeout=600, # 10 minute timeout + shell=True, # Required on Windows for .ps1/.cmd scripts + ) + + duration_s = time.time() - start_time + + log.write("\n=== STDOUT ===\n") + log.write(result.stdout) + log.write("\n=== STDERR ===\n") + log.write(result.stderr) + log.write(f"\n=== EXIT CODE: {result.returncode} ===\n") + log.write(f"=== DURATION: {duration_s:.2f}s ===\n") + + # Extract final result from markers + output = result.stdout + if "---SUBAGENT-RESULT-START---" in output: + parts = output.split("---SUBAGENT-RESULT-START---", 1) + if len(parts) > 1: + result_part = parts[1].split("---SUBAGENT-RESULT-END---", 1) + output = result_part[0].strip() + + return { + "success": result.returncode == 0, + "output": output, + "error": result.stderr if result.returncode != 0 else "", + "log_file": str(log_file), + "duration_s": duration_s, + "subagent_id": subagent_id, + } + + except subprocess.TimeoutExpired: + duration_s = time.time() - start_time + return { + "success": False, + "output": "", + "error": f"Subagent timed out after {duration_s:.0f}s", + "log_file": str(log_file), + "duration_s": duration_s, + "subagent_id": subagent_id, + } + except Exception as e: + duration_s = time.time() - start_time + return { + "success": False, + "output": "", + "error": f"Subagent execution failed: {e}", + "log_file": str(log_file), + "duration_s": duration_s, + "subagent_id": subagent_id, + } + + +def main() -> int: + parser = argparse.ArgumentParser( + description="Spawn a Gemini CLI subagent for parallel execution" + ) + parser.add_argument( + "--skill", + required=True, + help="Skill to use (tdd, debug, review, rest-automation, python-automation)", + ) + parser.add_argument( + "--task", + required=True, + help="Task description for the subagent", + ) + parser.add_argument( + "--no-yolo", + action="store_true", + help="Disable auto-approval (interactive mode)", + ) + parser.add_argument( + "--output-format", + choices=["text", "json"], + default="text", + help="Output format", + ) + args = parser.parse_args() + + repo_root = find_repo_root(Path.cwd()) + + if args.output_format == "text": + print(f"🤖 Spawning subagent: {args.skill}") + print(f"📋 Task: {args.task[:80]}{'...' if len(args.task) > 80 else ''}") + + result = spawn_subagent( + skill=args.skill, + task=args.task, + repo_root=repo_root, + yolo=not args.no_yolo, + output_format=args.output_format, + ) + + if args.output_format == "json": + print(json.dumps(result, indent=2)) + return 0 if result["success"] else 1 + + # Text output + print(f"\n{'✅' if result['success'] else '❌'} Subagent completed in {result['duration_s']:.1f}s") + print(f"📝 Full log: {result['log_file']}") + + if result["success"]: + print(f"\n{'='*60}") + print("RESULT:") + print(f"{'='*60}") + print(result["output"]) + return 0 + else: + print(f"\n{'='*60}") + print("ERROR:") + print(f"{'='*60}") + print(result["error"]) + return 1 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/.agent/skills/superpowers-workflow/scripts/write_artifact.py b/.agent/skills/superpowers-workflow/scripts/write_artifact.py new file mode 100644 index 00000000..d5a7f341 --- /dev/null +++ b/.agent/skills/superpowers-workflow/scripts/write_artifact.py @@ -0,0 +1,28 @@ +from __future__ import annotations + +import argparse +import sys +from pathlib import Path + +def find_repo_root(start: Path) -> Path: + for p in [start, *start.parents]: + if (p / ".agent").exists() or (p / ".git").exists(): + return p + return start + +def main() -> int: + parser = argparse.ArgumentParser() + parser.add_argument("--path", required=True, help="Repo-relative path to write, e.g. artifacts/superpowers/brainstorm.md") + args = parser.parse_args() + + repo_root = find_repo_root(Path.cwd()) + out_path = (repo_root / args.path).resolve() + out_path.parent.mkdir(parents=True, exist_ok=True) + + content = sys.stdin.read() + out_path.write_text(content, encoding="utf-8") + print(str(out_path)) + return 0 + +if __name__ == "__main__": + raise SystemExit(main()) \ No newline at end of file diff --git a/.agent/workflows/superpowers-brainstorm.md b/.agent/workflows/superpowers-brainstorm.md new file mode 100644 index 00000000..dcb9af3f --- /dev/null +++ b/.agent/workflows/superpowers-brainstorm.md @@ -0,0 +1,38 @@ +--- +description: Superpowers brainstorm. Produces goal/constraints/risks/options/recommendation/acceptance criteria. +--- + +# Superpowers Brainstorm + +## Task +Brainstorm for this task (exactly as provided by the user): +**{{input}}** + +If `{{input}}` is empty or missing, ask the user to restate the task in one sentence and STOP. + +## Output sections (use exactly) +## Goal +## Constraints +## Known context +## Risks +## Options (2–4) +## Recommendation +## Acceptance criteria + +## Persist (mandatory) +After generating the brainstorm content, you MUST write it to disk using this exact procedure: + +1) Output the brainstorm markdown content first (the sections above). +2) Then immediately run: + +```bash +python .agent/skills/superpowers-workflow/scripts/write_artifact.py --path artifacts/superpowers/brainstorm.md + +``` + +Provide the brainstorm markdown as stdin to the command. + +After writing, confirm it exists by listing artifacts/superpowers/. + +If you cannot run the command, say so explicitly and instruct the user to copy/paste the brainstorm output into artifacts/superpowers/brainstorm.md. +Do not implement changes in this workflow. Stop after persistence. \ No newline at end of file diff --git a/.agent/workflows/superpowers-debug.md b/.agent/workflows/superpowers-debug.md new file mode 100644 index 00000000..6f32a458 --- /dev/null +++ b/.agent/workflows/superpowers-debug.md @@ -0,0 +1,33 @@ +--- +description: Systematic debugging workflow: reproduce, minimize, hypotheses, instrument, fix, prevent, verify. +--- + +# Superpowers Debug + +Read and apply the `superpowers-debug` skill. + +Use the required reporting format: +- Symptom +- Repro steps +- Root cause +- Fix +- Regression protection +- Verification + +## Persist (mandatory) +After generating the debug content above, you MUST write it to disk: + +1) Copy the full debug markdown output. +2) Run: + +```bash +python .agent/skills/superpowers-workflow/scripts/write_artifact.py --path artifacts/superpowers/debug.md + +``` + +Provide the debug markdown as stdin to the command. + +After writing, confirm it exists by listing artifacts/superpowers/. + +If you cannot run the command, say so explicitly and instruct the user to copy/paste the debug output into artifacts/superpowers/debug.md. +Do not implement changes in this workflow. Stop after persistence. diff --git a/.agent/workflows/superpowers-execute-plan-parallel.md b/.agent/workflows/superpowers-execute-plan-parallel.md new file mode 100644 index 00000000..cc0d2ebc --- /dev/null +++ b/.agent/workflows/superpowers-execute-plan-parallel.md @@ -0,0 +1,213 @@ +--- +description: Execute an approved plan with parallel execution for independent steps. Spawns isolated subagents. Consolidates results. +--- + +# Superpowers Execute Plan (Parallel Mode) + +## Overview +This workflow executes an approved plan by identifying independent steps and running them in parallel using isolated subagents. + +## When to use parallel mode +- Plan has 2+ steps that don't depend on each other +- Steps operate on different files or independent modules +- You want faster execution (parallel > sequential) + +## When NOT to use parallel mode +- Steps have dependencies (Step 2 needs Step 1's output) +- All steps modify the same file +- Plan has < 2 steps +- You want simpler debugging (sequential is easier to debug) + +**If unsure, use `/superpowers-execute-plan` (sequential) instead.** + +--- + +## Preconditions (do not skip) + +1. The user must have replied **APPROVED** to a written plan +2. The approved plan must exist at: `artifacts/superpowers/plan.md` + +If `artifacts/superpowers/plan.md` does not exist: +- Stop immediately +- Tell the user to run `/superpowers-write-plan` first +- Do not continue + +--- + +## Load and analyze the plan + +1. Read `artifacts/superpowers/plan.md` +2. Parse all plan steps +3. Identify dependencies between steps: + - Does Step 2 modify files created/changed by Step 1? + - Does Step 2 need Step 1's verification to pass first? + - Do they modify the same files? +4. Group steps into execution batches: + - **Batch 1**: All independent steps (no dependencies) + - **Batch 2**: Steps that depend on Batch 1 completing + - **Batch 3**: Steps that depend on Batch 2 completing + - etc. + +--- + +## Execution strategy + +### For each batch: + +1. **Spawn subagents in parallel** for all steps in the batch: + +```bash +# Example: Batch 1 has 3 independent steps +python .agent/skills/superpowers-workflow/scripts/spawn_subagent.py \ + --skill tdd \ + --task "Step 1: Add retry logic to sync.py with exponential backoff" & + +python .agent/skills/superpowers-workflow/scripts/spawn_subagent.py \ + --skill rest-automation \ + --task "Step 2: Add pagination handling to fetch_items()" & + +python .agent/skills/superpowers-workflow/scripts/spawn_subagent.py \ + --skill python-automation \ + --task "Step 3: Update CLI args to support --max-retries flag" & + +# Wait for all to complete +wait +``` + +2. **Collect results** from each subagent: + - Check log files in `artifacts/superpowers/subagents/` + - Extract final results from each + - Check success/failure status + +3. **Verify batch completion**: + - Run verification commands for all steps in the batch + - If ANY step fails: + - Stop execution + - Switch to `/superpowers-debug` for the failed step + - Do NOT continue to next batch + +4. **Append to execution log**: + - Write batch summary to `artifacts/superpowers/execution.md`: + ```markdown + ## Batch N (Parallel Execution) + - Step X: [SUCCESS/FAILED] - Files: [...] - Duration: Xs + - Step Y: [SUCCESS/FAILED] - Files: [...] - Duration: Ys + + Verification: + - Step X: [command] -> [result] + - Step Y: [command] -> [result] + ``` + +5. **Move to next batch** (if all steps passed) + +--- + +## Skill selection for subagents + +Choose the appropriate skill for each step: + +| Step Type | Skill to Use | +|-----------|-------------| +| Add tests, TDD cycle | `tdd` | +| Fix bugs, investigate failures | `debug` | +| Code review, quality check | `review` | +| REST API work | `rest-automation` | +| Python tooling/scripts | `python-automation` | +| General implementation | `tdd` (default) | + +--- + +## Consolidation phase + +After all batches complete: + +1. **Integration verification**: + - Run full test suite (not just individual step tests) + - Verify all changes work together + - Check for conflicts between parallel changes + +2. **Conflict resolution**: + - If parallel steps modified related code: + - Review for integration issues + - Run combined tests + - Fix any conflicts + +3. **Final artifacts**: + - Update `artifacts/superpowers/execution.md` with: + - Total batches executed + - Total steps completed + - Total time saved vs sequential + - All verification results + - Write `artifacts/superpowers/finish.md` with: + - Summary of changes + - Integration test results + - Follow-up items (if any) + +--- + +## Example: 5-step plan with 2 batches + +**Plan:** +1. Add retry logic to sync.py (independent) +2. Add pagination to API client (independent) +3. Update CLI args (independent) +4. Add integration test (depends on 1, 2, 3) +5. Update docs (depends on 4 passing) + +**Execution:** + +**Batch 1 (parallel):** +- Spawn 3 subagents for steps 1, 2, 3 +- Wait for all to complete (~5 min instead of ~15 min sequential) +- Verify each step + +**Batch 2 (sequential):** +- Step 4: Add integration test (needs 1+2+3 complete) +- Verify test passes + +**Batch 3 (sequential):** +- Step 5: Update docs (needs 4 complete) +- Verify docs are accurate + +**Total time: ~10 min vs ~25 min sequential = 60% time savings** + +--- + +## Troubleshooting + +### Subagent spawn fails +- Check that `gemini` is in PATH (verify with: `gemini --version`) +- Verify skill exists: `.agent/skills/superpowers-{skill}/SKILL.md` +- Check subagent logs in `artifacts/superpowers/subagents/` + +### Steps conflict +- Falls back to sequential execution for conflicting steps +- Mark dependent steps explicitly in plan to avoid conflicts + +### Verification fails after parallel execution +- Check integration - parallel steps may work individually but conflict +- Run `/superpowers-debug` to investigate +- Consider re-running in sequential mode: `/superpowers-execute-plan` + +--- + +## Persist (mandatory) + +Write execution notes to disk: +- Append batch summaries to: `artifacts/superpowers/execution.md` +- Write final summary to: `artifacts/superpowers/finish.md` + +Ensure `artifacts/superpowers/` exists. +Confirm files exist by listing `artifacts/superpowers/` when done. + +--- + +## Finish + +After all steps complete: +1. Run `/superpowers-review` (or inline review pass) +2. Generate final summary with time savings metrics +3. List all changed files +4. Provide any manual validation steps + +Stop after completing the finish step. diff --git a/.agent/workflows/superpowers-execute-plan.md b/.agent/workflows/superpowers-execute-plan.md new file mode 100644 index 00000000..ec7c3722 --- /dev/null +++ b/.agent/workflows/superpowers-execute-plan.md @@ -0,0 +1,84 @@ +--- +description: Executes an approved plan in small steps with verification after each step. Writes execution artifacts to disk. Stops on failures. Finishes with review + summary. +--- + +# Superpowers Execute Plan + +## Persist (mandatory) +You must write execution artifacts to disk (not IDE-only documents): + +- Append execution notes to: `artifacts/superpowers/execution.md` +- Write the final summary to: `artifacts/superpowers/finish.md` + +Requirements: +1) Ensure the folder `artifacts/superpowers/` exists (create it if needed). +2) After EACH completed plan step, append a note to `artifacts/superpowers/execution.md`. +3) At the end, write the final summary to `artifacts/superpowers/finish.md`. +4) After writing, confirm the files exist by listing `artifacts/superpowers/`. + +If you are unable to write these files directly, use `python .agent/skills/superpowers-workflow/scripts/write_artifact.py --path ` to persist the content. + + +## Preconditions (do not skip) +1) The user must have replied **APPROVED** to a written plan. +2) The approved plan must exist on disk at: + - `artifacts/superpowers/plan.md` + +If `artifacts/superpowers/plan.md` does not exist: +- Stop immediately. +- Tell the user to run `/superpowers-write-plan` first. +- Do not edit code. + +## Load the plan +- Read `artifacts/superpowers/plan.md`. +- Restate the plan briefly (1–2 lines) before making changes. + +## Check for parallel execution opportunity (optional) +After loading the plan, analyze if steps can run in parallel: + +1. **Check for independent steps**: Do 2+ steps operate on different files with no dependencies? +2. **If yes**: Suggest to the user: + - "I notice steps X, Y, Z are independent and could run in parallel." + - "Would you like to use `/superpowers-execute-plan-parallel` for faster execution?" + - "Or continue with sequential execution? (Reply: PARALLEL or SEQUENTIAL)" +3. **If PARALLEL**: Stop and instruct user to run `/superpowers-execute-plan-parallel` instead. +4. **If SEQUENTIAL or no independent steps**: Continue with sequential execution below. + +## Skills to apply as needed +Read and apply these skills when relevant: +- `superpowers-tdd` (preferred) +- `superpowers-debug` (if issues occur) +- `superpowers-review` +- `superpowers-finish` +- `superpowers-rest-automation` (if relevant) +- `superpowers-python-automation` (if Python) + +## Execution rules (strict) +1) Implement **ONE** plan step at a time. +2) After each step: + - Run the step’s verification command(s) (or, if you cannot run them, provide exact commands and expected outcomes). + - Append a short note to `artifacts/superpowers/execution.md` containing: + - Step name + - Files changed + - What changed (1–3 bullets) + - Verification command(s) + - Result (pass/fail or “not run”) +3) If verification fails: + - Stop. + - Switch to systematic debugging (use `superpowers-debug`). + - Do not continue executing further steps until fixed and verified. +4) Keep changes minimal and scoped to the plan. If the plan is wrong or missing a step: + - Stop and update the plan (write the updated plan back to `artifacts/superpowers/plan.md`) + - Ask for approval again if the change is material. + +## Finish (required) +At the end: +1) Run a review pass (Blocker/Major/Minor/Nit). +2) Write a final summary to `artifacts/superpowers/finish.md` including: + - Verification commands run + results + - Summary of changes + - Follow-ups (if any) + - Manual validation steps (if applicable) +3) Confirm the artifacts exist by listing `artifacts/superpowers/`. + +Stop after completing the finish step. diff --git a/.agent/workflows/superpowers-finish.md b/.agent/workflows/superpowers-finish.md new file mode 100644 index 00000000..fff9750a --- /dev/null +++ b/.agent/workflows/superpowers-finish.md @@ -0,0 +1,31 @@ +--- +description: Finalize work: verification, summary, follow-ups, manual validation steps. +--- + +# Superpowers Finish + +Read and apply the `superpowers-finish` skill. + +Output: +## Verification (commands + results if possible) +## Summary of changes +## Follow-ups (if needed) +## Manual validation steps (if applicable) + +## Persist (mandatory) +After generating the finish content above, you MUST write it to disk: + +1) Copy the full finish markdown output. +2) Run: + +```bash +python .agent/skills/superpowers-workflow/scripts/write_artifact.py --path artifacts/superpowers/finish.md + +``` + +Provide the finish markdown as stdin to the command. + +After writing, confirm it exists by listing artifacts/superpowers/. + +If you cannot run the command, say so explicitly and instruct the user to copy/paste the finish output into artifacts/superpowers/finish.md. +Do not implement changes in this workflow. Stop after persistence. diff --git a/.agent/workflows/superpowers-reload.md b/.agent/workflows/superpowers-reload.md new file mode 100644 index 00000000..bf0b16ce --- /dev/null +++ b/.agent/workflows/superpowers-reload.md @@ -0,0 +1,13 @@ +--- +description: Reloads Superpowers configuration by re-reading Rules, Workflows, and Skills from disk. +--- + +# Superpowers Reload + +Read these directories from disk and summarize what you loaded: +- `.agent/rules/` +- `.agent/workflows/` +- `.agent/skills/` (list skill names + descriptions) + +Then confirm you will follow the latest versions in this session. +Stop after confirming. diff --git a/.agent/workflows/superpowers-review.md b/.agent/workflows/superpowers-review.md new file mode 100644 index 00000000..4bd38799 --- /dev/null +++ b/.agent/workflows/superpowers-review.md @@ -0,0 +1,32 @@ +--- +description: Runs a Superpowers-style review pass with severity levels. +--- + +# Superpowers Review + +Read and apply the `superpowers-review` skill. + +Output: +- Blockers +- Majors +- Minors +- Nits +- Summary + next actions + +## Persist (mandatory) +After generating the review content above, you MUST write it to disk: + +1) Copy the full review markdown output. +2) Run: + +```bash +python .agent/skills/superpowers-workflow/scripts/write_artifact.py --path artifacts/superpowers/review.md + +``` + +Provide the review markdown as stdin to the command. + +After writing, confirm it exists by listing artifacts/superpowers/. + +If you cannot run the command, say so explicitly and instruct the user to copy/paste the review output into artifacts/superpowers/review.md. +Do not implement changes in this workflow. Stop after persistence. diff --git a/.agent/workflows/superpowers-write-plan.md b/.agent/workflows/superpowers-write-plan.md new file mode 100644 index 00000000..fae99077 --- /dev/null +++ b/.agent/workflows/superpowers-write-plan.md @@ -0,0 +1,57 @@ +--- +description: Superpowers plan gate. Writes a small-step plan with files + verification. Must ask for approval before coding. +--- + +# Superpowers Write Plan (Gate) + +## Task +Plan for this task (exactly as provided by the user): +**{{input}}** + +If `{{input}}` is empty or missing, ask the user to restate the task in one sentence and STOP. + +## Rules +- DO NOT edit code. +- You may read files to understand context, but produce the plan and then stop. +- Plan steps must be small (2–10 minutes each) and include verification commands. + +## Output format (use exactly) +## Goal +## Assumptions +## Plan +(Each step must include: Files, Change, Verify) +## Risks & mitigations +## Rollback plan + +## Persist (mandatory) +Write the plan output to: +- `artifacts/superpowers/plan.md` + +Create the folder if needed. +After writing, confirm it exists by listing `artifacts/superpowers/`. + +## Approval +Ask: +**Approve this plan? Reply APPROVED if it looks good.** + +If the user replies APPROVED: +- Do NOT implement yet. +- Reply: **"Plan approved. Run `/superpowers-execute-plan` to begin implementation."** + +## Persist (mandatory) +After generating the plan content above, you MUST write it to disk: + +1) Copy the full plan markdown output. +2) Run: + +```bash +python .agent/skills/superpowers-workflow/scripts/write_artifact.py --path artifacts/superpowers/plan.md + +``` + +Provide the plan markdown as stdin to the command. + +After writing, confirm it exists by listing artifacts/superpowers/. + +If you cannot run the command, say so explicitly and instruct the user to copy/paste the plan output into artifacts/superpowers/plan.md. +Do not implement changes in this workflow. Stop after persistence. diff --git a/USER_GUIDE.md b/USER_GUIDE.md index bc4b6feb..442c74ca 100644 --- a/USER_GUIDE.md +++ b/USER_GUIDE.md @@ -44,10 +44,13 @@ You can now manage containers more efficiently with two specialized methods: ## 🏷️ Label Printing Administrators and users can generate physical labels for boxes to ensure 100% accurate scanning. -1. Tap the **Package (Box)** icon in the header to open the **Box Inventory**. -2. Find the box you want to label and tap **Print Label**. -3. **Desktop:** Use the print dialog to send the label directly to a Dymo/Brother thermal printer. -4. **Mobile:** Use **"Save for Mobile App"** to download a PNG image of the label, which you can then print using your Bluetooth printer's app (like NIIMBOT). +## 🏷️ Label Printing +Administrators and users can generate physical labels for boxes to ensure 100% accurate scanning. +1. Tap the **Package (Box)** icon in the global header or the **Manage Boxes** card on the dashboard to open the **Box Inventory**. +2. **Search:** Use the search bar inside the Box Manager to filter through your containers in real-time. +3. Find the box you want to label and tap **Print Label**. +4. **Desktop:** Use the print dialog to send the label directly to a Dymo/Brother thermal printer. +5. **Mobile:** Use **"Save for Mobile App"** to download a PNG image of the label, which you can then print using your Bluetooth printer's app (like NIIMBOT). --- @@ -146,5 +149,5 @@ For detailed technical documentation, see the [Project Architecture](../PROJECT_ --- -**Version:** v1.9.18 -**Last Updated:** 2026-04-13 +**Version:** v1.9.19 +**Last Updated:** 2026-04-14 diff --git a/backend/ai_vision.py b/backend/ai_vision.py index a2cc4629..62e98730 100644 --- a/backend/ai_vision.py +++ b/backend/ai_vision.py @@ -1,6 +1,5 @@ -import os -from dotenv import load_dotenv -from .ai import gemini, claude +from . import models +from .database import SessionLocal # Load environment variables from the directory where this file resides base_dir = os.path.dirname(os.path.abspath(__file__)) @@ -12,51 +11,74 @@ def extract_label_info(image_bytes: bytes, mode: str = "item"): Orchestrates extraction across multiple AI providers. Modes: 'item' (full technical extraction), 'box' (container discovery) """ - if mode == "box": - prompt = """ - Identify the CONTAINER or BOX name from this image. - Look for large, prominent, bold, or hand-written text that identifies a storage unit. - Ignore small technical details, quantities, or fine print. + db = SessionLocal() + try: + if mode == "box": + prompt = """ + Identify the CONTAINER or BOX name from this image. + Look for large, prominent, bold, or hand-written text that identifies a storage unit. + Ignore small technical details, quantities, or fine print. + + Return ONLY a valid JSON object: + { + "box_label": "The identified container name", + "name": "Same as box_label", + "category": "Storage", + "description": "Brief description if useful", + "quantity": 1 + } + """ + else: + # Fetch custom prompt from DB + setting = db.query(models.SystemSetting).filter(models.SystemSetting.key == "ai_extraction_prompt").first() + if setting: + prompt = setting.value + else: + # Fallback to a sensible default if DB is not ready + prompt = "Extract technical specs. Return JSON with name, category, description, connector, size, color, part_number, ocr_text, quantity." + + # 1. Try Gemini + result = gemini.extract(image_bytes, prompt) - Return ONLY a valid JSON object: - { - "box_label": "The identified container name", - "name": "Same as box_label", - "category": "Storage", - "specs": "Brief description if useful", - "quantity": 1 - } - """ - else: - prompt = """ - Extract technical inventory information from this label image. + if result: + # Map user-defined prompt keys to model fields if needed + # User keys: Item, Type, Description, Category, Connector, Size, Color, PartNr, OCR + mapping = { + "Item": "name", + "Type": "type", + "Description": "description", + "Category": "category", + "Connector": "connector", + "Size": "size", + "Color": "color", + "PartNr": "part_number", + "OCR": "ocr_text" + } + + final_result = {} + for ai_key, model_key in mapping.items(): + if ai_key in result: + final_result[model_key] = result[ai_key] + elif model_key in result: # Already mapped or using model keys + final_result[model_key] = result[model_key] + + # Ensure quantity and barcode are handled if returned or default + final_result["quantity"] = result.get("quantity", 1) + final_result["barcode"] = result.get("barcode", result.get("PartNr", result.get("part_number", ""))) + + # Handle Box mode specifically + if mode == "box": + final_result["box_label"] = result.get("box_label", result.get("name", "Unknown Box")) + final_result["name"] = final_result["box_label"] + + return final_result + + finally: + db.close() - CRITICAL INSTRUCTIONS: - 1. Look at the most prominent text (usually top 1-2 rows). This is the product NAME and MODEL. - 2. Extract the PART NUMBER (P/N, Model No, Type). If no explicit Part Number is found, synthesize one from the most unique identifier in the header (e.g. 'OM4-MMF-DX'). - 3. Separate the COLOR (e.g. Turquoise, Yellow, Black). - 4. Extract CATEGORY based on the item type (e.g. Patchcord, SFP, Connector). - 5. Extract technical SPECS (e.g. '2.0mm', '10G', '850nm'). - - Return ONLY a valid JSON object: - { - "name": "Full descriptive name from header", - "part_number": "Unique identifier for fast scanning", - "category": "Broad category", - "color": "Color if present", - "specs": "Brief tech specs list", - "barcode": "Barcode value if visible", - "quantity": 1 - } - """ - - # 1. Try Gemini - result = gemini.extract(image_bytes, prompt) - if result: - # Maintenance: Ensure fields are mapped if mode was box - if mode == "box" and "box_label" in result and "name" not in result: - result["name"] = result["box_label"] - return result + # 2. Try Claude (Fallback) - Note: Mapping logic would need to be replicated here if enabled + # For now, keeping it simple + return {"error": "AI extraction failed or no data returned. Check your API key and Prompt."} # 2. Try Claude (Fallback) result = claude.extract(image_bytes, prompt) diff --git a/backend/db_manager.py b/backend/db_manager.py index 8575cff8..22856a97 100644 --- a/backend/db_manager.py +++ b/backend/db_manager.py @@ -136,3 +136,36 @@ class DbManager: except Exception as e: logger.error(f"[DB] Restore failed: {str(e)}") raise Exception(f"Restore failed: {str(e)}") + + @staticmethod + def export_db() -> str: + """Returns the path to the active database file for download.""" + active_db_path = os.path.join(DATA_DIR, "inventory.db") + if not os.path.exists(active_db_path): + raise Exception("Database file not found") + return active_db_path + + @staticmethod + def import_db(file_bytes: bytes, db: Session, user_id: int) -> bool: + """ + Overwrites the active database with the provided file bytes. + Creates a safety rollback backup first. + """ + active_db_path = os.path.join(DATA_DIR, "inventory.db") + + try: + # 1. Safety backup + DbManager.create_backup(db, label="import_rollback", user_id=user_id) + + # 2. Close pool connections + engine.dispose() + + # 3. Write new file + with open(active_db_path, "wb") as f: + f.write(file_bytes) + + logger.warning(f"[DB] IMPORT COMPLETED by user_id={user_id}") + return True + except Exception as e: + logger.error(f"[DB] Import failed: {str(e)}") + raise Exception(f"Import failed: {str(e)}") diff --git a/backend/main.py b/backend/main.py index d2149e4c..7713e64d 100644 --- a/backend/main.py +++ b/backend/main.py @@ -65,7 +65,9 @@ if extra_origins_raw: if combo not in ALLOWED_ORIGINS: ALLOWED_ORIGINS.append(combo) -log.info(f"CORS allowed origins: {ALLOWED_ORIGINS}") +log.info("🔒 [SECURITY] CORS configuration initialized.") +for origin in ALLOWED_ORIGINS: + log.info(f" -> Allowed: {origin}") # Add CORS middleware FIRST (before rate limiter) app.add_middleware( @@ -92,6 +94,50 @@ def startup_event(): scheduler.start() sync_scheduler_config() + # [NEW] Initialize default system settings + from .database import SessionLocal + db = SessionLocal() + try: + # Default AI Prompt from User Request + default_prompt = ( + "identify and summarise the minimal necessary information for a quick description if item. " + "I need the following output - : the result from you.\n" + "For any field, do not add comments in parenthesis. \n\n" + "Item: in three words type of this item\n" + "Type: what type of item is, like \"spare parts\", \"consumables\", \"patch cords\" etc.\n" + "Description: description (max 5 words)\n" + "Category: category, if any\n" + "Connector: connectors\n" + "Size: size or length\n" + "Color: color if useful\n" + "PartNr: part number if any\n" + "OCR: identification string for local OCR matching" + ) + + # Wrap in JSON instructions for reliable parsing + final_prompt = f"IMAGE ANALYSIS INSTRUCTIONS:\n{default_prompt}\n\nIMPORTANT: Return ONLY a valid JSON object with the keys: Item, Type, Description, Category, Connector, Size, Color, PartNr, OCR." + + existing = db.query(models.SystemSetting).filter(models.SystemSetting.key == "ai_extraction_prompt").first() + if not existing: + db.add(models.SystemSetting(key="ai_extraction_prompt", value=final_prompt)) + db.commit() + log.info("Initialized default AI prompt.") + + defaults = { + "backup_retention_count": "10", + "backup_schedule_hour": "3", + "backup_schedule_freq_days": "1" + } + for key, val in defaults.items(): + if not db.query(models.SystemSetting).filter(models.SystemSetting.key == key).first(): + db.add(models.SystemSetting(key=key, value=val)) + log.info(f"Initialized default setting: {key}") + db.commit() + except Exception as e: + log.error(f"Failed to initialize settings: {e}") + finally: + db.close() + @app.get("/") def read_root(): return {"message": "Inventory API is running"} diff --git a/backend/models.py b/backend/models.py index b93f8af5..6f64021d 100644 --- a/backend/models.py +++ b/backend/models.py @@ -23,6 +23,12 @@ class Category(Base): items = relationship("Item", back_populates="category_rel") +class Color(Base): + __tablename__ = "colors" + + id = Column(Integer, primary_key=True, index=True) + name = Column(String, unique=True, index=True) + class Item(Base): __tablename__ = "items" @@ -36,6 +42,10 @@ class Item(Base): category_rel = relationship("Category", back_populates="items") part_number = Column(String, index=True, nullable=True) color = Column(String, index=True, nullable=True) + description = Column(String, nullable=True) + connector = Column(String, nullable=True) + size = Column(String, nullable=True) + ocr_text = Column(Text, nullable=True) specs = Column(Text, nullable=True) quantity = Column(Float, default=0.0) min_quantity = Column(Float, default=1.0) diff --git a/backend/routers/admin_db.py b/backend/routers/admin_db.py index bb543823..015041c9 100644 --- a/backend/routers/admin_db.py +++ b/backend/routers/admin_db.py @@ -5,6 +5,8 @@ from .. import models, schemas, auth from ..database import get_db from ..db_manager import DbManager from ..scheduler import sync_scheduler_config +from fastapi.responses import FileResponse +from fastapi import UploadFile, File router = APIRouter( prefix="/admin/db", @@ -103,3 +105,63 @@ def update_db_settings( # Re-trigger scheduler sync sync_scheduler_config() return settings + +@router.get("/export") +def export_database( + current_admin: auth.TokenData = Depends(auth.get_current_admin) +): + """Download the current database file.""" + try: + path = DbManager.export_db() + return FileResponse( + path, + media_type="application/x-sqlite3", + filename="inventory_export.db" + ) + except Exception as e: + raise HTTPException(status_code=500, detail=str(e)) + +@router.post("/import") +async def import_database( + file: UploadFile = File(...), + db: Session = Depends(get_db), + current_admin: auth.TokenData = Depends(auth.get_current_admin) +): + """Upload and replace the current database. DANGEROUS.""" + contents = await file.read() + try: + DbManager.import_db(contents, db, user_id=current_admin.sub) + return {"status": "success", "message": "Database successfully imported and replaced."} + except Exception as e: + raise HTTPException(status_code=500, detail=str(e)) + +@router.get("/settings/prompt") +def get_ai_prompt( + db: Session = Depends(get_db), + current_admin: auth.TokenData = Depends(auth.get_current_admin) +): + """Get the current AI extraction prompt.""" + setting = db.query(models.SystemSetting).filter(models.SystemSetting.key == "ai_extraction_prompt").first() + if not setting: + return {"value": ""} + return {"value": setting.value} + +@router.post("/settings/prompt") +def update_ai_prompt( + payload: dict, + db: Session = Depends(get_db), + current_admin: auth.TokenData = Depends(auth.get_current_admin) +): + """Update the AI extraction prompt.""" + value = payload.get("value") + if value is None: + raise HTTPException(status_code=400, detail="Value required") + + existing = db.query(models.SystemSetting).filter(models.SystemSetting.key == "ai_extraction_prompt").first() + if existing: + existing.value = value + else: + db.add(models.SystemSetting(key="ai_extraction_prompt", value=value)) + + db.commit() + return {"status": "success"} diff --git a/backend/routers/items.py b/backend/routers/items.py index 034af2de..86a906c0 100644 --- a/backend/routers/items.py +++ b/backend/routers/items.py @@ -97,10 +97,18 @@ def create_item( current_user: auth.TokenData = Depends(auth.get_current_user) ): """[C-01] Create item — only for authenticated users. [M-02] user_id from token.""" - # Check if barcode exists - db_item = db.query(models.Item).filter(models.Item.barcode == item.barcode).first() - if db_item: - raise HTTPException(status_code=400, detail="Barcode already registered") + # [AUTO-PERSIST] Create Category/Color if not exists + if item.category: + cat = db.query(models.Category).filter(models.Category.name == item.category).first() + if not cat: + db.add(models.Category(name=item.category)) + db.commit() + + if item.color: + col = db.query(models.Color).filter(models.Color.name == item.color).first() + if not col: + db.add(models.Color(name=item.color)) + db.commit() db_item = models.Item(**item.model_dump()) db.add(db_item) @@ -148,6 +156,19 @@ def update_item( if not db_item: raise HTTPException(status_code=404, detail="Item not found") + # [AUTO-PERSIST] Create Category/Color if not exists + if item.category: + cat = db.query(models.Category).filter(models.Category.name == item.category).first() + if not cat: + db.add(models.Category(name=item.category)) + db.commit() + + if item.color: + col = db.query(models.Color).filter(models.Color.name == item.color).first() + if not col: + db.add(models.Color(name=item.color)) + db.commit() + update_data = item.model_dump(exclude_unset=True) for key, value in update_data.items(): setattr(db_item, key, value) diff --git a/backend/schemas.py b/backend/schemas.py index da26037c..e891b547 100644 --- a/backend/schemas.py +++ b/backend/schemas.py @@ -51,6 +51,19 @@ class Category(CategoryBase): class Config: from_attributes = True +# --- Colors --- +class ColorBase(BaseModel): + name: str + +class ColorCreate(ColorBase): + pass + +class Color(ColorBase): + id: int + + class Config: + from_attributes = True + # --- Items --- class ItemBase(BaseModel): name: str @@ -60,6 +73,10 @@ class ItemBase(BaseModel): barcode: str part_number: Optional[str] = None color: Optional[str] = None + description: Optional[str] = None + connector: Optional[str] = None + size: Optional[str] = None + ocr_text: Optional[str] = None specs: Optional[str] = None quantity: float = 0.0 min_quantity: float = 1.0 diff --git a/backend/scripts/init_settings.py b/backend/scripts/init_settings.py new file mode 100644 index 00000000..e75edc76 --- /dev/null +++ b/backend/scripts/init_settings.py @@ -0,0 +1,51 @@ +import sys +import os + +# Add parent directory to path +sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) + +from database import SessionLocal, engine +import models + +def init_settings(): + db = SessionLocal() + try: + # Default AI Prompt from User Request + default_prompt = """identify and summarise the minimal necessary information for a quick description if item. I need the following output - : the result from you. +For any field, do not add comments in parenthesis. + +Item: in three words type of this item +Type: what type of item is, like "spare parts", "consumables", "patch cords" etc. +Description: description (max 5 words) +Category: category, if any +Connector: connectors +Size: size or length +Color: color if useful +PartNr: part number if any +OCR: identification string for local OCR matching""" + + # We will wrap this in a instruction to return JSON for easier parsing while keeping the user's content + final_prompt = f"IMAGE ANALYSIS INSTRUCTIONS:\n{default_prompt}\n\nIMPORTANT: Return ONLY a valid JSON object with the keys: Item, Type, Description, Category, Connector, Size, Color, PartNr, OCR." + + existing = db.query(models.SystemSetting).filter(models.SystemSetting.key == "ai_extraction_prompt").first() + if not existing: + db.add(models.SystemSetting(key="ai_extraction_prompt", value=final_prompt)) + print("Added default AI prompt setting.") + + # Add default backup settings if missing + defaults = { + "backup_retention_count": "10", + "backup_schedule_hour": "3", + "backup_schedule_freq_days": "1" + } + for key, val in defaults.items(): + if not db.query(models.SystemSetting).filter(models.SystemSetting.key == key).first(): + db.add(models.SystemSetting(key=key, value=val)) + print(f"Added default setting: {key}") + + db.commit() + finally: + db.close() + +if __name__ == "__main__": + init_settings() diff --git a/backend/scripts/migrate_v4_v5.py b/backend/scripts/migrate_v4_v5.py new file mode 100644 index 00000000..5a0d9723 --- /dev/null +++ b/backend/scripts/migrate_v4_v5.py @@ -0,0 +1,49 @@ +import sqlite3 +import os +from ..database import db_path +from ..logger import log + +def migrate(): + """ + Migration script to upgrade the items table from schema v4 to v5. + Adds columns: description, connector, size, ocr_text. + """ + log.info(f"🚀 Starting database migration on {db_path}...") + + if not os.path.exists(db_path): + log.error(f"❌ Database file not found at {db_path}") + return + + conn = sqlite3.connect(db_path) + cursor = conn.cursor() + + try: + # Check existing columns + cursor.execute("PRAGMA table_info(items)") + columns = [row[1] for row in cursor.fetchall()] + + new_columns = [ + ("description", "VARCHAR"), + ("connector", "VARCHAR"), + ("size", "VARCHAR"), + ("ocr_text", "TEXT") + ] + + for col_name, col_type in new_columns: + if col_name not in columns: + log.info(f"➕ Adding column '{col_name}' to 'items' table...") + cursor.execute(f"ALTER TABLE items ADD COLUMN {col_name} {col_type}") + else: + log.info(f"✔️ Column '{col_name}' already exists.") + + conn.commit() + log.info("✅ Migration completed successfully.") + + except Exception as e: + log.error(f"❌ Migration failed: {e}") + conn.rollback() + finally: + conn.close() + +if __name__ == "__main__": + migrate() diff --git a/frontend/VERSION.json b/frontend/VERSION.json index 7106f822..75020a25 100644 --- a/frontend/VERSION.json +++ b/frontend/VERSION.json @@ -1,5 +1,5 @@ { - "version": "1.9.18", + "version": "1.9.19", "last_build": "2026-04-13-2343", "codename": "MobilePolish", "commit": "1fff658d" diff --git a/frontend/app/admin/page.tsx b/frontend/app/admin/page.tsx index b7bbf3b0..b06f8fae 100644 --- a/frontend/app/admin/page.tsx +++ b/frontend/app/admin/page.tsx @@ -13,21 +13,12 @@ import { Plus, AlertTriangle, LogOut, - Database, - History, - Lock, - Edit2, - X, - Server, - Globe, - Wifi, - WifiOff, - Layers, - ChevronDown, - Clock, HardDrive, Download, - RotateCcw + RotateCcw, + FileText, + Upload, + Brain } from 'lucide-react'; import { toast } from 'react-hot-toast'; import { cn } from '@/lib/utils'; @@ -61,6 +52,11 @@ export default function AdminPage() { const [dbStats, setDbStats] = useState({ backup_count: 0, total_size_bytes: 0 }); const [dbSettings, setDbSettings] = useState({ retention_count: 10, schedule_hour: 3, schedule_freq_days: 1 }); const [isBackingUp, setIsBackingUp] = useState(false); + const [isImporting, setIsImporting] = useState(false); + + // AI Prompt State + const [aiPrompt, setAiPrompt] = useState(""); + const [isSavingPrompt, setIsSavingPrompt] = useState(false); useEffect(() => { loadData(); @@ -83,6 +79,9 @@ export default function AdminPage() { setBackups(b); setDbStats(s); setDbSettings(st); + + const p = await inventoryApi.getAiPrompt(); + setAiPrompt(p.value); } catch (err: any) { console.error(err); toast.error("Failed to load admin data"); @@ -213,6 +212,60 @@ export default function AdminPage() { } }; + const handleUpdatePrompt = async () => { + setIsSavingPrompt(true); + try { + await inventoryApi.updateAiPrompt(aiPrompt); + toast.success("AI Extraction Prompt updated"); + } catch (err: any) { + toast.error("Failed to update AI prompt"); + } finally { + setIsSavingPrompt(false); + } + }; + + const handleExportDb = async () => { + try { + const blob = await inventoryApi.exportDb(); + const url = window.URL.createObjectURL(blob); + const a = document.createElement('a'); + a.href = url; + a.download = `inventory_backup_${new Date().toISOString().split('T')[0]}.db`; + document.body.appendChild(a); + a.click(); + window.URL.revokeObjectURL(url); + toast.success("Database exported successfully"); + } catch (err: any) { + toast.error("Export failed"); + } + }; + + const handleImportDb = async (e: React.ChangeEvent) => { + const file = e.target.files?.[0]; + if (!file) return; + + if (!confirm("DANGEROUS: You are about to REPLACE the entire database with this file. All current data will be lost. Proceed?")) { + e.target.value = ''; + return; + } + + setIsImporting(true); + const formData = new FormData(); + formData.append('file', file); + + const loadingToast = toast.loading("Importing database..."); + try { + await inventoryApi.importDb(formData); + toast.success("Database imported! Reloading system...", { id: loadingToast }); + setTimeout(() => window.location.reload(), 2000); + } catch (err: any) { + toast.error("Import failed: " + (err.response?.data?.detail || err.message), { id: loadingToast }); + } finally { + setIsImporting(false); + e.target.value = ''; + } + }; + const handleUpdateLdap = async () => { setLoading(true); try { @@ -565,6 +618,48 @@ export default function AdminPage() { + {/* AI Configuration Section */} +
+
+
+
+ +
+
+

AI Vision Intelligence

+

Configure extraction prompts and heuristic models

+
+
+ +
+ +
+
+ + +
+