Compare commits
53 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6102ff39aa | ||
|
|
10d75619bf | ||
|
|
546eca6b9a | ||
|
|
66844a56e9 | ||
|
|
e8c804da71 | ||
|
|
45db169dda | ||
|
|
f47e7d005a | ||
|
|
adb6cde87a | ||
|
|
460dc4fe12 | ||
|
|
4df2d844ca | ||
|
|
409afb29f8 | ||
|
|
bacc23cc23 | ||
|
|
a6af90ec48 | ||
|
|
29b5c7020c | ||
|
|
70670a3c9d | ||
|
|
65b24079cb | ||
|
|
6c57b1b0c2 | ||
|
|
1ea5e90bc4 | ||
|
|
00ee4cf9c5 | ||
|
|
fcb187974e | ||
|
|
1fff658d3c | ||
|
|
826b264a70 | ||
|
|
94f1a515b7 | ||
|
|
4dc0ce50e7 | ||
|
|
ee7e7b7bd7 | ||
|
|
9d7e4f0ca3 | ||
|
|
bdf6d605cd | ||
|
|
a2f6cab492 | ||
|
|
476fda7203 | ||
|
|
9348336709 | ||
|
|
e5194a1dbb | ||
|
|
7c3d0e102f | ||
|
|
a0eaddf994 | ||
|
|
30be967887 | ||
|
|
2b8d0b3f43 | ||
|
|
07b15c8e01 | ||
|
|
65cc0c7b6d | ||
|
|
f137ded5aa | ||
|
|
946f1787c7 | ||
|
|
09be0b401d | ||
|
|
3069a921e7 | ||
|
|
55e3cf5042 | ||
|
|
c874d27d64 | ||
|
|
939ad8648d | ||
|
|
6f6caf3c5a | ||
|
|
5e648002f0 | ||
|
|
81b775c9ae | ||
|
|
994920bda2 | ||
|
|
0a6368b9f6 | ||
|
|
26e8f034a2 | ||
|
|
2e5c666cc8 | ||
|
|
e383b97d44 | ||
|
|
50ae3671c9 |
55
.agent/rules/superpowers.md
Normal file
@@ -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.
|
||||
37
.agent/skills/superpowers-brainstorm/SKILL.md
Normal file
@@ -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
|
||||
35
.agent/skills/superpowers-debug/SKILL.md
Normal file
@@ -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
|
||||
32
.agent/skills/superpowers-finish/SKILL.md
Normal file
@@ -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
|
||||
30
.agent/skills/superpowers-plan/SKILL.md
Normal file
@@ -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
|
||||
97
.agent/skills/superpowers-python-automation/SKILL.md
Normal file
@@ -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)
|
||||
109
.agent/skills/superpowers-rest-automation/SKILL.md
Normal file
@@ -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)
|
||||
33
.agent/skills/superpowers-review/SKILL.md
Normal file
@@ -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
|
||||
30
.agent/skills/superpowers-tdd/SKILL.md
Normal file
@@ -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
|
||||
48
.agent/skills/superpowers-workflow/SKILL.md
Normal file
@@ -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
|
||||
@@ -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())
|
||||
245
.agent/skills/superpowers-workflow/scripts/spawn_subagent.py
Executable file
@@ -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())
|
||||
28
.agent/skills/superpowers-workflow/scripts/write_artifact.py
Normal file
@@ -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())
|
||||
38
.agent/workflows/superpowers-brainstorm.md
Normal file
@@ -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.
|
||||
33
.agent/workflows/superpowers-debug.md
Normal file
@@ -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.
|
||||
213
.agent/workflows/superpowers-execute-plan-parallel.md
Normal file
@@ -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.
|
||||
84
.agent/workflows/superpowers-execute-plan.md
Normal file
@@ -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 <target>` 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.
|
||||
31
.agent/workflows/superpowers-finish.md
Normal file
@@ -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.
|
||||
13
.agent/workflows/superpowers-reload.md
Normal file
@@ -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.
|
||||
32
.agent/workflows/superpowers-review.md
Normal file
@@ -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.
|
||||
57
.agent/workflows/superpowers-write-plan.md
Normal file
@@ -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.
|
||||
43
.agents/rules/api-testing-postman-rest-asured.md
Normal file
@@ -0,0 +1,43 @@
|
||||
---
|
||||
trigger: manual
|
||||
description: You are an expert in API Testing using tools like Postman and REST Assured.
|
||||
---
|
||||
|
||||
# API Testing (Postman, REST Assured)
|
||||
|
||||
You are an expert in API Testing using tools like Postman and REST Assured.
|
||||
|
||||
Key Principles:
|
||||
- Test the business logic layer directly
|
||||
- Faster and more stable than UI tests
|
||||
- Validate request/response contracts
|
||||
- Check status codes, headers, and body
|
||||
- Ensure security and performance
|
||||
|
||||
Postman:
|
||||
- Collections and Folders
|
||||
- Environment and Global variables
|
||||
- Pre-request scripts and Tests (JavaScript)
|
||||
- Newman CLI for CI/CD integration
|
||||
- Mock Servers
|
||||
|
||||
REST Assured (Java):
|
||||
- Fluent BDD-like syntax (Given-When-Then)
|
||||
- Easy integration with JUnit/TestNG
|
||||
- JSON/XML Schema validation
|
||||
- Request/Response logging
|
||||
- Authentication support (OAuth, Basic)
|
||||
|
||||
What to Test:
|
||||
- Status Codes (200, 201, 400, 401, 403, 404, 500)
|
||||
- Response Payload (JSON structure and data)
|
||||
- Headers (Content-Type, Cache-Control)
|
||||
- Performance (Response time)
|
||||
- Security (Auth, Rate limiting)
|
||||
|
||||
Best Practices:
|
||||
- Chain requests (Extract token -> Use token)
|
||||
- Use JSON Schema validation
|
||||
- Data-driven testing (CSV/JSON files)
|
||||
- Clean up created resources
|
||||
- Run API tests in CI pipeline
|
||||
74
.agents/rules/modern-css-and-esponsive-design-expert.md
Normal file
@@ -0,0 +1,74 @@
|
||||
---
|
||||
trigger: manual
|
||||
description: You are an expert in modern CSS and responsive web design.
|
||||
---
|
||||
|
||||
# Modern CSS & Responsive Design Expert
|
||||
|
||||
You are an expert in modern CSS and responsive web design.
|
||||
|
||||
Key Principles:
|
||||
- Use mobile-first approach
|
||||
- Implement responsive design with CSS Grid and Flexbox
|
||||
- Use CSS custom properties (variables)
|
||||
- Follow BEM or similar naming convention
|
||||
- Write maintainable and scalable CSS
|
||||
|
||||
Layout:
|
||||
- Use CSS Grid for two-dimensional layouts
|
||||
- Use Flexbox for one-dimensional layouts
|
||||
- Use CSS Grid auto-fit and auto-fill
|
||||
- Implement proper spacing with gap property
|
||||
- Use logical properties (inline, block)
|
||||
|
||||
Responsive Design:
|
||||
- Use mobile-first media queries
|
||||
- Use relative units (rem, em, %)
|
||||
- Implement fluid typography with clamp()
|
||||
- Use container queries when appropriate
|
||||
- Test on multiple devices and screen sizes
|
||||
|
||||
Modern CSS Features:
|
||||
- Use CSS custom properties for theming
|
||||
- Use CSS Grid and Flexbox
|
||||
- Use aspect-ratio for maintaining proportions
|
||||
- Use clamp() for fluid sizing
|
||||
- Use min(), max() for responsive values
|
||||
- Use :is(), :where() for cleaner selectors
|
||||
|
||||
Animations:
|
||||
- Use CSS transitions for simple animations
|
||||
- Use CSS animations for complex sequences
|
||||
- Use transform for better performance
|
||||
- Respect prefers-reduced-motion
|
||||
- Use will-change sparingly
|
||||
|
||||
Performance:
|
||||
- Minimize CSS file size
|
||||
- Remove unused CSS
|
||||
- Use CSS containment
|
||||
- Avoid expensive selectors
|
||||
- Use CSS Grid/Flexbox over floats
|
||||
- Minimize repaints and reflows
|
||||
|
||||
Architecture:
|
||||
- Use BEM or similar methodology
|
||||
- Organize CSS logically
|
||||
- Use CSS custom properties for consistency
|
||||
- Implement design tokens
|
||||
- Use utility classes sparingly
|
||||
|
||||
Accessibility:
|
||||
- Ensure sufficient color contrast
|
||||
- Use focus-visible for focus styles
|
||||
- Don't rely on color alone
|
||||
- Test with high contrast mode
|
||||
- Ensure text is readable
|
||||
|
||||
Best Practices:
|
||||
- Use CSS reset or normalize
|
||||
- Implement consistent spacing scale
|
||||
- Use semantic class names
|
||||
- Avoid !important
|
||||
- Comment complex CSS
|
||||
- Use CSS linting tools
|
||||
88
.agents/rules/progressive-web-app-pwa-expert.md
Normal file
@@ -0,0 +1,88 @@
|
||||
---
|
||||
trigger: manual
|
||||
description: Progressive Web App (PWA) Expert
|
||||
---
|
||||
|
||||
# Progressive Web App (PWA) Expert
|
||||
|
||||
You are an expert in Progressive Web App development.
|
||||
|
||||
Key Principles:
|
||||
- Implement offline-first strategy
|
||||
- Use service workers for caching
|
||||
- Make app installable
|
||||
- Ensure fast loading
|
||||
- Provide app-like experience
|
||||
|
||||
Service Workers:
|
||||
- Implement proper caching strategies
|
||||
- Use Cache API effectively
|
||||
- Handle offline scenarios
|
||||
- Implement background sync
|
||||
- Use workbox for easier implementation
|
||||
- Handle service worker updates
|
||||
|
||||
Manifest:
|
||||
- Create comprehensive web app manifest
|
||||
- Define app icons for all sizes
|
||||
- Set appropriate display mode
|
||||
- Define theme and background colors
|
||||
- Set start URL and scope
|
||||
- Add screenshots for app stores
|
||||
|
||||
Caching Strategies:
|
||||
- Use cache-first for static assets
|
||||
- Use network-first for dynamic content
|
||||
- Implement stale-while-revalidate
|
||||
- Use cache-only for offline pages
|
||||
- Implement proper cache versioning
|
||||
|
||||
Offline Experience:
|
||||
- Provide offline fallback page
|
||||
- Cache critical resources
|
||||
- Implement background sync
|
||||
- Show offline indicator
|
||||
- Queue failed requests
|
||||
|
||||
Performance:
|
||||
- Implement lazy loading
|
||||
- Use code splitting
|
||||
- Optimize images
|
||||
- Minimize JavaScript
|
||||
- Use HTTP/2 push
|
||||
- Implement resource hints
|
||||
|
||||
Installability:
|
||||
- Meet PWA criteria
|
||||
- Implement beforeinstallprompt
|
||||
- Provide install UI
|
||||
- Test installation flow
|
||||
- Handle app updates
|
||||
|
||||
Push Notifications:
|
||||
- Implement push notification API
|
||||
- Request permission appropriately
|
||||
- Handle notification clicks
|
||||
- Implement notification best practices
|
||||
- Test on multiple platforms
|
||||
|
||||
Security:
|
||||
- Serve over HTTPS
|
||||
- Implement CSP headers
|
||||
- Validate all inputs
|
||||
- Use secure authentication
|
||||
- Implement proper CORS
|
||||
|
||||
Testing:
|
||||
- Use Lighthouse for audits
|
||||
- Test offline functionality
|
||||
- Test on multiple devices
|
||||
- Test installation flow
|
||||
- Test push notifications
|
||||
|
||||
Best Practices:
|
||||
- Follow PWA checklist
|
||||
- Implement progressive enhancement
|
||||
- Provide app shell architecture
|
||||
- Use PRPL pattern
|
||||
- Monitor performance metrics
|
||||
44
.agents/rules/security-and-penetration-testing.md
Normal file
@@ -0,0 +1,44 @@
|
||||
---
|
||||
trigger: manual
|
||||
description: You are an expert in Security Testing and Penetration Testing.
|
||||
---
|
||||
|
||||
# Security & Penetration Testing
|
||||
|
||||
You are an expert in Security Testing and Penetration Testing.
|
||||
|
||||
Key Principles:
|
||||
- Think like an attacker
|
||||
- Defense in Depth
|
||||
- Shift Left (Security early in SDLC)
|
||||
- Validate controls and mitigations
|
||||
- Compliance and Risk Management
|
||||
|
||||
OWASP Top 10 (Focus Areas):
|
||||
- Broken Access Control
|
||||
- Cryptographic Failures
|
||||
- Injection (SQLi, XSS)
|
||||
- Insecure Design
|
||||
- Security Misconfiguration
|
||||
|
||||
Testing Types:
|
||||
- SAST (Static Application Security Testing): Code analysis (SonarQube)
|
||||
- DAST (Dynamic Application Security Testing): Runtime analysis (OWASP ZAP, Burp Suite)
|
||||
- SCA (Software Composition Analysis): Dependency checks (Snyk, Dependabot)
|
||||
- Penetration Testing: Manual exploitation
|
||||
|
||||
Tools:
|
||||
- Burp Suite: Proxy and scanner
|
||||
- OWASP ZAP: Open source scanner
|
||||
- Metasploit: Exploitation framework
|
||||
- Nmap: Network scanning
|
||||
- Wireshark: Packet analysis
|
||||
|
||||
Best Practices:
|
||||
- Sanitize all inputs
|
||||
- Encode all outputs
|
||||
- Use parameterized queries
|
||||
- Implement proper authentication/authorization
|
||||
- Keep dependencies updated
|
||||
- Conduct regular vulnerability scans
|
||||
- Perform manual code reviews for security logic
|
||||
@@ -19,7 +19,21 @@
|
||||
"Bash(sqlite3 data/inventory.db \".schema users\")",
|
||||
"Bash(sqlite3 data/inventory.db \"SELECT * FROM users;\")",
|
||||
"Bash(git restore:*)",
|
||||
"Bash(pkill -f \"uvicorn\")"
|
||||
"Bash(pkill -f \"uvicorn\")",
|
||||
"Bash(docker-compose build:*)",
|
||||
"Bash(docker version:*)",
|
||||
"Bash(docker compose version:*)",
|
||||
"Bash(docker compose:*)",
|
||||
"Bash(open -a Docker)",
|
||||
"Bash(dockerd)",
|
||||
"Bash(brew services:*)",
|
||||
"Bash(colima start:*)",
|
||||
"Bash(colima status:*)",
|
||||
"Bash(colima stop:*)",
|
||||
"Bash(bash *)",
|
||||
"Bash(npm run *)",
|
||||
"Bash(npm test *)",
|
||||
"Bash(python3 *)"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
3
.gitignore
vendored
@@ -38,7 +38,10 @@ backend/config/ldap_config.json
|
||||
# ── Environment files (secrets) ──────────────────────────────
|
||||
.env
|
||||
.env.*
|
||||
inventory.env
|
||||
inventory.env.*
|
||||
!.env.example
|
||||
!inventory.env.example
|
||||
backend/.env
|
||||
backend/.env.*
|
||||
!backend/.env.example
|
||||
|
||||
27
AGENTS.md
Normal file
@@ -0,0 +1,27 @@
|
||||
# AGENTS.md — Web App Project Rules
|
||||
|
||||
## Tech Stack
|
||||
- Frontend: Next.js 15, React 19, TypeScript 5
|
||||
- Styling: Tailwind CSS v3.4, Lucide React (Icons)
|
||||
- Backend: Python 3.14, FastAPI, Uvicorn
|
||||
- Database: SQLite (SQLAlchemy) + Dexie (IndexedDB pentru offline-first)
|
||||
- AI & Vision: Google Gemini 2.0 (Vision), Tesseract.js (Local OCR)
|
||||
- Infrastructure: Docker, Caddy (Automatic SSL Reverse Proxy)
|
||||
- Security: JWT (python-jose), LDAP (Enterprise Integration)
|
||||
|
||||
## Code Quality
|
||||
- Keep cyclomatic complexity under 10 per function
|
||||
- Aim for files under 300 lines
|
||||
|
||||
## Testing
|
||||
- Write unit tests for all utility functions
|
||||
- Minimum 80% coverage on new code
|
||||
|
||||
## Security
|
||||
- Store all secrets in inventory.env — never hardcode credentials
|
||||
- Never log API keys, tokens or passwords
|
||||
- Validate all user input before processing
|
||||
|
||||
## Git Conventions
|
||||
- Use conventional commits (feat:, fix:, docs:, etc.)
|
||||
- Keep PRs under 400 lines of diff when possible
|
||||
122
AI_RULES.md
@@ -1,81 +1,61 @@
|
||||
# AI AGENT RULES - MANDATORY ENTRY POINT
|
||||
# AI AGENT RULES - MANDATORY SSOT ENTRY POINT
|
||||
|
||||
**READ THIS ENTIRE FILE BEFORE EXECUTING ANY TASK.**
|
||||
This is the **Single Source of Truth** for ALL Artificial Intelligence agents (Claude, Gemini, etc.) working on this project.
|
||||
(Automatic IDE entry points like `GEMINI.md` and `CLAUDE.md` exist solely to redirect agents to this main file).
|
||||
For technical architecture, data models, and stack details, refer to [PROJECT_ARCHITECTURE.md](PROJECT_ARCHITECTURE.md).
|
||||
This is the **Single Source of Truth** for ALL AI agents. Refer to [PROJECT_ARCHITECTURE.md](PROJECT_ARCHITECTURE.md) for technical logic.
|
||||
|
||||
## 1. Multi-AI Coordination & Memory
|
||||
- **MANDATORY STARTUP**: Every AI session MUST first read `dev_docs/SESSION_STATE.md` to understand current context.
|
||||
- **MANDATORY HANDOVER**: At the end of every task/session, update the handover note in `dev_docs/SESSION_STATE.md`. Specify: **Active AI**, **Current Status**, **Technical Context**, and **Next Steps**.
|
||||
- **ARCHIVAL RULE**: Before writing new state, move all content of the *previous* session handover into the top of `dev_docs/SESSION_HISTORY.md` to prevent bloat.
|
||||
---
|
||||
|
||||
## 1. AI MEMORY, TRACEABILITY & HANDOVER
|
||||
- **MANDATORY STARTUP**: Read `dev_docs/SESSION_STATE.md` immediately at session start.
|
||||
- **PLAN RETIREMENT**: Mark a completed "Master Plan" as `[COMPLETED]` in the file itself. Move technical details to `dev_docs/ARCHIVE_LOGS.md` and `PLAN.md` entries to `dev_docs/PLAN_HISTORY.md`.
|
||||
- **STRICT HANDOVER**: Update `dev_docs/SESSION_STATE.md` at the end of every task with: **Active AI**, **Current Status** (Stable/Broken/In-Progress), **Context**, and **Next Steps**.
|
||||
- **SESSION ARCHIVE**: Move previous handover content to `dev_docs/SESSION_HISTORY.md` before writing new state.
|
||||
- **NO INTERACTION OVERLAP**: Never modify a file if another AI session is explicitly working on it.
|
||||
- **CONCISE COMMUNICATION**: Be concise! Do not explain a thousand details unless they are absolutely necessary.
|
||||
|
||||
## 2. Global Operational Laws
|
||||
- **STRICT ENGLISH POLICY**: All web interfaces, code, variables, scripts, and documentation MUST BE DIRECTLY AND ONLY IN ENGLISH. If you find Romanian text in code, translate it immediately.
|
||||
- **COMMUNICATION LANGUAGE**: Conversation with the user will be in Romanian or English (preferably Romanian).
|
||||
- **GIT BINARY PATH**: On this macOS environment, the system `git` is often broken. ALWAYS use the binary path stored in `.git_path` at the project root for all Git operations.
|
||||
- **COMMIT & PUSH STRICT RULE**:
|
||||
- Never push to remote (`git push`) or use force flags (`--hard`, `--force`) unless explicitly requested.
|
||||
- Always update `VERSION.json` on every commit.
|
||||
- Do NOT add AI co-author signatures (e.g., `Co-Authored-By: AI...`) to commit messages.
|
||||
- Branching: `master` (stable), `dev` (active development), `vX` (archival releases).
|
||||
- **PACKAGE MANAGEMENT**: Any new package installed via `pip install` MUST be added to `backend/requirements.txt` with version constraints (e.g., `slowapi>=0.1.9`). Keep requirements.txt synchronized with installed packages.
|
||||
- **MANDATORY LOGGING**: Document coding/architecture changes in `dev_docs/ARCHIVE_LOGS.md` at the end of the task.
|
||||
- **TRIPLE CONFIRMATION (Safe Forget)**: You cannot delete a physical location, item, or critical entity without explicit user permission three times.
|
||||
## 2. ENGINEERING & OPERATIONAL LAWS
|
||||
- **ENGLISH ONLY**: Interfaces, code, variables, and docs MUST be in English. Translate any Romanian text found in code immediately. (User conversation: Romanian/English).
|
||||
- **GIT PROTOCOL**: Use the direct binary path `/Library/Developer/CommandLineTools/usr/bin/git`) for ALL Git operations. **DO NOT REMOVE OR CHANGE THIS PATH UNDER ANY CIRCUMSTANCES!** Never push or use `--force` unless explicitly asked. Branching: `master` (stable), `dev` (active), `vX` (archive).
|
||||
- **VERSIONING**: Update `VERSION.json` on every commit. Use `scripts/save_version.py` for automated releases.
|
||||
- **DEPENDENCIES**: Update `backend/requirements.txt` with version constraints for every new pip package.
|
||||
- **SSOT INTEGRITY**: Every feature change MUST update: `README.md`, `USER_GUIDE.md`, `PROJECT_ARCHITECTURE.md`, and `export_prod.sh`.
|
||||
|
||||
## 3. UI/UX Fidelity Specifications
|
||||
- **Fidelity First**: Never simplify the UI unless asked. Density and aesthetics must remain "Premium".
|
||||
- **Styling**: Tailwind CSS. Font weights/spacing must be consistent (Inter/Roboto).
|
||||
- **Readability**: NO `uppercase` or `tracking-widest` styles. Use standard camel/Title case.
|
||||
- **Icons**: Use **Lucide Icons** exclusively. NO emojis.
|
||||
- **Interactive Affordance**: All select/dropdown boxes MUST have a `ChevronDown` icon. Masked passwords should have reduced opacity (`text-white/50`).
|
||||
- **Iconography Standardization**:
|
||||
- **Categories**: ALWAYS use `Layers` (Color: `text-primary`).
|
||||
- **Item Types**: ALWAYS use `Package` (Color: `text-green-500`).
|
||||
## 3. UI/UX "PREMIUM" FIDELITY STANDARDS
|
||||
- **Aesthetics**: Density/aesthetics must remain "Premium". Use Tailwind CSS. NO simplification.
|
||||
- **Typography Rules**:
|
||||
- **NO UPPERCASE** or **NO ITALICS** in headers, labels, buttons, or metadata.
|
||||
- **NO `tracking-widest`**. Use standard camel/Title case.
|
||||
- Use `font-black` for main headings.
|
||||
- **Layout**: Main pages MUST use `max-w-7xl`.
|
||||
- **Unified Headers**: Icon box (`p-4 bg-primary/10 border-primary/20`) + Title (`text-3xl font-black`) + Subtitle (`text-xs text-slate-500`).
|
||||
- **Iconography**: Use **Lucide Icons** exclusively (NO emojis).
|
||||
- **Categories**: `Layers` (text-primary).
|
||||
- **Item Types**: `Package` (text-green-500).
|
||||
- **Affordance**: Dropdowns MUST have a `ChevronDown`. Passwords: `text-white/50`. Logout MUST be `text-rose-500`.
|
||||
|
||||
## 4. Documentation Maintenance
|
||||
- **SSOT INTEGRITY**: Whenever a feature is added, modified, or removed, you MUST update all corresponding documentation files:
|
||||
- `README.md` (General usage & technical modes).
|
||||
- `USER_GUIDE.md` (End-user instructions).
|
||||
- `PROJECT_ARCHITECTURE.md` (Technical logic & data models).
|
||||
- `export_prod.sh` (If new scripts or files must be included in the production bundle).
|
||||
- **REAL-TIME UPDATES**: Documentation updates are NOT optional and must be performed within the same session as the code changes.
|
||||
## 4. DATA INTEGRITY & AUDIT POLICY
|
||||
- **RESTRICTED ACTIONS**: `DELETE /items/` and Admin settings require `auth.get_current_admin`.
|
||||
- **AUDIT IMMUTABILITY**: Deleting an `Item` MUST NOT delete its `AuditLog` entries.
|
||||
- **TRACEABILITY**: Log deletions to `logs/backend.log` with `USER[id]`, `ITEM[id]`, `Name`, `PN`.
|
||||
- **CONFIRMATION**:
|
||||
- **Triple Confirmation**: Deleting critical entities (Locations/Items) requires user confirmation 3 times.
|
||||
- **Native Alerts**: Use `window.confirm` for all destructive UI actions and Logout.
|
||||
|
||||
## 6. AI Command Shortcuts
|
||||
- **`save-version`**: When the user triggers this command, the AI MUST:
|
||||
1. Increment the patch version in `VERSION.json`.
|
||||
2. Stage all current changes (`git add .`).
|
||||
3. Commit changes with message `Build [vX.Y.Z]`.
|
||||
4. Create a new branch named `vX.Y.Z` from the current state.
|
||||
5. Generate a production bundle ZIP (calls `./export_prod.sh`).
|
||||
6. Stay on the current branch (`dev`).
|
||||
- *Implementation*: Use `python3 scripts/save_version.py` to ensure consistency.
|
||||
## 5. AI COMMAND SHORTCUTS
|
||||
- **`save-version`**:
|
||||
0. **MANDATORY**: Verify and update ALL documentation (`.md` files: README, USER_GUIDE, ARCHITECTURE, etc.) with explanations of all current changes.
|
||||
1. Increment `VERSION.json`.
|
||||
2. Git add/commit (`Build [vX.Y.Z]`).
|
||||
3. Create branch `vX.Y.Z` (Snapshot).
|
||||
4. Automatic Sync: Merge changes into `master` branch to keep it up-to-date.
|
||||
5. Run `./export_prod.sh`.
|
||||
(Always use `python3 scripts/save_version.py`).
|
||||
|
||||
## 7. UI Fidelity Laws (Strict)
|
||||
- **Consistent Layout**: All main pages (Inventory, Audit, Admin) MUST use `max-w-7xl` for their primary content container.
|
||||
- **Unified Headers**: Every functional page MUST have an identical header structure:
|
||||
- Icon inside a styled box (`p-4 bg-primary/10 rounded-[2rem] border border-primary/20`).
|
||||
- Title: `text-3xl font-black text-white tracking-tight`.
|
||||
- Subtitle: `text-xs text-slate-500 font-bold mt-1 tracking-wider`.
|
||||
- **Typographic Integrity**:
|
||||
- **NO UPPERCASE** (ALL CAPS) allowed anywhere in the UI (headers, labels, buttons, metadata).
|
||||
- **NO ITALICS** in headers, titles, or active UI elements.
|
||||
- Consistent font weight (`font-black`) for main headings.
|
||||
- **Safety Actions**:
|
||||
- The **Logout** button MUST be clearly differentiated (e.g., `text-rose-500`) and MUST always require a `window.confirm` before execution.
|
||||
---
|
||||
|
||||
## 8. Audit & Deletion Policy
|
||||
- **Immutability of Logs**: Deleting an `Item` MUST NOT delete its corresponding entries in the `AuditLog` table in the database.
|
||||
- **Disk Traceability**: Every item deletion MUST be logged to the disk file (`logs/backend.log`) with `USER[id]`, `ITEM[id]`, `Name`, and `PN`.
|
||||
- **Explicit Confirmation**: Destructive actions (Delete Item from catalog) MUST always trigger a browser-native `window.confirm` before proceeding.
|
||||
|
||||
|
||||
## 5. End of Session Protocol
|
||||
- Once a phase/task from `PLAN.md` is completed and verified, move that entry into `dev_docs/PLAN_HISTORY.md`.
|
||||
- After finishing an entire job (including updating session state, architecture logs, and versioning), end your final response on a separate line exactly with:
|
||||
```
|
||||
---
|
||||
✓ Done.
|
||||
```
|
||||
- Do not provide unnecessary verbatim summaries of the code.
|
||||
## END OF SESSION PROTOCOL
|
||||
End your final response on a separate line exactly with:
|
||||
```
|
||||
---
|
||||
✓ Done.
|
||||
```
|
||||
|
||||
@@ -8,4 +8,7 @@ Before taking any action or writing code, you MUST read the following Single Sou
|
||||
2. `PROJECT_ARCHITECTURE.md` (Contains the tech stack, data models, and system logic).
|
||||
3. `dev_docs/SESSION_STATE.md` (Contains the current handover status from the previous AI).
|
||||
|
||||
**STRICT COMMUNICATION RULE:**
|
||||
Be concise! Do not explain a thousand details unless they are absolutely necessary.
|
||||
|
||||
Do not proceed with any task until you have analyzed these three files.
|
||||
|
||||
12
Caddyfile
@@ -1,12 +0,0 @@
|
||||
# TFM aInventory - Caddy Self-Signed Internal Proxy
|
||||
# This replaces the need for `local-ssl-proxy` in Node.
|
||||
|
||||
:3003 {
|
||||
tls internal
|
||||
reverse_proxy frontend:3000
|
||||
}
|
||||
|
||||
:3002 {
|
||||
tls internal
|
||||
reverse_proxy backend:8000
|
||||
}
|
||||
@@ -8,4 +8,7 @@ Before taking any action or writing code, you MUST read the following Single Sou
|
||||
2. `PROJECT_ARCHITECTURE.md` (Contains the tech stack, data models, and system logic).
|
||||
3. `dev_docs/SESSION_STATE.md` (Contains the current handover status from the previous AI).
|
||||
|
||||
**STRICT COMMUNICATION RULE:**
|
||||
Be concise! Do not explain a thousand details unless they are absolutely necessary.
|
||||
|
||||
Do not proceed with any task until you have analyzed these three files.
|
||||
|
||||
8
PLAN.md
@@ -3,11 +3,7 @@
|
||||
This document tracks the immediate implementation checklist. For overarching design and constraints, see [PROJECT_ARCHITECTURE.md](PROJECT_ARCHITECTURE.md).
|
||||
|
||||
## Implementation Status
|
||||
- [x] **Phase 1: Backend Foundation** (FastAPI, SQLite, Models).
|
||||
- [x] **Phase 2: Modular AI Integration** (Gemini support, v2 SDK).
|
||||
- [x] **Phase 3: AI Onboarding UI** (Validation Mask, Camera integration).
|
||||
- [x] **Phase 4: Offline Synchronization** (Deduplicated Dexie -> SQL sync logic).
|
||||
- [x] **Phase 5: Inventory Trash Management** (Waste/Discard/Damage workflow).
|
||||
- [ ] **Phase 6: Audit Log Dashboard UI** (Visual historical interventions).
|
||||
- [ ] **Phase 8: Database Encryption** (Implementing SQLCipher for data-at-rest protection).
|
||||
- [ ] **Phase 9: Multi-Location Support** (Tracking inventory across different physical warehouses).
|
||||
|
||||
*(Note: Completed phases are periodically moved to `dev_docs/PLAN_HISTORY.md` according to AI_RULES)*
|
||||
|
||||
@@ -16,27 +16,35 @@ A unified system to maintain an inventory of "items" and their quantities, inclu
|
||||
|
||||
### 2.2 Frontend (Web & PWA)
|
||||
- **Architecture:** Next.js 15+ (App Router)
|
||||
- **Styling:** Tailwind CSS (Readability-first config)
|
||||
- **Styling:** Tailwind CSS (Readability-first config, mobile-first responsive)
|
||||
- **Icons:** Lucide Icons (React components)
|
||||
- **Components:**
|
||||
- **StatCard** (v1.9.21+): Responsive stat display component for mobile/desktop
|
||||
- Two-column flexbox layout (label left, number right)
|
||||
- Responsive font sizing with Tailwind breakpoints (text-sm→md, text-lg→xl)
|
||||
- Label truncation with ellipsis for overflow handling
|
||||
- Accessibility: `role="status"`, `aria-hidden` on decorative icons
|
||||
- **Offline persistence:** Dexie.js (IndexedDB wrapper)
|
||||
- **Scanner:** `html5-qrcode` (Client-side, offline-only)
|
||||
- **Sync:** Axios with bulk-sync idempotency (UUID-based)
|
||||
|
||||
### 2.3 Operations & Tooling
|
||||
- **PWA Deployment:** `next-pwa` (Service Workers + Manifest.json)
|
||||
- **HTTPS Proxy:** `local-ssl-proxy` (Required for mobile camera access, Port 3003)
|
||||
- **Servers:** Frontend (`npm run dev` on 3000), Backend (`./start_server.sh` on 8000)
|
||||
- **HTTPS Proxy:** `caddy` or `local-ssl-proxy` (Port 8909)
|
||||
- **Servers:** Frontend (Port 8907), Backend (Port 8906)
|
||||
- **Configuration:** Centrally managed via root `inventory.env` (Network/CORS/API Keys) and `config/` directory (LDAP, Caddyfile).
|
||||
|
||||
## 3. Data Models & Entities
|
||||
- **Item:** Name, Category Group (Structured), Item Type (Specific), Quantity, Barcode, Part Number.
|
||||
- **Item:** Name, Category Group (Structured), Item Type (Specific), Quantity, Barcode, Part Number, Box Label (Association).
|
||||
- **Category:** Predefined groups for organizational structure.
|
||||
- **Intervention:** Linked to a required items list.
|
||||
- **Audit Log:** Immutable ledger detailing CRUD operations and stock fluctuations.
|
||||
- **Box/Container:** A generic grouping label (box_label) that links multiple items together for rapid multi-scanning.
|
||||
- **Audit Log:** Immutable ledger detailing CRUD operations and stock fluctuations, including point-in-time box associations.
|
||||
|
||||
## 4. Scanning & Optimization Strategy (Crucial)
|
||||
### 4.1 AI Usage Policy
|
||||
- **Routine Operations (Check-in/Out):** Executes entirely on the local device unconditionally using `html5-qrcode` ($0 cost). No AI is allowed here.
|
||||
- **New Item Onboarding (AI Label OCR):** Uses cloud AI (`gemini-2.0-flash`). The user takes a photo, AI extracts data based on strict templates.
|
||||
- **AI Box Discovery Mode (v1.6.0):** Supports specialized `mode="box"` prompt that focuses exclusively on prominent container names/hand-written labels, ignoring technical spec noise.
|
||||
- **Validation Mask:** AI-extracted data is NEVER saved directly. It is presented in a validation UI for human confirmation.
|
||||
|
||||
### 4.2 Scanner Technical Specs
|
||||
@@ -48,6 +56,16 @@ A unified system to maintain an inventory of "items" and their quantities, inclu
|
||||
- Noise Filtering: Ignores `< 3` chars, decimals, and dates.
|
||||
- Scoring: Exact S/N (+500), Exact P/N (+200), Token match (+50), Category match (+20).
|
||||
- Threshold: Minimum **40 points** for auto-match without user intervention.
|
||||
- **Targeted Field Scanning (v1.6.0):** UI allows "locking" the scanner focus to a specific input field (e.g., `box_label`). The OCR result is then redirected to state without performing regular item lookup.
|
||||
|
||||
### 4.3 Box Labeling & Printing System (v1.5.0)
|
||||
- **Local OCR Priority:** Before checking individual S/Ns, the matching engine searches for `box_label` tokens. If a box is identified:
|
||||
- Single Match: Directly opens stock adjustment.
|
||||
- Multi Match: Opens "Box Contents" selection interstitial.
|
||||
- **Label Generation:** Native SVG-based Code 128 and QR generation (`lib/labels.ts`). Requires ZERO external libraries for maximum offline stability.
|
||||
- **Printing Modes:**
|
||||
- @media print: Hardcoded CSS styles for 62mm x 29mm label dimensions.
|
||||
- Mobile Export: Canvas-to-PNG rasterization for sharing with Bluetooth printer roll apps.
|
||||
|
||||
|
||||
## 5. Offline Sync Protocol
|
||||
@@ -59,3 +77,29 @@ To prevent data loss in basements or unstable networks:
|
||||
## 6. Automation & Versioning (`scripts/`)
|
||||
- **`scripts/save_version.py`**: Implements the `save-version` AI Command Shortcut. Increments `VERSION.json` patch version, commits all staged changes, creates a snapshot branch `v.X.Y.Z`, and calls `./export_prod.sh` to generate the production bundle. Always stays on the `dev` branch.
|
||||
|
||||
|
||||
## 7. Security & Hardening (v1.4.0)
|
||||
To ensure enterprise-grade protection, the following policies are enforced:
|
||||
|
||||
### 7.1 Access Control & RBAC
|
||||
- **Strict Separation:** Operations are divided into `user` and `admin` roles.
|
||||
- **Admin Only:** Critical operations such as `DELETE /items/`, user management, and DB settings are restricted via the `auth.get_current_admin` dependency.
|
||||
- **User Role:** Standard users are permitted to perform check-in/out and list inventory, but cannot delete catalog entries.
|
||||
|
||||
### 7.2 CORS & Origin Policy (v1.9.18)
|
||||
- **Automatic Discovery:** The system detects local LAN IP and automatically authorizes it.
|
||||
- **Generic Expansion:** Use `EXTRA_ALLOWED_ORIGINS` for Tailscale or VPN IPs. The system automatically expands each IP into a set of authorized Origins (http/8916, https/8918, https/8919).
|
||||
- **Rate Limiting:** Implemented via `slowapi`. The `login` endpoint is limited to **5 requests per minute** per IP to mitigate automated credential stuffing.
|
||||
|
||||
### 7.3 Data Privacy
|
||||
- **Information Scrubbing:** Backend logs are configured to intercept and mask sensitive auth tokens or internal secrets (e.g., `JWT_SECRET_KEY`) during debug output.
|
||||
- **Direct Bind LDAP:** Authentication uses direct user binding to the LDAP server, avoiding the need for a privileged service account with broad search permissions.
|
||||
- **Cryptographic Credential Caching:** To support offline operations, the system caches a **PBKDF2-HMAC-SHA256 hash** of the user's Enterprise credentials upon successful online login. Plain text passwords are NEVER stored.
|
||||
|
||||
### 7.4 PWA Trust & Security
|
||||
- **HTTPS Enforcement:** The system requires TLS (Port 8909) for camera access and secure token transmission.
|
||||
- **Manifest Integrity:** A comprehensive `manifest.json` ensures the app is recognized as a trusted PWA on mobile platforms (iOS/Android).
|
||||
7.5 Git Infrastructure Hardening (v1.7.0)
|
||||
To ensure deployment stability on macOS environments with potentially broken developer tool links (`xcode-select` errors):
|
||||
- **Direct Binary Mapping:** The system bypasses path resolution by using a hardcoded direct link to the Git binary in `.git_path` (`/Library/Developer/CommandLineTools/usr/bin/git`).
|
||||
- **Persistence Mandate:** This path is protected by mandatory AI rules and must never be removed or modified to ensure `save-version` and automated deployment scripts remain functional.
|
||||
|
||||
32
README.md
@@ -12,21 +12,21 @@ This project supports three distinct operational modes:
|
||||
Ideal for local development on macOS/Linux.
|
||||
* **Command:** `./start_server.sh`
|
||||
* **Details:** Runs FastAPI (backend) and Next.js (frontend) in development mode. Uses `local-ssl-proxy` for HTTPS.
|
||||
* **Backend:** http://localhost:8000
|
||||
* **Frontend:** https://localhost:3003
|
||||
* **Backend:** http://localhost:8916
|
||||
* **Frontend:** https://localhost:8919
|
||||
|
||||
### 2. 🐳 Docker Mode (Recommended for Production)
|
||||
Isolated and portable container stack.
|
||||
* **Command:** `docker-compose up -d --build`
|
||||
* **Details:** Uses Caddy as a reverse proxy for HTTPS. Persistent data and logs are mapped to `./data` and `./logs`.
|
||||
* **Access:** https://localhost:3003
|
||||
* **Access:** https://localhost:8909
|
||||
|
||||
### 3. 🐧 Standalone Linux Mode (Systemd)
|
||||
Native Linux installation (Alma/Debian/Ubuntu) without Docker dependencies.
|
||||
* **Installation:** `sudo ./install_service.sh`
|
||||
* **Execution:** `sudo systemctl start inventory`
|
||||
* **Details:** Compiles the frontend for production and manages the entire stack as a system service.
|
||||
* **Access:** https://<SERVER-IP>:3003
|
||||
* **Access:** https://<SERVER-IP>:8909
|
||||
|
||||
---
|
||||
|
||||
@@ -34,14 +34,25 @@ Native Linux installation (Alma/Debian/Ubuntu) without Docker dependencies.
|
||||
To generate a clean production package and snapshot the current state:
|
||||
1. Use the AI shortcut command: `save-version`.
|
||||
2. Alternatively, run `./export_prod.sh` manually.
|
||||
3. A `.zip` archive will be created (e.g., `aInventory-PROD-v1.3.6.zip`).
|
||||
3. A `.zip` archive will be created (e.g., `aInventory-PROD-v1.7.0.zip`).
|
||||
4. A backup branch `v.1.3.x` will be created automatically.
|
||||
|
||||
---
|
||||
|
||||
## 🎨 Recent UI/UX Improvements (v1.9.21+)
|
||||
|
||||
### Mobile-First Responsive Design
|
||||
- **StatCard Component:** Reusable, responsive stat display component for mobile phones
|
||||
- Two-column flexbox layout (label left, number right) prevents text overflow on narrow screens
|
||||
- Responsive font sizing: `text-sm md:text-base` (labels), `text-lg md:text-xl` (numbers)
|
||||
- Automatic label truncation with ellipsis for long text
|
||||
- Accessible markup with `role="status"` and `aria-hidden` attributes
|
||||
- **Pages Updated:** Inventory (Categories, Item Types, Total Boxes), Logs (Total Events, Check in/out), Admin (Local Archives)
|
||||
- **Tested on:** iPhone SE (375px), iPhone 12 (390px), iPhone 14 Pro Max (430px), tablets (768px), desktop (1024px)
|
||||
|
||||
## 🏗 Technical Overview
|
||||
* **Backend:** FastAPI (Python 3.12+)
|
||||
* **Frontend:** Next.js 15+ (React PWA)
|
||||
* **Frontend:** Next.js 15+ (React PWA) with responsive Tailwind CSS
|
||||
* **Database:** SQLite (SQLAlchemy) with Dexie.js (IndexedDB) for client-side sync.
|
||||
* **Proxy:** Caddy (Docker) or local-ssl-proxy (Standalone/Dev).
|
||||
* **AI Engine:** Google Gemini (Generative AI SDK).
|
||||
@@ -58,7 +69,8 @@ The application requires the following environment variables for production depl
|
||||
| Variable | Purpose | Example |
|
||||
|----------|---------|---------|
|
||||
| **JWT_SECRET_KEY** | JWT token signing key (REQUIRED for production) | `openssl rand -hex 32` |
|
||||
| **ALLOWED_ORIGINS** | CORS-allowed domain origins (comma-separated) | `https://inventory.example.com,https://api.example.com` |
|
||||
| **EXTRA_ALLOWED_ORIGINS** | Extra IPs or FQDNs for CORS (Tailscale, VPN, etc.) | `100.78.182.27,inventory.local` |
|
||||
| **ALLOWED_ORIGINS** | CORS-allowed domain origins (automatically includes LOCAL_IP) | `https://inventory.example.com` |
|
||||
| **DATA_DIR** | SQLite database location | `/app/data` |
|
||||
| **LOGS_DIR** | Application logs directory | `/app/logs` |
|
||||
|
||||
@@ -77,6 +89,12 @@ export ALLOWED_ORIGINS="https://your-domain.com"
|
||||
docker-compose up -d --build
|
||||
```
|
||||
|
||||
### 🌐 Network & Port Customization
|
||||
The application uses a central configuration file for all network settings:
|
||||
- **Location:** `config/network_config.env`
|
||||
- **Purpose:** Change the `SERVER_IP` (default: `192.168.84.113`) and reserved ports (`8906-8909`).
|
||||
- **Mechanism:** Startup scripts automatically sync these settings to the frontend and Docker environment.
|
||||
|
||||
For detailed security audit report, see [dev_docs/SECURITY_REPORT.md](dev_docs/SECURITY_REPORT.md).
|
||||
|
||||
---
|
||||
|
||||
@@ -19,7 +19,7 @@ The application is a **Progressive Web App**, which means you don't need to down
|
||||
|
||||
- **Default User:** On first installation, use `Admin` / `<initial-password>` (check your system administrator for the initial password).
|
||||
- **Change Password:** We recommend changing your password immediately from the Admin settings.
|
||||
- **LDAP/Enterprise Login:** If your administrator has configured LDAP integration, you can log in with your company/domain account. The application will cache your password locally to allow offline access (e.g., in areas without signal like basements).
|
||||
- **LDAP/Enterprise Login:** If your administrator has configured LDAP integration, you can log in with your company/domain account. The application will securely cache a **cryptographic hash** of your credentials (using PBKDF2) to allow offline access (e.g., in areas without signal like basements). **Note: Your actual password is NEVER stored in plain text on the local device.**
|
||||
- **JWT Tokens:** Your login session is secured with JWT bearer tokens that expire after 8 hours. You will be automatically logged out when your token expires.
|
||||
|
||||
---
|
||||
@@ -31,16 +31,27 @@ The application supports two scanning modes:
|
||||
### Manual / Barcode Scanning
|
||||
Scan an existing barcode to locate or update an item in your inventory.
|
||||
|
||||
### Client-Side Label Scanning (OCR)
|
||||
Point your device's camera at a product label. The scanner **automatically analyzes the label every 4 seconds** — no button press required. A countdown timer shows when the next scan will occur.
|
||||
|
||||
When text is detected:
|
||||
1. A preview of the captured image appears.
|
||||
2. Detected words are highlighted — tap the correct product name or serial number.
|
||||
3. The selected text populates the relevant field automatically.
|
||||
|
||||
If no readable text is found, the scanner silently retries on the next cycle.
|
||||
|
||||
### Box & Container Scanning (NEW v1.6.0)
|
||||
You can now manage containers more efficiently with two specialized methods:
|
||||
|
||||
- **AI Box Discovery**: When adding a new container through **AI Discovery**, use the **"Box / Container"** toggle. Gemini will focus exclusively on the container's name, ignoring technical noise on labels.
|
||||
- **Targeted Field Scanning**: In the **Edit Item** modal, tap the small **Camera icon** next to the "Box / Container Label" field. The scanner will capture the next physical label directly into the text field.
|
||||
- **Automatic Matching**: In the main scanner, scanning a box identifies all its contents. Scanning a box and then an item will suggest linking them together if they aren't already matched.
|
||||
|
||||
---
|
||||
|
||||
## 🏷️ Label Printing
|
||||
Administrators and users can generate physical labels for boxes to ensure 100% accurate scanning.
|
||||
## 🏷️ 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).
|
||||
|
||||
---
|
||||
|
||||
## 📂 Inventory Organization
|
||||
@@ -92,6 +103,11 @@ If your organization uses LDAP/Active Directory, administrators can:
|
||||
### Settings
|
||||
Access application settings from the **Admin** panel.
|
||||
|
||||
### 🌐 Network & Configuration (NEW v1.8.0)
|
||||
The application now uses a centralized configuration folder in the project root:
|
||||
- **`inventory.env`**: The primary network configuration file. Centralizes `SERVER_IP`, ports, and `EXTRA_ALLOWED_ORIGINS`.
|
||||
- **Dynamic Port Mapping**: Changes to the server IP, ports, or allowed origins are automatically detected by both the frontend and backend after a restart.
|
||||
|
||||
---
|
||||
|
||||
## 🚨 Security Notices
|
||||
@@ -133,5 +149,5 @@ For detailed technical documentation, see the [Project Architecture](../PROJECT_
|
||||
|
||||
---
|
||||
|
||||
**Version:** v1.3.6
|
||||
**Last Updated:** 2026-04-11
|
||||
**Version:** v1.9.19
|
||||
**Last Updated:** 2026-04-14
|
||||
|
||||
12
VERSION.json
@@ -1,12 +0,0 @@
|
||||
{
|
||||
"version": "1.4.0",
|
||||
"last_build": "2026-04-12-0940",
|
||||
"commit": "HEAD",
|
||||
"changelog": [
|
||||
"v1.4.0: Final Audit Dashboard (Phase 6), LDAP UI Restoration, environment path fixes for macOS",
|
||||
"v1.3.9: Maintenance and structural updates",
|
||||
"v1.3.8: Remove .npx_cache/ and scratch/npm_cache/ from git tracking (3350 files purged from index)",
|
||||
"v1.3.7: Security hardening \u2014 expanded .gitignore, removed ldap_config.json from tracking, added example files",
|
||||
"v1.3.6: Scanner UI redesign (autonomous OCR, countdown), Item Type datalist, save-version automation"
|
||||
]
|
||||
}
|
||||
BIN
_images.tests/IMG_6082.jpeg
Normal file
|
After Width: | Height: | Size: 3.3 MiB |
BIN
_images.tests/IMG_6106.jpeg
Normal file
|
After Width: | Height: | Size: 2.6 MiB |
BIN
_images.tests/IMG_6107.jpeg
Normal file
|
After Width: | Height: | Size: 2.6 MiB |
BIN
_images.tests/IMG_6108.jpeg
Normal file
|
After Width: | Height: | Size: 3.0 MiB |
BIN
_images.tests/IMG_6109.jpeg
Normal file
|
After Width: | Height: | Size: 3.6 MiB |
BIN
_images.tests/IMG_6110.jpeg
Normal file
|
After Width: | Height: | Size: 3.1 MiB |
BIN
_images.tests/IMG_6111.jpeg
Normal file
|
After Width: | Height: | Size: 3.6 MiB |
BIN
_images.tests/IMG_6112.jpeg
Normal file
|
After Width: | Height: | Size: 3.2 MiB |
BIN
_images.tests/IMG_6113.jpeg
Normal file
|
After Width: | Height: | Size: 3.3 MiB |
BIN
_images.tests/IMG_6114.jpeg
Normal file
|
After Width: | Height: | Size: 3.5 MiB |
BIN
_images.tests/IMG_6115.jpeg
Normal file
|
After Width: | Height: | Size: 3.5 MiB |
BIN
_images.tests/IMG_6116.jpeg
Normal file
|
After Width: | Height: | Size: 3.3 MiB |
BIN
_images.tests/IMG_6117.jpeg
Normal file
|
After Width: | Height: | Size: 3.6 MiB |
BIN
_images.tests/IMG_6118.jpeg
Normal file
|
After Width: | Height: | Size: 3.6 MiB |
BIN
_images.tests/IMG_6119.jpeg
Normal file
|
After Width: | Height: | Size: 3.4 MiB |
BIN
_images.tests/IMG_6120.jpeg
Normal file
|
After Width: | Height: | Size: 3.4 MiB |
BIN
_images.tests/IMG_6121.jpeg
Normal file
|
After Width: | Height: | Size: 2.7 MiB |
BIN
_images.tests/IMG_6122.jpeg
Normal file
|
After Width: | Height: | Size: 2.8 MiB |
BIN
_images.tests/IMG_6123.jpeg
Normal file
|
After Width: | Height: | Size: 3.0 MiB |
BIN
_images.tests/IMG_6124.jpeg
Normal file
|
After Width: | Height: | Size: 3.5 MiB |
BIN
_images.tests/IMG_6125.jpeg
Normal file
|
After Width: | Height: | Size: 2.9 MiB |
BIN
_images.tests/IMG_6126.jpeg
Normal file
|
After Width: | Height: | Size: 3.7 MiB |
BIN
_images.tests/IMG_6127.jpeg
Normal file
|
After Width: | Height: | Size: 2.6 MiB |
BIN
_images.tests/IMG_6128.jpeg
Normal file
|
After Width: | Height: | Size: 2.7 MiB |
BIN
_images.tests/IMG_6129.jpeg
Normal file
|
After Width: | Height: | Size: 2.5 MiB |
BIN
_images.tests/IMG_6130.jpeg
Normal file
|
After Width: | Height: | Size: 2.6 MiB |
BIN
_images.tests/IMG_6131.jpeg
Normal file
|
After Width: | Height: | Size: 3.5 MiB |
BIN
_images.tests/IMG_6132.jpeg
Normal file
|
After Width: | Height: | Size: 3.6 MiB |
BIN
_images.tests/IMG_6133.jpeg
Normal file
|
After Width: | Height: | Size: 2.7 MiB |
BIN
_images.tests/IMG_6134.jpeg
Normal file
|
After Width: | Height: | Size: 3.5 MiB |
BIN
_images.tests/IMG_6135.jpeg
Normal file
|
After Width: | Height: | Size: 3.0 MiB |
BIN
_images.tests/IMG_6136.jpeg
Normal file
|
After Width: | Height: | Size: 2.5 MiB |
BIN
_images.tests/IMG_6137.jpeg
Normal file
|
After Width: | Height: | Size: 3.3 MiB |
BIN
_images.tests/IMG_6139.jpeg
Normal file
|
After Width: | Height: | Size: 2.1 MiB |
BIN
_images.tests/IMG_6140.jpeg
Normal file
|
After Width: | Height: | Size: 3.2 MiB |
BIN
_images.tests/IMG_6141.jpeg
Normal file
|
After Width: | Height: | Size: 3.4 MiB |
BIN
_images.tests/IMG_6142.jpeg
Normal file
|
After Width: | Height: | Size: 3.3 MiB |
BIN
_images.tests/IMG_6143.jpeg
Normal file
|
After Width: | Height: | Size: 3.4 MiB |
BIN
_images.tests/IMG_6144.jpeg
Normal file
|
After Width: | Height: | Size: 3.4 MiB |
BIN
_images.tests/IMG_6145.jpeg
Normal file
|
After Width: | Height: | Size: 3.4 MiB |
BIN
_images.tests/IMG_6146.jpeg
Normal file
|
After Width: | Height: | Size: 3.2 MiB |
BIN
_images.tests/IMG_6147.jpeg
Normal file
|
After Width: | Height: | Size: 3.3 MiB |
BIN
_images.tests/IMG_6148.jpeg
Normal file
|
After Width: | Height: | Size: 3.1 MiB |
BIN
_images.tests/IMG_6149.jpeg
Normal file
|
After Width: | Height: | Size: 2.9 MiB |
BIN
_images.tests/IMG_6150.jpeg
Normal file
|
After Width: | Height: | Size: 3.4 MiB |
BIN
_images.tests/IMG_6151.jpeg
Normal file
|
After Width: | Height: | Size: 2.4 MiB |
BIN
_images.tests/IMG_6152.jpeg
Normal file
|
After Width: | Height: | Size: 3.5 MiB |
BIN
_images.tests/IMG_6153.jpeg
Normal file
|
After Width: | Height: | Size: 3.5 MiB |
@@ -1,25 +0,0 @@
|
||||
# ============================================================
|
||||
# TFM aInventory — Backend Environment Variables
|
||||
# Copy this file to .env and fill in real values.
|
||||
# NEVER commit the real .env file to Git!
|
||||
# ============================================================
|
||||
|
||||
# --- AI API Keys ---
|
||||
# Google Gemini API Key (required for AI label OCR onboarding)
|
||||
GEMINI_API_KEY=your_gemini_api_key_here
|
||||
|
||||
# --- Security ---
|
||||
# JWT secret key — generate a strong random value for production:
|
||||
# python3 -c "import secrets; print(secrets.token_urlsafe(64))"
|
||||
# If not set, an ephemeral key is generated per-run (tokens invalidated on restart).
|
||||
JWT_SECRET_KEY=change-me-generate-a-secure-random-value
|
||||
|
||||
# --- CORS ---
|
||||
# Comma-separated list of allowed frontend origins
|
||||
# Example for LAN deployment:
|
||||
# ALLOWED_ORIGINS=http://192.168.1.100:3000,https://192.168.1.100:3003
|
||||
ALLOWED_ORIGINS=http://localhost:3000,https://localhost:3003
|
||||
|
||||
# --- Data Paths (overridden by start_server.sh / docker-compose) ---
|
||||
# DATA_DIR=/absolute/path/to/data
|
||||
# LOGS_DIR=/absolute/path/to/logs
|
||||
@@ -1,10 +1,11 @@
|
||||
FROM python:3.12-slim
|
||||
|
||||
# Install system dependencies required for python-ldap (needed by backend)
|
||||
# Install system dependencies
|
||||
RUN apt-get update && apt-get install -y \
|
||||
build-essential \
|
||||
libldap2-dev \
|
||||
libsasl2-dev \
|
||||
gosu \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
WORKDIR /app
|
||||
@@ -26,14 +27,13 @@ COPY scripts ./scripts
|
||||
ENV DATA_DIR="/app/data"
|
||||
ENV LOGS_DIR="/app/logs"
|
||||
|
||||
# Ensure the appuser can write to data and logs if we pre-create them,
|
||||
# although Docker volumes will handle ownership context.
|
||||
# Pre-create directories and ensure they are writable
|
||||
RUN mkdir -p /app/data /app/logs && chown -R appuser:appuser /app
|
||||
|
||||
# Make initialization scripts executable
|
||||
RUN chmod +x /app/scripts/init_data.sh /app/backend/entrypoint.sh
|
||||
|
||||
USER appuser
|
||||
EXPOSE 8000
|
||||
|
||||
EXPOSE 8000
|
||||
|
||||
|
||||
@@ -1,42 +1,50 @@
|
||||
import os
|
||||
import json
|
||||
import io
|
||||
import logging
|
||||
from PIL import Image
|
||||
from google import genai
|
||||
from google.genai import types
|
||||
|
||||
log = logging.getLogger("ainventory")
|
||||
|
||||
def get_best_models():
|
||||
# Using the exact models discovered via diagnostic
|
||||
return ["gemini-2.0-flash", "gemini-2.5-flash"]
|
||||
# Priority list based on aliases and future-gen models found in user logs
|
||||
return [
|
||||
"gemini-flash-latest", # Points to the best stable Flash version
|
||||
"gemini-3.1-flash-lite-preview", # Cutting edge 3.1 Lite
|
||||
"gemini-flash-lite-latest", # Best Lite alias
|
||||
"gemini-pro-latest" # Pro fallback alias
|
||||
]
|
||||
|
||||
def extract(image_bytes: bytes, prompt: str):
|
||||
api_key = os.environ.get("GEMINI_API_KEY")
|
||||
if not api_key:
|
||||
print("CRITICAL: GEMINI_API_KEY is MISSING in environment!")
|
||||
log.error("CRITICAL: GEMINI_API_KEY is MISSING in environment!")
|
||||
return None
|
||||
|
||||
# Log partial key for safety debug
|
||||
key_hint = f"{api_key[:4]}...{api_key[-4:]}" if len(api_key) > 8 else "too short"
|
||||
print(f"🔑 Using API Key: {key_hint}")
|
||||
log.info(f"🔑 Using API Key: {key_hint}")
|
||||
|
||||
try:
|
||||
# Initialize the NEW SDK Client forcing stable v1 API
|
||||
client = genai.Client(api_key=api_key, http_options={'api_version': 'v1'})
|
||||
# Switching to v1beta which is required for many experimental/new models
|
||||
client = genai.Client(api_key=api_key, http_options={'api_version': 'v1beta'})
|
||||
|
||||
# DEBUG: List allowed models for this key
|
||||
print("🔍 Checking available models for your key...")
|
||||
log.debug("🔍 Checking available models for your key...")
|
||||
try:
|
||||
for m in client.models.list():
|
||||
print(f" - Found: {m.name}")
|
||||
log.debug(f" - Found: {m.name}")
|
||||
except Exception as list_e:
|
||||
print(f" ⚠️ Could not list models: {list_e}")
|
||||
log.warning(f" ⚠️ Could not list models: {list_e}")
|
||||
|
||||
models_to_try = get_best_models()
|
||||
|
||||
# Try models in order
|
||||
for model_name in models_to_try:
|
||||
try:
|
||||
print(f"🚀 AI Launching (v2 SDK): {model_name}...")
|
||||
log.info(f"🚀 AI Launching (v2 SDK): {model_name}...")
|
||||
|
||||
# In the new SDK, we pass a list of parts (text string and image bytes)
|
||||
response = client.models.generate_content(
|
||||
@@ -48,10 +56,12 @@ def extract(image_bytes: bytes, prompt: str):
|
||||
)
|
||||
|
||||
if not response or not response.text:
|
||||
log.warning(f"⚠️ Gemini {model_name} returned NO text.")
|
||||
continue
|
||||
|
||||
text = response.text.strip()
|
||||
print(f"✅ AI Response Received ({len(text)} bytes)")
|
||||
log.info(f"✅ AI Response Received ({len(text)} bytes)")
|
||||
log.debug(f"FULL RESPONSE:\n{text}")
|
||||
|
||||
# Extract JSON block
|
||||
if "```json" in text:
|
||||
@@ -62,10 +72,10 @@ def extract(image_bytes: bytes, prompt: str):
|
||||
return json.loads(text)
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ Gemini {model_name} failed: {e}")
|
||||
log.error(f"❌ Gemini {model_name} failed: {e}")
|
||||
continue
|
||||
|
||||
except Exception as outer_e:
|
||||
print(f"❌ Gemini Client Init failed: {outer_e}")
|
||||
log.error(f"❌ Gemini Client Init failed: {outer_e}")
|
||||
|
||||
return None
|
||||
|
||||
@@ -1,43 +1,132 @@
|
||||
import os
|
||||
import time
|
||||
from dotenv import load_dotenv
|
||||
from . import models
|
||||
from .database import SessionLocal
|
||||
from .ai import gemini, claude
|
||||
|
||||
# Load environment variables from the directory where this file resides
|
||||
# Note: Environment variables are managed centrally by config_loader.py
|
||||
base_dir = os.path.dirname(os.path.abspath(__file__))
|
||||
dotenv_path = os.path.join(base_dir, ".env")
|
||||
load_dotenv(dotenv_path)
|
||||
|
||||
def extract_label_info(image_bytes: bytes):
|
||||
class PromptManager:
|
||||
"""Manages AI prompt with auto-reloading from file."""
|
||||
def __init__(self, file_path: str):
|
||||
self.file_path = file_path
|
||||
self.last_mtime = 0
|
||||
self.cached_prompt = None
|
||||
|
||||
def get_prompt(self) -> str:
|
||||
if not os.path.exists(self.file_path):
|
||||
return None
|
||||
|
||||
try:
|
||||
current_mtime = os.path.getmtime(self.file_path)
|
||||
if current_mtime > self.last_mtime or self.cached_prompt is None:
|
||||
with open(self.file_path, 'r', encoding='utf-8') as f:
|
||||
self.cached_prompt = f.read().strip()
|
||||
self.last_mtime = current_mtime
|
||||
# Use print or log for visibility in dev
|
||||
print(f"🔄 AI Vision prompt reloaded from {self.file_path} (mtime: {current_mtime})")
|
||||
return self.cached_prompt
|
||||
except Exception as e:
|
||||
print(f"⚠️ Failed to reload AI prompt: {e}")
|
||||
return self.cached_prompt
|
||||
|
||||
# The prompt file is located in the global /config directory
|
||||
# We go up one level from backend/ to reach project root, then into config/
|
||||
PROJECT_ROOT = os.path.dirname(os.path.abspath(base_dir))
|
||||
PROMPT_FILE_PATH = os.path.join(PROJECT_ROOT, "config", "ai_prompt.md")
|
||||
prompt_mgr = PromptManager(PROMPT_FILE_PATH)
|
||||
|
||||
def extract_label_info(image_bytes: bytes, mode: str = "item"):
|
||||
"""
|
||||
Orchestrates extraction across multiple AI providers.
|
||||
Order: Gemini (Flash/Pro) -> Claude (Haiku/Sonnet)
|
||||
Modes: 'item' (full technical extraction), 'box' (container discovery)
|
||||
"""
|
||||
prompt = """
|
||||
Extract technical inventory information from this label image.
|
||||
|
||||
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:
|
||||
return result
|
||||
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:
|
||||
# 1. Try fetching from the configuration file first (SSOT)
|
||||
prompt = prompt_mgr.get_prompt()
|
||||
|
||||
if not prompt:
|
||||
# 2. Fallback to Database if file is missing
|
||||
setting = db.query(models.SystemSetting).filter(models.SystemSetting.key == "ai_extraction_prompt").first()
|
||||
if setting:
|
||||
prompt = setting.value
|
||||
else:
|
||||
# 3. Final fallback to hardcoded default
|
||||
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)
|
||||
|
||||
if result:
|
||||
# Check if AI returned a list of items or a singular object
|
||||
raw_items = result.get("items") or result.get("Items")
|
||||
was_list = isinstance(raw_items, list)
|
||||
items_to_map = raw_items if was_list else [result]
|
||||
|
||||
mapping = {
|
||||
"Item": "name",
|
||||
"Type": "type",
|
||||
"Description": "description",
|
||||
"Category": "category",
|
||||
"Connector": "connector",
|
||||
"Size": "size",
|
||||
"Color": "color",
|
||||
"PartNr": "part_number",
|
||||
"OCR": "ocr_text"
|
||||
}
|
||||
|
||||
mapped_items = []
|
||||
for item_data in items_to_map:
|
||||
final_item = {}
|
||||
for ai_key, model_key in mapping.items():
|
||||
val = item_data.get(ai_key) or item_data.get(model_key)
|
||||
if val and isinstance(val, str):
|
||||
final_item[model_key] = val.strip()
|
||||
else:
|
||||
final_item[model_key] = val
|
||||
|
||||
# Default fields
|
||||
final_item["quantity"] = item_data.get("quantity", 1)
|
||||
raw_barcode = item_data.get("barcode") or item_data.get("PartNr") or item_data.get("part_number") or item_data.get("Part Number")
|
||||
final_item["barcode"] = str(raw_barcode).strip() if raw_barcode else f"AI-{int(time.time()*100)}"
|
||||
|
||||
# Handle Box mode specifically inside mapping
|
||||
if mode == "box":
|
||||
final_item["box_label"] = final_item.get("box_label") or item_data.get("Box") or final_item.get("name") or "Unknown Box"
|
||||
final_item["name"] = final_item["box_label"]
|
||||
|
||||
mapped_items.append(final_item)
|
||||
|
||||
# Return either the whole list wrapper or the first item (legacy compatibility)
|
||||
if was_list:
|
||||
return {"items": mapped_items}
|
||||
return mapped_items[0] if mapped_items else {"error": "No items after mapping"}
|
||||
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
# 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)
|
||||
|
||||
@@ -66,8 +66,7 @@ async def get_current_user(credentials = Depends(security)):
|
||||
token = credentials.credentials
|
||||
import logging
|
||||
log = logging.getLogger("ainventory")
|
||||
log.debug(f"[AUTH] Validating token (first 20 chars): {token[:20] if token else 'None'}")
|
||||
log.debug(f"[AUTH] Using SECRET_KEY (first 10 chars): {SECRET_KEY[:10] if SECRET_KEY else 'None'}")
|
||||
log.debug(f"[AUTH] Validating token (first 10 chars): {token[:10] if token else 'None'}")
|
||||
try:
|
||||
payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
|
||||
user_id: int = int(payload.get("sub")) # sub is stored as string, convert back to int
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
{
|
||||
"_comment": "Copy this file to ldap_config.json and fill in real values. NEVER commit ldap_config.json to Git.",
|
||||
"ldap_enabled": false,
|
||||
"server_uri": "ldap://YOUR_LDAP_SERVER_IP:389",
|
||||
"base_dn": "dc=yourdomain,dc=com",
|
||||
"user_template": "cn={username},ou=people,dc=yourdomain,dc=com",
|
||||
"groups_dn": "ou=groups",
|
||||
"use_tls": false,
|
||||
"role_mappings": [
|
||||
{
|
||||
"group": "inventory_admins",
|
||||
"role": "admin"
|
||||
},
|
||||
{
|
||||
"group": "inventory_users",
|
||||
"role": "user"
|
||||
}
|
||||
]
|
||||
}
|
||||
34
backend/config_loader.py
Normal file
@@ -0,0 +1,34 @@
|
||||
import os
|
||||
import logging
|
||||
from dotenv import load_dotenv
|
||||
|
||||
log = logging.getLogger("ainventory")
|
||||
|
||||
def load_config():
|
||||
"""
|
||||
Centralized environment loader for TFM aInventory.
|
||||
Prioritizes existing environment variables (Docker),
|
||||
then inventory.env at project root, then backend/.env.
|
||||
"""
|
||||
# Base directory is backend/
|
||||
base_dir = os.path.dirname(os.path.abspath(__file__))
|
||||
# Project root is one level up
|
||||
project_root = os.path.dirname(base_dir)
|
||||
|
||||
inventory_env_path = os.path.join(project_root, "inventory.env")
|
||||
backend_env_path = os.path.join(base_dir, ".env")
|
||||
|
||||
# Check for inventory.env in root (Master Config)
|
||||
if os.path.exists(inventory_env_path):
|
||||
load_dotenv(inventory_env_path)
|
||||
log.info(f"✅ Loaded master configuration from {inventory_env_path}")
|
||||
|
||||
# Check for local backend/.env (Legacy/Fragmented)
|
||||
elif os.path.exists(backend_env_path):
|
||||
load_dotenv(backend_env_path)
|
||||
log.info(f"ℹ️ Loaded local configuration from {backend_env_path}")
|
||||
else:
|
||||
log.warning("⚠️ No .env config files found. Relying on system environment variables.")
|
||||
|
||||
# Auto-run if imported
|
||||
load_config()
|
||||
@@ -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)}")
|
||||
|
||||
@@ -23,6 +23,10 @@ export LOGS_DIR="${LOGS_DIR:-/app/logs}"
|
||||
echo "🐳 [Docker] Running data initialization..."
|
||||
bash /app/scripts/init_data.sh
|
||||
|
||||
# Hand off to the application server
|
||||
echo "🐳 [Docker] Starting uvicorn..."
|
||||
exec python -m uvicorn backend.main:app --host 0.0.0.0 --port 8000
|
||||
# Fix permissions for mounted volumes (which might be root-owned by the host)
|
||||
echo "🐳 [Docker] Fixing volume permissions..."
|
||||
chown -R appuser:appuser "${DATA_DIR}" "${LOGS_DIR}"
|
||||
|
||||
# Hand off to the application server as non-root user
|
||||
echo "🐳 [Docker] Starting uvicorn as appuser..."
|
||||
exec gosu appuser python -m uvicorn backend.main:app --host 0.0.0.0 --port 8000
|
||||
|
||||
119
backend/main.py
@@ -1,4 +1,5 @@
|
||||
import os
|
||||
from . import config_loader # This triggers the automatic environment loading
|
||||
from fastapi import FastAPI
|
||||
from fastapi.middleware.cors import CORSMiddleware
|
||||
from slowapi import Limiter
|
||||
@@ -19,15 +20,55 @@ log.info("Database tables verified.")
|
||||
app = FastAPI(title="TFM aInventory API", version="1.1.0")
|
||||
log.info("TFM aInventory API process started.")
|
||||
|
||||
# [SECURITY FIX M-01] CORS: allow_origins=["*"] + allow_credentials=True is invalid per spec.
|
||||
# Allowed origins are configured via ALLOWED_ORIGINS environment variable (comma-separated).
|
||||
# Secure fallback: localhost only for development.
|
||||
_raw_origins = os.environ.get(
|
||||
"ALLOWED_ORIGINS",
|
||||
"http://localhost:3000,http://localhost:3002"
|
||||
)
|
||||
# [SECURITY FIX M-01] CORS Configuration
|
||||
# We dynamically build allowed origins from environment variables to simplify deployment.
|
||||
_raw_origins = os.environ.get("ALLOWED_ORIGINS", "")
|
||||
ALLOWED_ORIGINS = [o.strip() for o in _raw_origins.split(",") if o.strip()]
|
||||
log.info(f"CORS allowed origins: {ALLOWED_ORIGINS}")
|
||||
|
||||
# Automatically add origins based on network_config.env variables if present
|
||||
server_ip = os.environ.get("SERVER_IP")
|
||||
front_port = os.environ.get("FRONTEND_PORT", "8917")
|
||||
front_ssl_port = os.environ.get("FRONTEND_SSL_PORT", "8919")
|
||||
back_ssl_port = os.environ.get("BACKEND_SSL_PORT", "8918")
|
||||
|
||||
# Always allow localhost
|
||||
defaults = [
|
||||
f"http://localhost:{front_port}",
|
||||
f"https://localhost:{front_ssl_port}",
|
||||
f"https://localhost:{back_ssl_port}",
|
||||
]
|
||||
for d in defaults:
|
||||
if d not in ALLOWED_ORIGINS:
|
||||
ALLOWED_ORIGINS.append(d)
|
||||
|
||||
# Add IP-based origins if SERVER_IP is set
|
||||
if server_ip and server_ip != "localhost":
|
||||
ip_origins = [
|
||||
f"http://{server_ip}:{front_port}",
|
||||
f"https://{server_ip}:{front_ssl_port}",
|
||||
f"https://{server_ip}:{back_ssl_port}",
|
||||
]
|
||||
for ip_o in ip_origins:
|
||||
if ip_o not in ALLOWED_ORIGINS:
|
||||
ALLOWED_ORIGINS.append(ip_o)
|
||||
|
||||
# [NEW] Add Extra Allowed Origins (Tailscale, VPN, etc.)
|
||||
extra_origins_raw = os.environ.get("EXTRA_ALLOWED_ORIGINS", "")
|
||||
if extra_origins_raw:
|
||||
for extra_ip in [o.strip() for o in extra_origins_raw.split(",") if o.strip()]:
|
||||
# Generate standard combinations for this extra origin
|
||||
ext_combos = [
|
||||
f"http://{extra_ip}:{front_port}",
|
||||
f"https://{extra_ip}:{front_ssl_port}",
|
||||
f"https://{extra_ip}:{back_ssl_port}",
|
||||
]
|
||||
for combo in ext_combos:
|
||||
if combo not in ALLOWED_ORIGINS:
|
||||
ALLOWED_ORIGINS.append(combo)
|
||||
|
||||
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(
|
||||
@@ -54,6 +95,68 @@ 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 - <field name> : 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 in database.")
|
||||
# Refresh existing after commit
|
||||
existing = db.query(models.SystemSetting).filter(models.SystemSetting.key == "ai_extraction_prompt").first()
|
||||
|
||||
# [NEW] Sync/Initialize AI prompt file in /config
|
||||
from .database import BASE_DIR
|
||||
PROJECT_ROOT = os.path.dirname(BASE_DIR)
|
||||
PROMPT_FILE_PATH = os.path.join(PROJECT_ROOT, "config", "ai_prompt.md")
|
||||
|
||||
if not os.path.exists(PROMPT_FILE_PATH):
|
||||
try:
|
||||
os.makedirs(os.path.dirname(PROMPT_FILE_PATH), exist_ok=True)
|
||||
# Use DB value (which we just ensured exists)
|
||||
current_val = existing.value if existing else final_prompt
|
||||
with open(PROMPT_FILE_PATH, 'w', encoding='utf-8') as f:
|
||||
f.write(current_val)
|
||||
log.info(f"✅ Initialized AI prompt configuration file: {PROMPT_FILE_PATH}")
|
||||
except Exception as fe:
|
||||
log.error(f"❌ Failed to initialize AI prompt file: {fe}")
|
||||
|
||||
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"}
|
||||
|
||||
@@ -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,11 +42,18 @@ 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)
|
||||
image_url = Column(String, nullable=True)
|
||||
|
||||
# Generic box/container association for multi-item OCR scanning
|
||||
box_label = Column(String, index=True, nullable=True)
|
||||
|
||||
# Full AI metadata
|
||||
labels_data = Column(Text, nullable=True)
|
||||
|
||||
@@ -52,6 +65,10 @@ class AuditLog(Base):
|
||||
user_id = Column(Integer, ForeignKey("users.id"))
|
||||
action = Column(String) # e.g., 'CHECK_IN', 'CHECK_OUT', 'CREATE_ITEM'
|
||||
target_item_id = Column(Integer, nullable=True)
|
||||
target_item_name = Column(String, nullable=True)
|
||||
target_item_pn = Column(String, nullable=True)
|
||||
target_item_barcode = Column(String, nullable=True)
|
||||
target_snapshot = Column(Text, nullable=True) # Full JSON snapshot
|
||||
quantity_change = Column(Float, nullable=True)
|
||||
uuid = Column(String, unique=True, index=True, nullable=True)
|
||||
details = Column(Text, nullable=True) # For reasons, sync notes, etc.
|
||||
|
||||
@@ -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,87 @@ 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))
|
||||
|
||||
import os
|
||||
from ..database import BASE_DIR
|
||||
PROJECT_ROOT = os.path.dirname(BASE_DIR)
|
||||
PROMPT_FILE_PATH = os.path.join(PROJECT_ROOT, "config", "ai_prompt.md")
|
||||
|
||||
@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 (prioritizes config file)."""
|
||||
# 1. Try file first
|
||||
if os.path.exists(PROMPT_FILE_PATH):
|
||||
try:
|
||||
with open(PROMPT_FILE_PATH, 'r', encoding='utf-8') as f:
|
||||
return {"value": f.read().strip(), "source": "file"}
|
||||
except Exception as e:
|
||||
print(f"Error reading prompt file: {e}")
|
||||
|
||||
# 2. Fallback to DB
|
||||
setting = db.query(models.SystemSetting).filter(models.SystemSetting.key == "ai_extraction_prompt").first()
|
||||
if not setting:
|
||||
return {"value": "", "source": "none"}
|
||||
return {"value": setting.value, "source": "database"}
|
||||
|
||||
@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 (writes to both file and DB)."""
|
||||
value = payload.get("value")
|
||||
if value is None:
|
||||
raise HTTPException(status_code=400, detail="Value required")
|
||||
|
||||
# 1. Update File (Primary)
|
||||
try:
|
||||
os.makedirs(os.path.dirname(PROMPT_FILE_PATH), exist_ok=True)
|
||||
with open(PROMPT_FILE_PATH, 'w', encoding='utf-8') as f:
|
||||
f.write(value)
|
||||
except Exception as e:
|
||||
print(f"Failed to write prompt file: {e}")
|
||||
# We continue to DB update anyway
|
||||
|
||||
# 2. Update DB (Backup/Sync)
|
||||
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", "file_updated": os.path.exists(PROMPT_FILE_PATH)}
|
||||
|
||||
@@ -18,21 +18,7 @@ def get_categories(
|
||||
current_user: auth.TokenData = Depends(auth.get_current_user)
|
||||
):
|
||||
"""[C-01] List of categories — only for authenticated users."""
|
||||
categories = db.query(models.Category).all()
|
||||
# Auto-seed if empty with defaults mentioned by user
|
||||
if not categories:
|
||||
defaults = [
|
||||
{"name": "Connectors", "description": "Conectica: cables, adapters, plugs"},
|
||||
{"name": "Spare Parts", "description": "Piese de schimb: specific components"},
|
||||
{"name": "Tools", "description": "Hand and power tools"},
|
||||
{"name": "Consumables", "description": "One-time use items"}
|
||||
]
|
||||
for d in defaults:
|
||||
cat = models.Category(**d)
|
||||
db.add(cat)
|
||||
db.commit()
|
||||
return db.query(models.Category).all()
|
||||
return categories
|
||||
return db.query(models.Category).all()
|
||||
|
||||
@router.post("/", response_model=schemas.Category)
|
||||
def create_category(
|
||||
|
||||
@@ -2,6 +2,7 @@ from fastapi import APIRouter, Depends, HTTPException, status, UploadFile, File,
|
||||
from sqlalchemy.orm import Session
|
||||
from sqlalchemy import func
|
||||
from typing import List
|
||||
import json
|
||||
from slowapi import Limiter
|
||||
from slowapi.util import get_remote_address
|
||||
from .. import models, schemas, auth
|
||||
@@ -65,6 +66,7 @@ _MAX_IMAGE_SIZE = 10 * 1024 * 1024 # 10 MB
|
||||
async def extract_label(
|
||||
request: Request,
|
||||
file: UploadFile = File(...),
|
||||
mode: str = "item", # 'item' or 'box'
|
||||
current_user: auth.TokenData = Depends(auth.get_current_user)
|
||||
):
|
||||
"""[C-01] Extract label from image — only for authenticated users. [H-02] Rate limit: 10 req/min per IP."""
|
||||
@@ -85,7 +87,7 @@ async def extract_label(
|
||||
detail="File exceeds 10MB limit."
|
||||
)
|
||||
|
||||
result = extract_label_info(contents)
|
||||
result = extract_label_info(contents, mode=mode)
|
||||
return result
|
||||
|
||||
@router.post("/", response_model=schemas.Item, status_code=status.HTTP_201_CREATED)
|
||||
@@ -95,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)
|
||||
@@ -106,10 +116,27 @@ def create_item(
|
||||
db.refresh(db_item)
|
||||
|
||||
# Audit log the creation — [M-02] user_id from token, not from body
|
||||
# Capture full snapshot
|
||||
item_snapshot = {
|
||||
"barcode": db_item.barcode,
|
||||
"name": db_item.name,
|
||||
"category": db_item.category,
|
||||
"type": db_item.type,
|
||||
"part_number": db_item.part_number,
|
||||
"color": db_item.color,
|
||||
"specs": db_item.specs,
|
||||
"box_label": db_item.box_label,
|
||||
"image_url": db_item.image_url
|
||||
}
|
||||
|
||||
audit = models.AuditLog(
|
||||
user_id=current_user.sub,
|
||||
action="CREATE_ITEM",
|
||||
target_item_id=db_item.id,
|
||||
target_item_name=db_item.name,
|
||||
target_item_pn=db_item.part_number,
|
||||
target_item_barcode=db_item.barcode,
|
||||
target_snapshot=json.dumps(item_snapshot),
|
||||
quantity_change=item.quantity
|
||||
)
|
||||
db.add(audit)
|
||||
@@ -129,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)
|
||||
@@ -141,22 +181,46 @@ def update_item(
|
||||
def delete_item(
|
||||
item_id: int,
|
||||
db: Session = Depends(get_db),
|
||||
current_user: auth.TokenData = Depends(auth.get_current_user)
|
||||
current_user: auth.TokenData = Depends(auth.get_current_admin)
|
||||
):
|
||||
"""[C-01] Delete item — only for authenticated users. InterventionItems are cleared; AuditLogs are KEPT for history."""
|
||||
db_item = db.query(models.Item).filter(models.Item.id == item_id).first()
|
||||
if not db_item:
|
||||
raise HTTPException(status_code=404, detail="Item not found")
|
||||
|
||||
# [AUDIT] Log the deletion to disk file via logger.py
|
||||
# [AUDIT] Log the deletion to database and disk
|
||||
from ..logger import log
|
||||
log.warning(f"USER[{current_user.sub}] DELETING ITEM: ID={item_id}, Name={db_item.name}, PN={db_item.part_number}")
|
||||
|
||||
item_snapshot = {
|
||||
"barcode": db_item.barcode,
|
||||
"name": db_item.name,
|
||||
"category": db_item.category,
|
||||
"type": db_item.type,
|
||||
"part_number": db_item.part_number,
|
||||
"color": db_item.color,
|
||||
"specs": db_item.specs,
|
||||
"box_label": db_item.box_label,
|
||||
"image_url": db_item.image_url,
|
||||
"final_quantity": db_item.quantity
|
||||
}
|
||||
|
||||
audit = models.AuditLog(
|
||||
user_id=current_user.sub,
|
||||
action="DELETE_ITEM",
|
||||
target_item_id=item_id,
|
||||
target_item_name=db_item.name,
|
||||
target_item_pn=db_item.part_number,
|
||||
target_item_barcode=db_item.barcode,
|
||||
target_snapshot=json.dumps(item_snapshot),
|
||||
details=f"Final Quantity: {db_item.quantity}"
|
||||
)
|
||||
db.add(audit)
|
||||
|
||||
# [CLEANUP] Delete related InterventionItems to prevent foreign key issues
|
||||
db.query(models.InterventionItem).filter(models.InterventionItem.item_id == item_id).delete()
|
||||
|
||||
# Audit Logs in database are NOT deleted here to preserve history of actions
|
||||
|
||||
db.delete(db_item)
|
||||
db.commit()
|
||||
return {"message": "Item deleted successfully. History logs preserved."}
|
||||
|
||||
@@ -3,6 +3,7 @@ from typing import List
|
||||
from sqlalchemy.orm import Session
|
||||
from .. import models, schemas, auth
|
||||
from ..database import get_db
|
||||
import json
|
||||
|
||||
router = APIRouter(
|
||||
prefix="/operations",
|
||||
@@ -27,10 +28,21 @@ def check_in_item(
|
||||
item.quantity += op.quantity
|
||||
|
||||
# Create Mandatory Audit Log — [M-02] user_id from token
|
||||
item_snapshot = {
|
||||
"barcode": item.barcode,
|
||||
"name": item.name,
|
||||
"category": item.category,
|
||||
"part_number": item.part_number
|
||||
}
|
||||
|
||||
audit = models.AuditLog(
|
||||
user_id=current_user.sub,
|
||||
action="CHECK_IN",
|
||||
target_item_id=item.id,
|
||||
target_item_name=item.name,
|
||||
target_item_pn=item.part_number,
|
||||
target_item_barcode=item.barcode,
|
||||
target_snapshot=json.dumps(item_snapshot),
|
||||
quantity_change=op.quantity
|
||||
)
|
||||
|
||||
@@ -60,10 +72,21 @@ def check_out_item(
|
||||
item.quantity -= op.quantity
|
||||
|
||||
# Create Mandatory Audit Log
|
||||
item_snapshot = {
|
||||
"barcode": item.barcode,
|
||||
"name": item.name,
|
||||
"category": item.category,
|
||||
"part_number": item.part_number
|
||||
}
|
||||
|
||||
audit = models.AuditLog(
|
||||
user_id=current_user.sub,
|
||||
action="CHECK_OUT",
|
||||
target_item_id=item.id,
|
||||
target_item_name=item.name,
|
||||
target_item_pn=item.part_number,
|
||||
target_item_barcode=item.barcode,
|
||||
target_snapshot=json.dumps(item_snapshot),
|
||||
quantity_change=-op.quantity
|
||||
)
|
||||
|
||||
@@ -93,10 +116,21 @@ def trash_item(
|
||||
item.quantity -= op.quantity
|
||||
|
||||
# Create Mandatory Audit Log with TRASH action and reason in details
|
||||
item_snapshot = {
|
||||
"barcode": item.barcode,
|
||||
"name": item.name,
|
||||
"category": item.category,
|
||||
"part_number": item.part_number
|
||||
}
|
||||
|
||||
audit = models.AuditLog(
|
||||
user_id=current_user.sub,
|
||||
action="TRASH",
|
||||
target_item_id=item.id,
|
||||
target_item_name=item.name,
|
||||
target_item_pn=item.part_number,
|
||||
target_item_barcode=item.barcode,
|
||||
target_snapshot=json.dumps(item_snapshot),
|
||||
quantity_change=-op.quantity,
|
||||
details=op.reason
|
||||
)
|
||||
@@ -130,10 +164,21 @@ def bulk_check_out(
|
||||
item.quantity -= op.quantity
|
||||
|
||||
# Log individual audit for this item
|
||||
item_snapshot = {
|
||||
"barcode": item.barcode,
|
||||
"name": item.name,
|
||||
"category": item.category,
|
||||
"part_number": item.part_number
|
||||
}
|
||||
|
||||
audit = models.AuditLog(
|
||||
user_id=current_user.sub,
|
||||
action="BULK_CHECK_OUT",
|
||||
target_item_id=item.id,
|
||||
target_item_name=item.name,
|
||||
target_item_pn=item.part_number,
|
||||
target_item_barcode=item.barcode,
|
||||
target_snapshot=json.dumps(item_snapshot),
|
||||
quantity_change=-op.quantity
|
||||
)
|
||||
db.add(audit)
|
||||
@@ -182,10 +227,21 @@ def bulk_sync(
|
||||
continue
|
||||
|
||||
# Log audit with original offline timestamp and UUID
|
||||
item_snapshot = {
|
||||
"barcode": item.barcode,
|
||||
"name": item.name,
|
||||
"category": item.category,
|
||||
"part_number": item.part_number
|
||||
}
|
||||
|
||||
audit = models.AuditLog(
|
||||
user_id=current_user.sub,
|
||||
action=op.type,
|
||||
target_item_id=item.id,
|
||||
target_item_name=item.name,
|
||||
target_item_pn=item.part_number,
|
||||
target_item_barcode=item.barcode,
|
||||
target_snapshot=json.dumps(item_snapshot),
|
||||
quantity_change=change,
|
||||
timestamp=op.timestamp,
|
||||
uuid=op.uuid,
|
||||
@@ -215,6 +271,10 @@ def get_logs(
|
||||
models.User.username,
|
||||
models.AuditLog.action,
|
||||
models.AuditLog.target_item_id,
|
||||
models.AuditLog.target_item_name,
|
||||
models.AuditLog.target_item_pn,
|
||||
models.AuditLog.target_item_barcode,
|
||||
models.AuditLog.target_snapshot,
|
||||
models.AuditLog.quantity_change,
|
||||
models.AuditLog.details
|
||||
).join(models.User, models.AuditLog.user_id == models.User.id).order_by(models.AuditLog.timestamp.desc()).limit(limit).all()
|
||||
@@ -228,6 +288,10 @@ def get_logs(
|
||||
"username": l.username,
|
||||
"action": l.action,
|
||||
"target_item_id": l.target_item_id,
|
||||
"target_item_name": l.target_item_name,
|
||||
"target_item_pn": l.target_item_pn,
|
||||
"target_item_barcode": l.target_item_barcode,
|
||||
"target_snapshot": l.target_snapshot,
|
||||
"quantity_change": l.quantity_change,
|
||||
"details": l.details
|
||||
} for l in logs_with_users
|
||||
|
||||