Compare commits
120 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 | ||
|
|
f3d861b1a2 | ||
|
|
5cceba21f4 | ||
|
|
161a182281 | ||
|
|
38e9428109 | ||
|
|
0869ab8cdd | ||
|
|
106f46e9f8 | ||
|
|
955b1e86e5 | ||
|
|
6981cadb57 | ||
|
|
704934165f | ||
|
|
775808506f | ||
|
|
cee93fe53c | ||
|
|
c95d095f9f | ||
|
|
a8d74f3ae8 | ||
|
|
d6e7a8d2a4 | ||
|
|
c1b8d2d8b9 | ||
|
|
02a4951901 | ||
|
|
ac1703e6c2 | ||
|
|
a6d2d176ba | ||
|
|
6e58cce73a | ||
|
|
7d821d1f7b | ||
|
|
c816cb4630 | ||
|
|
3c8d50162b | ||
|
|
483a747600 | ||
|
|
d9e75368fb | ||
|
|
cf528ac161 | ||
|
|
c949bcd211 | ||
|
|
356dfa32f1 | ||
|
|
c2bd8e44fb | ||
|
|
6fede92860 | ||
|
|
427be99f67 | ||
|
|
1767b38373 | ||
|
|
45b0f8b35c | ||
|
|
0b77324a3f | ||
|
|
73115a24ac | ||
|
|
ccc69d92df | ||
|
|
9dbe0f8b6c | ||
|
|
54b40c9d37 | ||
|
|
c31209b740 | ||
|
|
f0de7d763a | ||
|
|
29dee921f9 | ||
|
|
2574726f78 | ||
|
|
e6ca33f2f0 | ||
|
|
e9ada00497 | ||
|
|
9b6adad618 | ||
|
|
247ea45408 | ||
|
|
903c65a4b4 | ||
|
|
cb9df1d73f | ||
|
|
fe18fe01c6 | ||
|
|
91bdd791d2 | ||
|
|
ed06f0b56e | ||
|
|
df9468344d | ||
|
|
d87067e88c | ||
|
|
2e5ce8d153 | ||
|
|
c71a815792 | ||
|
|
0a9ea0f575 | ||
|
|
432cd28ca5 | ||
|
|
96fe655f72 | ||
|
|
aa134e4384 | ||
|
|
a4cea4ce07 | ||
|
|
d7dcd523a6 | ||
|
|
4136d49936 | ||
|
|
99b70a9de8 | ||
|
|
9c76c0cbf5 | ||
|
|
80af77f81a | ||
|
|
8bf1945acc | ||
|
|
7d7bdc727d | ||
|
|
8a3783c7e9 |
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
|
||||||
39
.claude/settings.local.json
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
{
|
||||||
|
"permissions": {
|
||||||
|
"allow": [
|
||||||
|
"mcp__plugin_context-mode_context-mode__ctx_batch_execute",
|
||||||
|
"mcp__plugin_context-mode_context-mode__ctx_search",
|
||||||
|
"Bash(git add:*)",
|
||||||
|
"Bash(git commit -m ':*)",
|
||||||
|
"mcp__plugin_context-mode_context-mode__ctx_execute",
|
||||||
|
"Bash(git commit -m 'docs: update SESSION_STATE cu status [C-01] JWT auth COMPLET:*)",
|
||||||
|
"Bash(git commit -m 'docs: final SESSION_STATE — ALL TASKS COMPLETE v1.3.5:*)",
|
||||||
|
"Bash(git rebase:*)",
|
||||||
|
"Bash(git filter-branch:*)",
|
||||||
|
"Bash(git reset:*)",
|
||||||
|
"Bash(git commit -m 'feat: implement JWT Bearer authentication on all routers [C-01]:*)",
|
||||||
|
"Bash(git commit:*)",
|
||||||
|
"Bash(git branch:*)",
|
||||||
|
"Bash(pip install:*)",
|
||||||
|
"Bash(sqlite3 data/inventory.db \"SELECT username, role, origin, hashed_password FROM users;\")",
|
||||||
|
"Bash(sqlite3 data/inventory.db \".schema users\")",
|
||||||
|
"Bash(sqlite3 data/inventory.db \"SELECT * FROM users;\")",
|
||||||
|
"Bash(git restore:*)",
|
||||||
|
"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 *)"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
92
.gitignore
vendored
@@ -1,6 +1,94 @@
|
|||||||
|
# ============================================================
|
||||||
|
# TFM aInventory — .gitignore
|
||||||
|
# ============================================================
|
||||||
|
|
||||||
|
# ── Python environments ──────────────────────────────────────
|
||||||
|
.venv/
|
||||||
backend/venv/
|
backend/venv/
|
||||||
backend/data/
|
**/__pycache__/
|
||||||
__pycache__/
|
|
||||||
*.pyc
|
*.pyc
|
||||||
|
*.pyo
|
||||||
|
*.pyd
|
||||||
|
*.egg-info/
|
||||||
|
dist/
|
||||||
|
build/
|
||||||
|
|
||||||
|
# ── Runtime data directories ─────────────────────────────────
|
||||||
|
# Content is excluded; the directories themselves are tracked via .gitkeep.
|
||||||
|
# On fresh clone: run ./start_server.sh or docker compose up to initialize.
|
||||||
|
|
||||||
|
/data/*
|
||||||
|
!/data/.gitkeep
|
||||||
|
/data/backups/
|
||||||
|
|
||||||
|
/logs/*
|
||||||
|
!/logs/.gitkeep
|
||||||
|
|
||||||
|
# Duplicate runtime dirs that may exist inside backend/ (Docker legacy)
|
||||||
|
backend/data/
|
||||||
|
backend/logs/
|
||||||
|
|
||||||
|
# ── Sensitive configuration files ────────────────────────────
|
||||||
|
# The ACTIVE LDAP config is: backend/config/ldap_config.json
|
||||||
|
# It contains real server IPs and credentials — never commit.
|
||||||
|
# The template/example IS committed and used by init_data.sh on fresh installs.
|
||||||
|
backend/config/ldap_config.json
|
||||||
|
!backend/config/ldap_config.json.example
|
||||||
|
|
||||||
|
# ── Environment files (secrets) ──────────────────────────────
|
||||||
.env
|
.env
|
||||||
|
.env.*
|
||||||
|
inventory.env
|
||||||
|
inventory.env.*
|
||||||
|
!.env.example
|
||||||
|
!inventory.env.example
|
||||||
|
backend/.env
|
||||||
|
backend/.env.*
|
||||||
|
!backend/.env.example
|
||||||
|
# Docker environment override file
|
||||||
|
docker-compose.override.yml
|
||||||
|
.env.docker
|
||||||
|
|
||||||
|
# ── Application logs ─────────────────────────────────────────
|
||||||
|
# (also covered by /logs/* above, these catch any other locations)
|
||||||
|
frontend/logs/
|
||||||
|
*.log
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
|
||||||
|
# ── Frontend build artifacts ──────────────────────────────────
|
||||||
|
frontend/.next/
|
||||||
|
frontend/out/
|
||||||
|
frontend/build/
|
||||||
|
frontend/node_modules/
|
||||||
|
|
||||||
|
# ── Frontend generated/runtime assets ────────────────────────
|
||||||
|
# SSL certs and runtime configs (generated by start_server.sh)
|
||||||
|
frontend/config/
|
||||||
|
# PWA icons generated at build time
|
||||||
|
frontend/public/icons/
|
||||||
|
|
||||||
|
# ── npm / npx caches ─────────────────────────────────────────
|
||||||
|
.npx_cache/
|
||||||
|
scratch/npm_cache/
|
||||||
|
|
||||||
|
# ── Production bundles (generated by export_prod.sh) ─────────
|
||||||
|
aInventory-PROD*/
|
||||||
|
aInventory-PROD*.zip
|
||||||
|
|
||||||
|
# ── AI / IDE metadata ─────────────────────────────────────────
|
||||||
|
.remember/
|
||||||
|
.claude/
|
||||||
|
|
||||||
|
# ── macOS system files ────────────────────────────────────────
|
||||||
.DS_Store
|
.DS_Store
|
||||||
|
**/.DS_Store
|
||||||
|
|
||||||
|
# ── Certificates & keys ──────────────────────────────────────
|
||||||
|
*.pem
|
||||||
|
*.key
|
||||||
|
*.crt
|
||||||
|
*.cert
|
||||||
|
|
||||||
|
__push_ALL_to_remote.sh
|
||||||
|
|||||||
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
|
||||||
87
AI_RULES.md
@@ -1,38 +1,61 @@
|
|||||||
# AI AGENT RULES - SINGLE SOURCE OF TRUTH
|
# AI AGENT RULES - MANDATORY SSOT ENTRY POINT
|
||||||
|
|
||||||
This file is the single source of truth for ALL Artificial Intelligence agents working on this project (Claude, Gemini, etc.).
|
**READ THIS ENTIRE FILE BEFORE EXECUTING ANY TASK.**
|
||||||
Any AI or session MUST respect these mandatory rules.
|
This is the **Single Source of Truth** for ALL AI agents. Refer to [PROJECT_ARCHITECTURE.md](PROJECT_ARCHITECTURE.md) for technical logic.
|
||||||
|
|
||||||
## General Rules
|
---
|
||||||
- **UI & Code Language**: All web interfaces, functions, variables, and any text inside the application MUST BE DIRECTLY AND ONLY IN ENGLISH.
|
|
||||||
- **Communication Language**: Conversation with the user will be in Romanian or English, preferably Romanian.
|
|
||||||
|
|
||||||
## Multi-AI Coordination & Handover
|
## 1. AI MEMORY, TRACEABILITY & HANDOVER
|
||||||
- **MANDATORY STARTUP**: Every AI session MUST first read `dev_docs/SESSION_STATE.md` to understand current context.
|
- **MANDATORY STARTUP**: Read `dev_docs/SESSION_STATE.md` immediately at session start.
|
||||||
- **MANDATORY HANDOVER**: At the end of every task/session, create or update a handover note in `dev_docs/SESSION_STATE.md`. Specify: **Active AI**, **Current Status**, **Technical Context** (details not in code), and **Next Steps**.
|
- **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`.
|
||||||
- **ARCHIVAL RULE**: To prevent `SESSION_STATE.md` from bloating, incoming AI agents MUST move all content of the *previous* session handover into the top of `dev_docs/SESSION_HISTORY.md` before writing their own new state. This keeps `SESSION_STATE.md` focused only on the *active* session.
|
- **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**.
|
||||||
- **NO INTERACTION OVERLAP**: Never modify a file if another AI session is explicitly working on it according to `SESSION_STATE.md`.
|
- **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.
|
||||||
|
|
||||||
## Implementation Completion
|
## 2. ENGINEERING & OPERATIONAL LAWS
|
||||||
- All code modifications MUST be committed in git before the task is considered finished. `VERSION.json` must be updated on EACH commit.
|
- **ENGLISH ONLY**: Interfaces, code, variables, and docs MUST be in English. Translate any Romanian text found in code immediately. (User conversation: Romanian/English).
|
||||||
- **MANDATORY GIT RULE:** Never push to remote unless explicitly requested by the user. Only commit locally with proper messages. Always assume the user will handle all `git push` operations. If a task requires pushing, ask for explicit permission first.
|
- **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).
|
||||||
- **MANDATORY GIT RULE**: NO AI is allowed to write in git commits that it is the author or co-author (e.g., DO NOT add texts like `Co-Authored-By: AI...`). Commits should only contain technical messages.
|
- **VERSIONING**: Update `VERSION.json` on every commit. Use `scripts/save_version.py` for automated releases.
|
||||||
- **MANDATORY LOGGING**: Any modification of code, architecture, or logic MUST be documented in `dev_docs/ARCHIVE_LOGS.md` at the end of the task. The log must include modified files, purpose of modification, and (if applicable) test results.
|
- **DEPENDENCIES**: Update `backend/requirements.txt` with version constraints for every new pip package.
|
||||||
- **MANDATORY PLAN ARCHIVING**: Once a phase or task is confirmed as completed and verified, the Architect (Gemini/AI) MUST move these entries from `PLAN.md` into `dev_docs/PLAN_HISTORY.md`. This maintains the active plan's focus and prevents document bloat.
|
- **SSOT INTEGRITY**: Every feature change MUST update: `README.md`, `USER_GUIDE.md`, `PROJECT_ARCHITECTURE.md`, and `export_prod.sh`.
|
||||||
- After finishing an entire job, end your final response on a separate line exactly with:
|
|
||||||
```
|
|
||||||
---
|
|
||||||
✓ Done.
|
|
||||||
```
|
|
||||||
- Do not provide unnecessary summaries of the code.
|
|
||||||
|
|
||||||
## Triple Confirmation & Safety Limits
|
## 3. UI/UX "PREMIUM" FIDELITY STANDARDS
|
||||||
- **Safe Forget**: You cannot delete a physical location, item, or critical entity without a triple confirmation mechanism.
|
- **Aesthetics**: Density/aesthetics must remain "Premium". Use Tailwind CSS. NO simplification.
|
||||||
- **No Force Operations**: Never use destructive flags (e.g., `git reset --hard`, `git push --force`, `rm -rf`) without explicit user permission.
|
- **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`.
|
||||||
|
|
||||||
## UI/UX & Documentation Rules
|
## 4. DATA INTEGRITY & AUDIT POLICY
|
||||||
- **STRICT ENGLISH POLICY**: ANY TEXT within the application (documentation, scripts, CLI, UI, internal comments, logs, etc.) MUST be strictly in English. NO Romanian characters (ăâîșț) or words are allowed in the repository.
|
- **RESTRICTED ACTIONS**: `DELETE /items/` and Admin settings require `auth.get_current_admin`.
|
||||||
- **MANDATORY TRANSLATION**: Any Romanian text found during an AI session (in any file) MUST be translated to English immediately.
|
- **AUDIT IMMUTABILITY**: Deleting an `Item` MUST NOT delete its `AuditLog` entries.
|
||||||
- Use Bootstrap Icons (`<i class="bi bi-something"></i>`); never use emojis.
|
- **TRACEABILITY**: Log deletions to `logs/backend.log` with `USER[id]`, `ITEM[id]`, `Name`, `PN`.
|
||||||
- The UI must remain fully functional offline (no external CDNs).
|
- **CONFIRMATION**:
|
||||||
- **MANDATORY UI FIDELITY**: Any modification to UI components (headers, banners, cards, buttons) MUST comply with the specifications in `dev_docs/UI_FIDELITY_SPEC.md` to prevent regressions. All agents MUST read that document before modifying frontend code.
|
- **Triple Confirmation**: Deleting critical entities (Locations/Items) requires user confirmation 3 times.
|
||||||
|
- **Native Alerts**: Use `window.confirm` for all destructive UI actions and Logout.
|
||||||
|
|
||||||
|
## 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`).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## END OF SESSION PROTOCOL
|
||||||
|
End your final response on a separate line exactly with:
|
||||||
|
```
|
||||||
|
---
|
||||||
|
✓ Done.
|
||||||
|
```
|
||||||
|
|||||||
14
CLAUDE.md
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
# CLAUDE ENTRY POINT
|
||||||
|
|
||||||
|
You are Claude, operating on the TFM aInventory project.
|
||||||
|
|
||||||
|
**IMMEDIATE MANDATORY ACTION:**
|
||||||
|
Before taking any action or writing code, you MUST read the following Single Source of Truth files:
|
||||||
|
1. `AI_RULES.md` (Contains your operational constraints, Git rules, and UI fidelity laws).
|
||||||
|
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.
|
||||||
14
GEMINI.md
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
# GEMINI ENTRY POINT
|
||||||
|
|
||||||
|
You are Gemini (Antigravity), operating on the TFM aInventory project.
|
||||||
|
|
||||||
|
**IMMEDIATE MANDATORY ACTION:**
|
||||||
|
Before taking any action or writing code, you MUST read the following Single Source of Truth files:
|
||||||
|
1. `AI_RULES.md` (Contains your operational constraints, Git rules, and UI fidelity laws).
|
||||||
|
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.
|
||||||
34
PLAN.md
@@ -1,31 +1,9 @@
|
|||||||
# Inventory PWA System Implementation Plan
|
# Active Development Plan
|
||||||
|
|
||||||
This document outlines the technical architecture for the unified Inventory System (FastAPI + PWA).
|
This document tracks the immediate implementation checklist. For overarching design and constraints, see [PROJECT_ARCHITECTURE.md](PROJECT_ARCHITECTURE.md).
|
||||||
|
|
||||||
## Proposed Architecture
|
## Implementation Status
|
||||||
|
- [ ] **Phase 8: Database Encryption** (Implementing SQLCipher for data-at-rest protection).
|
||||||
|
- [ ] **Phase 9: Multi-Location Support** (Tracking inventory across different physical warehouses).
|
||||||
|
|
||||||
### 1. Backend Server (Linux / Docker)
|
*(Note: Completed phases are periodically moved to `dev_docs/PLAN_HISTORY.md` according to AI_RULES)*
|
||||||
- **Framework:** Python + FastAPI
|
|
||||||
- **Database:** SQLite (SQLAlchemy ORM)
|
|
||||||
- **Key Modules:**
|
|
||||||
- `Items / Interventions`: Standard CRUD logic.
|
|
||||||
- `Audit`: Every payload that mutates data writes an immutable log row.
|
|
||||||
- `AI-OCR`: Endpoint integrating Google Gemini Vision API (via Google AI Studio key) for complex label extraction onboarding.
|
|
||||||
|
|
||||||
### 2. Unified Web Application (PWA)
|
|
||||||
- **Framework:** React + Next.js (or equivalent).
|
|
||||||
- **Offline Engine:** Service Workers, IndexedDB local storage map.
|
|
||||||
- **Desktop Mode:** Full width dashboard, management, and settings.
|
|
||||||
- **Mobile Mode (Browser / Add to Homescreen):** Dedicated full-screen scanner using `html5-qrcode`.
|
|
||||||
|
|
||||||
### 3. AI Cost Strategy
|
|
||||||
- **Routine Scans (Check-ins/Outs):** Client-side HTML5 barcode scanner. **Cost: $0.**
|
|
||||||
- **Label Scanning (New Item):** Proxies a request to the Gemini API to parse the SFP label into JSON template fields.
|
|
||||||
|
|
||||||
## Implementation Phases
|
|
||||||
|
|
||||||
- **Phase 1: Database & Backend Foundation** - SQLite schemas, User management, and Python API structures.
|
|
||||||
- **Phase 2: Core Inventory API** - Endpoints to Add/Remove items, offline sync-merge logic, Audit triggers.
|
|
||||||
- **Phase 3: The PWA Frontend** - PWA manifest, offline capabilities, UI scaffolding for Desktop/Mobile.
|
|
||||||
- **Phase 4: Client-Side Scanning** - Integration of local JS barcode reading for routine stock scanning.
|
|
||||||
- **Phase 5: Gemini AI Vision Integration** - Server-side Gemini API integration and the text-mapping frontend view.
|
|
||||||
|
|||||||
105
PROJECT_ARCHITECTURE.md
Normal file
@@ -0,0 +1,105 @@
|
|||||||
|
# TFM aInventory - Project Architecture & Requirements
|
||||||
|
|
||||||
|
This document is the **Single Source of Truth** for the project's technical architecture, business requirements, and core logic.
|
||||||
|
|
||||||
|
## 1. Application Overview
|
||||||
|
A unified system to maintain an inventory of "items" and their quantities, inclusive of a web administration interface, offline field operations, audit logging, and AI-powered label extraction functionalities.
|
||||||
|
|
||||||
|
## 2. Technical Stack
|
||||||
|
### 2.1 Backend (API & Data)
|
||||||
|
- **Language:** Python 3.12+ (Optimized for performance and type safety)
|
||||||
|
- **Framework:** FastAPI (Async ASGI)
|
||||||
|
- **Database:** SQLite (SQLAlchemy) - Local file-based persistence
|
||||||
|
- **Validation:** Pydantic v2
|
||||||
|
- **Auth:** Hybrid LDAP (python-ldap) + PBKDF2 local password hash caching
|
||||||
|
- **AI Engine:** Google GenAI SDK (Gemini 2.0 Flash) - Location: `backend/ai/`
|
||||||
|
|
||||||
|
### 2.2 Frontend (Web & PWA)
|
||||||
|
- **Architecture:** Next.js 15+ (App Router)
|
||||||
|
- **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:** `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, Box Label (Association).
|
||||||
|
- **Category:** Predefined groups for organizational structure.
|
||||||
|
- **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
|
||||||
|
- **Hardware Access:** Direct `MediaStreamTrack` access. Zoom cycle: 1x -> 2x -> Max/2 -> Max.
|
||||||
|
- **Image Pre-processing:** Rescaling (1200px), 60% Center Crop, Grayscale/Contrast filters, JPEG (`0.85` quality).
|
||||||
|
- **OCR Mode:** Fully automated. Cycles every 4 seconds without user intervention. Visual countdown shown in controls panel.
|
||||||
|
- **UI Layout:** Camera viewport is always unobstructed. Controls (Zoom + countdown status) are displayed in a dedicated section below the viewport.
|
||||||
|
- **OCR Matching Engine (`page.tsx`):**
|
||||||
|
- 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
|
||||||
|
To prevent data loss in basements or unstable networks:
|
||||||
|
- **Offline Engine:** Service Workers cache assets. IndexedDB saves data.
|
||||||
|
- **UUID Labeling:** Every sync operation generated offline is tagged with a client-side UUID.
|
||||||
|
- **Idempotent Backend:** The `bulk_sync` endpoint checks UUIDs against `AuditLog` before applying increments, preventing double-counts.
|
||||||
|
|
||||||
|
## 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.
|
||||||
103
README.md
Normal file
@@ -0,0 +1,103 @@
|
|||||||
|
# TFM aInventory (2026 Edition)
|
||||||
|
|
||||||
|
A unified, offline-first Inventory Management System built as a Progressive Web App (PWA). Features include AI-powered label extraction (OCR), local barcode/QR scanning, and multi-user authentication with LDAP support.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🛠 Project Modes
|
||||||
|
|
||||||
|
This project supports three distinct operational modes:
|
||||||
|
|
||||||
|
### 1. 🚀 Development Mode (Bare-Metal)
|
||||||
|
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: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: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>:8909
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📦 Production Distribution & Versioning
|
||||||
|
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.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) 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).
|
||||||
|
|
||||||
|
For more details, see [PROJECT_ARCHITECTURE.md](PROJECT_ARCHITECTURE.md).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🔐 Security & Production Deployment
|
||||||
|
|
||||||
|
### Critical Environment Variables
|
||||||
|
The application requires the following environment variables for production deployment:
|
||||||
|
|
||||||
|
| Variable | Purpose | Example |
|
||||||
|
|----------|---------|---------|
|
||||||
|
| **JWT_SECRET_KEY** | JWT token signing key (REQUIRED for production) | `openssl rand -hex 32` |
|
||||||
|
| **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` |
|
||||||
|
|
||||||
|
**⚠️ IMPORTANT:**
|
||||||
|
- In development, `JWT_SECRET_KEY` defaults to an ephemeral random value, which is reset on restart.
|
||||||
|
- For production, set `JWT_SECRET_KEY` to a stable, long random string and store it in a secrets manager (AWS Secrets, HashiCorp Vault, etc.).
|
||||||
|
- `ALLOWED_ORIGINS` **must** be set to your actual production domain(s). Wildcard origins (`*`) are rejected when `allow_credentials=True`.
|
||||||
|
|
||||||
|
### Docker Production Deployment
|
||||||
|
```bash
|
||||||
|
# Set environment variables
|
||||||
|
export JWT_SECRET_KEY="$(openssl rand -hex 32)"
|
||||||
|
export ALLOWED_ORIGINS="https://your-domain.com"
|
||||||
|
|
||||||
|
# Launch stack
|
||||||
|
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).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📜 AI Operational Rules
|
||||||
|
AI agents working on this project MUST follow the guidelines in [AI_RULES.md](AI_RULES.md).
|
||||||
153
USER_GUIDE.md
Normal file
@@ -0,0 +1,153 @@
|
|||||||
|
# TFM aInventory - User Guide
|
||||||
|
|
||||||
|
Welcome to **TFM aInventory**, the unified inventory management system. This guide explains how to use the application for managing your inventory.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📱 Installing on Mobile (PWA)
|
||||||
|
|
||||||
|
The application is a **Progressive Web App**, which means you don't need to download it from the App Store or Google Play.
|
||||||
|
|
||||||
|
1. Open the application URL in your browser (e.g., Safari on iOS or Chrome on Android).
|
||||||
|
2. Tap the **Share** button (iOS) or the **three dots menu** (Android).
|
||||||
|
3. Select **"Add to Home Screen"**.
|
||||||
|
4. The application will now appear as an icon on your home screen and run in immersive mode (without browser chrome).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🔐 Authentication
|
||||||
|
|
||||||
|
- **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 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.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🔍 Scanning and Adding Items
|
||||||
|
|
||||||
|
The application supports two scanning modes:
|
||||||
|
|
||||||
|
### Manual / Barcode Scanning
|
||||||
|
Scan an existing barcode to locate or update an item in your inventory.
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
The inventory is organized in a hierarchical structure:
|
||||||
|
|
||||||
|
- **Categories:** Broad groupings (e.g., Connectors, Spare Parts, Tools, Consumables).
|
||||||
|
- **Items:** Individual products within categories, identified by barcode.
|
||||||
|
- **Item Properties:** Name, part number, color, technical specifications, and quantity.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📶 Offline Operation
|
||||||
|
|
||||||
|
The application is designed to work even when you don't have internet connectivity in your warehouse or field location:
|
||||||
|
|
||||||
|
- **Offline Data:** All item data, categories, and your pending operations are stored locally on your device using IndexedDB.
|
||||||
|
- **Automatic Sync:** When you return to an area with internet connectivity, pending check-ins, check-outs, and other operations are automatically synchronized with the server.
|
||||||
|
- **UUID Tracking:** Each offline operation is tagged with a unique ID to prevent duplicates during synchronization.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📜 Activity Log (Audit Trail)
|
||||||
|
|
||||||
|
All actions (additions, modifications, deletions) are recorded in real-time with your user ID and timestamp. You can review the activity history in the **Logs** section to see:
|
||||||
|
|
||||||
|
- Who performed the action
|
||||||
|
- What action was performed (Check-in, Check-out, Item creation, etc.)
|
||||||
|
- When the action occurred
|
||||||
|
- The item affected and quantity changed
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ⚙️ Admin Functions
|
||||||
|
|
||||||
|
### User Management
|
||||||
|
Administrators can:
|
||||||
|
- View all system users
|
||||||
|
- Create new users (local or LDAP-integrated)
|
||||||
|
- Modify user roles (admin or standard user)
|
||||||
|
- Delete users (except the default Admin account)
|
||||||
|
|
||||||
|
### LDAP Configuration
|
||||||
|
If your organization uses LDAP/Active Directory, administrators can:
|
||||||
|
- Configure LDAP server connection details
|
||||||
|
- Set up role mapping (group membership → admin/user roles)
|
||||||
|
- Test LDAP connectivity
|
||||||
|
|
||||||
|
### 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
|
||||||
|
|
||||||
|
- **Do not share your login credentials** with other users. Each user should have their own account.
|
||||||
|
- **Logout when done:** Always log out when finished to protect your account.
|
||||||
|
- **Report suspicious activity:** If you notice unauthorized changes in the audit log, contact your system administrator immediately.
|
||||||
|
- **API Security:** The application uses JWT (JSON Web Tokens) for API authentication. Tokens are valid for 8 hours.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ❓ Troubleshooting
|
||||||
|
|
||||||
|
### "Insufficient Stock" Error
|
||||||
|
You attempted to check out more items than are currently in inventory. Check the current stock level and try again with a valid quantity.
|
||||||
|
|
||||||
|
### Offline Mode Not Syncing
|
||||||
|
Ensure you have internet connectivity and wait a moment. Synchronization happens automatically when the connection is re-established. You can manually refresh the page to trigger an immediate sync.
|
||||||
|
|
||||||
|
### Login Failed
|
||||||
|
- Verify your username and password are correct.
|
||||||
|
- If using LDAP, ensure your domain credentials are correct and the server is reachable.
|
||||||
|
- Check with your system administrator if you cannot reset your password.
|
||||||
|
|
||||||
|
### AI Label Extraction Not Working
|
||||||
|
- Ensure adequate lighting when photographing the label.
|
||||||
|
- The label image must be clear and not blurry.
|
||||||
|
- The image size must not exceed 10 MB.
|
||||||
|
- The application supports JPEG, PNG, WebP, and GIF formats.
|
||||||
|
- If the AI service is unavailable, try again later or contact your administrator.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📞 Technical Support
|
||||||
|
|
||||||
|
For technical assistance, contact your system administrator or email: `support@example.com`
|
||||||
|
|
||||||
|
For detailed technical documentation, see the [Project Architecture](../PROJECT_ARCHITECTURE.md) guide.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Version:** v1.9.19
|
||||||
|
**Last Updated:** 2026-04-14
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
{
|
|
||||||
"version": "0.1.0",
|
|
||||||
"last_updated": "2026-04-10"
|
|
||||||
}
|
|
||||||
BIN
_images.tests/52DDFDF7-B3E1-4E2B-AC0E-FAE614AB3426_1_102_o.jpeg
Normal file
|
After Width: | Height: | Size: 945 KiB |
BIN
_images.tests/FD8A3A50-1BDC-4094-BD7F-8B7BD83AFD81_1_102_a.jpeg
Normal file
|
After Width: | Height: | Size: 706 KiB |
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 |
41
backend/Dockerfile
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
FROM python:3.12-slim
|
||||||
|
|
||||||
|
# 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
|
||||||
|
|
||||||
|
# Copy requirements and install
|
||||||
|
COPY backend/requirements.txt .
|
||||||
|
RUN pip install --no-cache-dir -r requirements.txt
|
||||||
|
|
||||||
|
# Create non-root user
|
||||||
|
RUN adduser --system --group appuser
|
||||||
|
|
||||||
|
# Copy application files
|
||||||
|
COPY backend ./backend
|
||||||
|
|
||||||
|
# Copy initialization scripts (shared with start_server.sh)
|
||||||
|
COPY scripts ./scripts
|
||||||
|
|
||||||
|
# We define the data dir explicitly for Docker
|
||||||
|
ENV DATA_DIR="/app/data"
|
||||||
|
ENV LOGS_DIR="/app/logs"
|
||||||
|
|
||||||
|
# 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
|
||||||
|
|
||||||
|
EXPOSE 8000
|
||||||
|
|
||||||
|
EXPOSE 8000
|
||||||
|
|
||||||
|
# Entrypoint runs init_data.sh first, then starts uvicorn
|
||||||
|
ENTRYPOINT ["/app/backend/entrypoint.sh"]
|
||||||
0
backend/ai/__init__.py
Normal file
47
backend/ai/claude.py
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
import os
|
||||||
|
import anthropic
|
||||||
|
import json
|
||||||
|
import base64
|
||||||
|
|
||||||
|
def extract(image_bytes: bytes, prompt: str):
|
||||||
|
api_key = os.environ.get("CLAUDE_API_KEY")
|
||||||
|
if not api_key:
|
||||||
|
return None
|
||||||
|
|
||||||
|
client = anthropic.Anthropic(api_key=api_key)
|
||||||
|
base64_image = base64.b64encode(image_bytes).decode('utf-8')
|
||||||
|
|
||||||
|
# 2026 Target models
|
||||||
|
models_to_try = ["claude-3-5-haiku-latest", "claude-3-5-sonnet-latest"]
|
||||||
|
|
||||||
|
for model_name in models_to_try:
|
||||||
|
try:
|
||||||
|
print(f"Claude Attempt: {model_name}")
|
||||||
|
message = client.messages.create(
|
||||||
|
model=model_name,
|
||||||
|
max_tokens=1024,
|
||||||
|
messages=[{
|
||||||
|
"role": "user",
|
||||||
|
"content": [
|
||||||
|
{
|
||||||
|
"type": "image",
|
||||||
|
"source": {
|
||||||
|
"type": "base64",
|
||||||
|
"media_type": "image/jpeg",
|
||||||
|
"data": base64_image,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{"type": "text", "text": prompt}
|
||||||
|
],
|
||||||
|
}]
|
||||||
|
)
|
||||||
|
text = message.content[0].text.strip()
|
||||||
|
|
||||||
|
if "```json" in text:
|
||||||
|
text = text.split("```json")[1].split("```")[0].strip()
|
||||||
|
|
||||||
|
return json.loads(text)
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Claude {model_name} failed: {e}")
|
||||||
|
continue
|
||||||
|
return None
|
||||||
81
backend/ai/gemini.py
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
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():
|
||||||
|
# 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:
|
||||||
|
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"
|
||||||
|
log.info(f"🔑 Using API Key: {key_hint}")
|
||||||
|
|
||||||
|
try:
|
||||||
|
# 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
|
||||||
|
log.debug("🔍 Checking available models for your key...")
|
||||||
|
try:
|
||||||
|
for m in client.models.list():
|
||||||
|
log.debug(f" - Found: {m.name}")
|
||||||
|
except Exception as 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:
|
||||||
|
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(
|
||||||
|
model=model_name,
|
||||||
|
contents=[
|
||||||
|
prompt,
|
||||||
|
types.Part.from_bytes(data=image_bytes, mime_type="image/jpeg")
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
if not response or not response.text:
|
||||||
|
log.warning(f"⚠️ Gemini {model_name} returned NO text.")
|
||||||
|
continue
|
||||||
|
|
||||||
|
text = response.text.strip()
|
||||||
|
log.info(f"✅ AI Response Received ({len(text)} bytes)")
|
||||||
|
log.debug(f"FULL RESPONSE:\n{text}")
|
||||||
|
|
||||||
|
# Extract JSON block
|
||||||
|
if "```json" in text:
|
||||||
|
text = text.split("```json")[1].split("```")[0].strip()
|
||||||
|
elif "```" in text:
|
||||||
|
text = text.split("```")[1].strip()
|
||||||
|
|
||||||
|
return json.loads(text)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
log.error(f"❌ Gemini {model_name} failed: {e}")
|
||||||
|
continue
|
||||||
|
|
||||||
|
except Exception as outer_e:
|
||||||
|
log.error(f"❌ Gemini Client Init failed: {outer_e}")
|
||||||
|
|
||||||
|
return None
|
||||||
136
backend/ai_vision.py
Normal file
@@ -0,0 +1,136 @@
|
|||||||
|
import os
|
||||||
|
import time
|
||||||
|
from dotenv import load_dotenv
|
||||||
|
from . import models
|
||||||
|
from .database import SessionLocal
|
||||||
|
from .ai import gemini, claude
|
||||||
|
|
||||||
|
# Note: Environment variables are managed centrally by config_loader.py
|
||||||
|
base_dir = os.path.dirname(os.path.abspath(__file__))
|
||||||
|
|
||||||
|
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.
|
||||||
|
Modes: 'item' (full technical extraction), 'box' (container discovery)
|
||||||
|
"""
|
||||||
|
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)
|
||||||
|
if result:
|
||||||
|
return result
|
||||||
|
|
||||||
|
return {"error": "All AI providers failed or no API keys configured. Check your .env file."}
|
||||||
105
backend/auth.py
Normal file
@@ -0,0 +1,105 @@
|
|||||||
|
"""
|
||||||
|
[C-01] JWT Authentication Module
|
||||||
|
Implement Bearer token authentication for API endpoints.
|
||||||
|
"""
|
||||||
|
import os
|
||||||
|
from datetime import datetime, timedelta, timezone
|
||||||
|
from typing import Optional
|
||||||
|
from fastapi import Depends, HTTPException, status
|
||||||
|
from fastapi.security import HTTPBearer
|
||||||
|
from jose import JWTError, jwt
|
||||||
|
from pydantic import BaseModel
|
||||||
|
|
||||||
|
# Configuration
|
||||||
|
SECRET_KEY = os.environ.get("JWT_SECRET_KEY")
|
||||||
|
if not SECRET_KEY:
|
||||||
|
# Generate fallback key for dev (NOT FOR PRODUCTION)
|
||||||
|
import secrets
|
||||||
|
SECRET_KEY = secrets.token_urlsafe(32)
|
||||||
|
import sys
|
||||||
|
print(f"[WARNING] JWT_SECRET_KEY not set. Generated ephemeral key: {SECRET_KEY[:20]}...", file=sys.stderr)
|
||||||
|
|
||||||
|
ALGORITHM = "HS256"
|
||||||
|
ACCESS_TOKEN_EXPIRE_MINUTES = 480 # 8 hours
|
||||||
|
|
||||||
|
security = HTTPBearer()
|
||||||
|
|
||||||
|
|
||||||
|
class TokenData(BaseModel):
|
||||||
|
sub: int # user_id
|
||||||
|
username: str
|
||||||
|
role: str
|
||||||
|
exp: datetime
|
||||||
|
|
||||||
|
|
||||||
|
class TokenResponse(BaseModel):
|
||||||
|
access_token: str
|
||||||
|
token_type: str
|
||||||
|
user_id: int
|
||||||
|
username: str
|
||||||
|
role: str
|
||||||
|
|
||||||
|
|
||||||
|
def create_access_token(user_id: int, username: str, role: str, expires_delta: Optional[timedelta] = None):
|
||||||
|
"""Create JWT token with expiration."""
|
||||||
|
if expires_delta:
|
||||||
|
expire = datetime.now(timezone.utc) + expires_delta
|
||||||
|
else:
|
||||||
|
expire = datetime.now(timezone.utc) + timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
|
||||||
|
|
||||||
|
to_encode = {
|
||||||
|
"sub": str(user_id), # JWT spec requires sub to be a string
|
||||||
|
"username": username,
|
||||||
|
"role": role,
|
||||||
|
"exp": expire,
|
||||||
|
"iat": datetime.now(timezone.utc)
|
||||||
|
}
|
||||||
|
encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)
|
||||||
|
return encoded_jwt
|
||||||
|
|
||||||
|
|
||||||
|
async def get_current_user(credentials = Depends(security)):
|
||||||
|
"""
|
||||||
|
Dependency that validates JWT token from Authorization header.
|
||||||
|
Returns TokenData with user_id, username, role.
|
||||||
|
"""
|
||||||
|
token = credentials.credentials
|
||||||
|
import logging
|
||||||
|
log = logging.getLogger("ainventory")
|
||||||
|
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
|
||||||
|
username: str = payload.get("username")
|
||||||
|
role: str = payload.get("role")
|
||||||
|
log.debug(f"[AUTH] Token valid — user_id={user_id}, username={username}, role={role}")
|
||||||
|
|
||||||
|
if user_id is None or username is None:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||||
|
detail="Invalid token claims"
|
||||||
|
)
|
||||||
|
token_data = TokenData(
|
||||||
|
sub=user_id,
|
||||||
|
username=username,
|
||||||
|
role=role,
|
||||||
|
exp=datetime.fromtimestamp(payload.get("exp"), tz=timezone.utc)
|
||||||
|
)
|
||||||
|
except JWTError as e:
|
||||||
|
log.error(f"[AUTH] JWTError: {type(e).__name__}: {str(e)}")
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||||
|
detail="Invalid authentication credentials",
|
||||||
|
headers={"WWW-Authenticate": "Bearer"},
|
||||||
|
)
|
||||||
|
return token_data
|
||||||
|
|
||||||
|
|
||||||
|
async def get_current_admin(current_user: TokenData = Depends(get_current_user)):
|
||||||
|
"""Dependency that checks if user has 'admin' role."""
|
||||||
|
if current_user.role != "admin":
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_403_FORBIDDEN,
|
||||||
|
detail="Admin role required"
|
||||||
|
)
|
||||||
|
return current_user
|
||||||
20
backend/check_models.py
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
import os
|
||||||
|
import google.generativeai as genai
|
||||||
|
from dotenv import load_dotenv
|
||||||
|
|
||||||
|
load_dotenv()
|
||||||
|
API_KEY = os.environ.get("GEMINI_API_KEY")
|
||||||
|
|
||||||
|
if not API_KEY:
|
||||||
|
print("Error: GEMINI_API_KEY not found in .env")
|
||||||
|
exit(1)
|
||||||
|
|
||||||
|
genai.configure(api_key=API_KEY)
|
||||||
|
|
||||||
|
print(f"Checking available models for your API key...")
|
||||||
|
try:
|
||||||
|
for m in genai.list_models():
|
||||||
|
if 'generateContent' in m.supported_generation_methods:
|
||||||
|
print(f"- {m.name}")
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error listing models: {e}")
|
||||||
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()
|
||||||
@@ -2,10 +2,18 @@ from sqlalchemy import create_engine
|
|||||||
from sqlalchemy.orm import sessionmaker, declarative_base
|
from sqlalchemy.orm import sessionmaker, declarative_base
|
||||||
import os
|
import os
|
||||||
|
|
||||||
# Create data directory if it doesn't exist
|
# Get absolute path for the backend directory
|
||||||
os.makedirs("data", exist_ok=True)
|
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
|
||||||
|
|
||||||
SQLALCHEMY_DATABASE_URL = "sqlite:///./data/inventory.db"
|
# Use DATA_DIR from environment if running in Docker, otherwise fallback to local 'data' folder
|
||||||
|
DATA_DIR = os.environ.get("DATA_DIR", os.path.join(BASE_DIR, "data"))
|
||||||
|
|
||||||
|
# Create data directory if it doesn't exist
|
||||||
|
os.makedirs(DATA_DIR, exist_ok=True)
|
||||||
|
|
||||||
|
# Handle absolute path for SQLite (needs 4 slashes on Unix)
|
||||||
|
db_path = os.path.join(DATA_DIR, "inventory.db")
|
||||||
|
SQLALCHEMY_DATABASE_URL = f"sqlite:////{db_path}"
|
||||||
|
|
||||||
# connect_args={"check_same_thread": False} is required for SQLite in FastAPI/Starlette
|
# connect_args={"check_same_thread": False} is required for SQLite in FastAPI/Starlette
|
||||||
engine = create_engine(
|
engine = create_engine(
|
||||||
|
|||||||
171
backend/db_manager.py
Normal file
@@ -0,0 +1,171 @@
|
|||||||
|
import os
|
||||||
|
import shutil
|
||||||
|
import datetime
|
||||||
|
import sqlite3
|
||||||
|
import logging
|
||||||
|
from typing import List
|
||||||
|
from sqlalchemy import text
|
||||||
|
from sqlalchemy.orm import Session
|
||||||
|
from .database import DATA_DIR, engine
|
||||||
|
from . import models, schemas
|
||||||
|
|
||||||
|
logger = logging.getLogger("ainventory")
|
||||||
|
|
||||||
|
BACKUP_DIR = os.path.join(DATA_DIR, "backups")
|
||||||
|
os.makedirs(BACKUP_DIR, exist_ok=True)
|
||||||
|
|
||||||
|
class DbManager:
|
||||||
|
@staticmethod
|
||||||
|
def get_backup_list() -> List[schemas.BackupInfo]:
|
||||||
|
"""List all available backup files with metadata."""
|
||||||
|
backups = []
|
||||||
|
if not os.path.exists(BACKUP_DIR):
|
||||||
|
return []
|
||||||
|
|
||||||
|
for filename in os.listdir(BACKUP_DIR):
|
||||||
|
if filename.endswith(".db"):
|
||||||
|
path = os.path.join(BACKUP_DIR, filename)
|
||||||
|
stats = os.stat(path)
|
||||||
|
backups.append(schemas.BackupInfo(
|
||||||
|
filename=filename,
|
||||||
|
size_bytes=stats.st_size,
|
||||||
|
created_at=datetime.datetime.fromtimestamp(stats.st_mtime)
|
||||||
|
))
|
||||||
|
|
||||||
|
# Sort by creation time descending
|
||||||
|
backups.sort(key=lambda x: x.created_at, reverse=True)
|
||||||
|
return backups
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_stats() -> schemas.DatabaseStats:
|
||||||
|
"""Calculate storage statistics for backups."""
|
||||||
|
backups = DbManager.get_backup_list()
|
||||||
|
total_size = sum(b.size_bytes for b in backups)
|
||||||
|
return schemas.DatabaseStats(
|
||||||
|
backup_count=len(backups),
|
||||||
|
total_size_bytes=total_size
|
||||||
|
)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def create_backup(db: Session, label: str = "manual", user_id: int = None) -> str:
|
||||||
|
"""
|
||||||
|
Creates a consistent snapshot of the active database using VACUUM INTO.
|
||||||
|
Records the action in AuditLog.
|
||||||
|
"""
|
||||||
|
timestamp = datetime.datetime.now().strftime("%Y%m%d_%H%M%S")
|
||||||
|
filename = f"inventory_{label}_{timestamp}.db"
|
||||||
|
target_path = os.path.join(BACKUP_DIR, filename)
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Use SQLite's VACUUM INTO for a safe, non-blocking backup
|
||||||
|
# We need a clean string path without potential SQL injection (filenames are generated here)
|
||||||
|
db.execute(text(f"VACUUM INTO '{target_path}'"))
|
||||||
|
logger.info(f"[DB] Backup created successfully: {filename}")
|
||||||
|
|
||||||
|
# Audit Log
|
||||||
|
audit = models.AuditLog(
|
||||||
|
user_id=user_id,
|
||||||
|
action="DB_BACKUP",
|
||||||
|
details=f"Backup type: {label}. Created file: {filename}"
|
||||||
|
)
|
||||||
|
db.add(audit)
|
||||||
|
db.commit()
|
||||||
|
|
||||||
|
# Enforce retention policy
|
||||||
|
DbManager.enforce_retention(db)
|
||||||
|
|
||||||
|
return filename
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"[DB] Backup failed: {str(e)}")
|
||||||
|
db.rollback()
|
||||||
|
raise Exception(f"Backup failed: {str(e)}")
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def enforce_retention(db: Session):
|
||||||
|
"""Deletes oldest backups if count exceeds retention limit."""
|
||||||
|
try:
|
||||||
|
# Get retention limit from settings
|
||||||
|
limit_setting = db.query(models.SystemSetting).filter(models.SystemSetting.key == "backup_retention_count").first()
|
||||||
|
limit = int(limit_setting.value) if limit_setting else 10 # Default to 10
|
||||||
|
|
||||||
|
backups = DbManager.get_backup_list()
|
||||||
|
if len(backups) > limit:
|
||||||
|
to_delete = backups[limit:]
|
||||||
|
for b in to_delete:
|
||||||
|
path = os.path.join(BACKUP_DIR, b.filename)
|
||||||
|
if os.path.exists(path):
|
||||||
|
os.remove(path)
|
||||||
|
logger.info(f"[DB] Retention policy: deleted old backup {b.filename}")
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"[DB] Retention enforcement failed: {str(e)}")
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def restore_backup(filename: str, db: Session, user_id: int) -> bool:
|
||||||
|
"""
|
||||||
|
Restores a database from a backup file.
|
||||||
|
IMPORTANT: This replaces the primary inventory.db file.
|
||||||
|
"""
|
||||||
|
source_path = os.path.join(BACKUP_DIR, filename)
|
||||||
|
active_db_path = os.path.join(DATA_DIR, "inventory.db")
|
||||||
|
|
||||||
|
if not os.path.exists(source_path):
|
||||||
|
raise Exception("Backup file not found")
|
||||||
|
|
||||||
|
try:
|
||||||
|
# 1. Create a safety rollback backup of current state
|
||||||
|
logger.info("[DB] Creating safety rollback backup before restore...")
|
||||||
|
DbManager.create_backup(db, label="rollback", user_id=user_id)
|
||||||
|
|
||||||
|
# 2. Close all connections (or as many as possible via pool dispose)
|
||||||
|
# engine.dispose() drops the current connection pool
|
||||||
|
engine.dispose()
|
||||||
|
|
||||||
|
# 3. Physically swap files
|
||||||
|
# Note: shutil.copy2 handles file metadata preservation
|
||||||
|
shutil.copy2(source_path, active_db_path)
|
||||||
|
|
||||||
|
logger.warning(f"[DB] RESTORE COMPLETED from {filename} by user_id={user_id}")
|
||||||
|
|
||||||
|
# Record the restore in the NEW database (since we just swapped it)
|
||||||
|
# Re-initializing session logic might be needed but usually next request handles it.
|
||||||
|
# However, for the current request, the 'db' session is still bound to the OLD file mapping possibly?
|
||||||
|
# Actually, the file on disk is changed. Next commit might fail or work depending on how sqlite handles it.
|
||||||
|
# It's safest to return success and let the frontend trigger a reload.
|
||||||
|
|
||||||
|
return True
|
||||||
|
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)}")
|
||||||
32
backend/entrypoint.sh
Executable file
@@ -0,0 +1,32 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# =============================================================================
|
||||||
|
# backend/entrypoint.sh
|
||||||
|
# =============================================================================
|
||||||
|
# Docker container entrypoint for TFM aInventory backend.
|
||||||
|
# Runs first-run initialization then starts the application server.
|
||||||
|
#
|
||||||
|
# This script is the ENTRYPOINT defined in backend/Dockerfile.
|
||||||
|
# DATA_DIR and LOGS_DIR are set via docker-compose.yml environment section.
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
echo "🐳 [Docker] Backend container starting..."
|
||||||
|
echo "🐳 [Docker] DATA_DIR=${DATA_DIR:-/app/data}"
|
||||||
|
echo "🐳 [Docker] LOGS_DIR=${LOGS_DIR:-/app/logs}"
|
||||||
|
|
||||||
|
# Export defaults if not already set by docker-compose
|
||||||
|
export DATA_DIR="${DATA_DIR:-/app/data}"
|
||||||
|
export LOGS_DIR="${LOGS_DIR:-/app/logs}"
|
||||||
|
|
||||||
|
# Run shared first-run initialization
|
||||||
|
echo "🐳 [Docker] Running data initialization..."
|
||||||
|
bash /app/scripts/init_data.sh
|
||||||
|
|
||||||
|
# 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
|
||||||
53
backend/logger.py
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
import logging
|
||||||
|
import os
|
||||||
|
from logging.handlers import RotatingFileHandler
|
||||||
|
|
||||||
|
# Define paths
|
||||||
|
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
|
||||||
|
# Fallback to local 'logs' dir if not overridden in Docker
|
||||||
|
LOGS_DIR = os.environ.get("LOGS_DIR", os.path.join(BASE_DIR, "logs"))
|
||||||
|
|
||||||
|
# Ensure directory exists
|
||||||
|
os.makedirs(LOGS_DIR, exist_ok=True)
|
||||||
|
|
||||||
|
LOG_FILE_PATH = os.path.join(LOGS_DIR, "backend.log")
|
||||||
|
|
||||||
|
def setup_logger():
|
||||||
|
logger = logging.getLogger("ainventory")
|
||||||
|
# Set to DEBUG for development; change to INFO for production
|
||||||
|
log_level = os.environ.get("LOG_LEVEL", "DEBUG")
|
||||||
|
logger.setLevel(getattr(logging, log_level.upper(), logging.INFO))
|
||||||
|
|
||||||
|
# Avoid duplicate handlers if setup multiple times
|
||||||
|
if logger.handlers:
|
||||||
|
return logger
|
||||||
|
|
||||||
|
formatter = logging.Formatter(
|
||||||
|
"[%(asctime)s] [%(levelname)s] [%(name)s] %(message)s",
|
||||||
|
datefmt="%Y-%m-%d %H:%M:%S"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Console Handler
|
||||||
|
console_handler = logging.StreamHandler()
|
||||||
|
console_handler.setFormatter(formatter)
|
||||||
|
|
||||||
|
# File Handler (10MB max, keep 5 backups)
|
||||||
|
file_handler = RotatingFileHandler(
|
||||||
|
LOG_FILE_PATH, maxBytes=10*1024*1024, backupCount=5
|
||||||
|
)
|
||||||
|
file_handler.setFormatter(formatter)
|
||||||
|
|
||||||
|
# Attach handlers
|
||||||
|
logger.addHandler(console_handler)
|
||||||
|
logger.addHandler(file_handler)
|
||||||
|
|
||||||
|
# Special redirect for uvicorn logs to also go to file
|
||||||
|
uvicorn_logger = logging.getLogger("uvicorn")
|
||||||
|
uvicorn_logger.addHandler(file_handler)
|
||||||
|
|
||||||
|
uvicorn_access_logger = logging.getLogger("uvicorn.access")
|
||||||
|
uvicorn_access_logger.addHandler(file_handler)
|
||||||
|
|
||||||
|
return logger
|
||||||
|
|
||||||
|
log = setup_logger()
|
||||||
146
backend/main.py
@@ -1,25 +1,161 @@
|
|||||||
|
import os
|
||||||
|
from . import config_loader # This triggers the automatic environment loading
|
||||||
from fastapi import FastAPI
|
from fastapi import FastAPI
|
||||||
from fastapi.middleware.cors import CORSMiddleware
|
from fastapi.middleware.cors import CORSMiddleware
|
||||||
|
from slowapi import Limiter
|
||||||
|
from slowapi.util import get_remote_address
|
||||||
from . import models
|
from . import models
|
||||||
from .database import engine
|
from .database import engine
|
||||||
from .routers import items, operations
|
from .routers import items, operations, users, categories, admin_db
|
||||||
|
from .logger import log
|
||||||
|
from .scheduler import scheduler, sync_scheduler_config
|
||||||
|
|
||||||
# Create the database tables
|
# Create the database tables
|
||||||
|
from .database import DATA_DIR, db_path
|
||||||
|
log.info(f"Using DATA_DIR: {DATA_DIR}")
|
||||||
|
log.info(f"Database path: {db_path}")
|
||||||
models.Base.metadata.create_all(bind=engine)
|
models.Base.metadata.create_all(bind=engine)
|
||||||
|
log.info("Database tables verified.")
|
||||||
|
|
||||||
app = FastAPI(title="Inventory PWA API", version="0.1.0")
|
app = FastAPI(title="TFM aInventory API", version="1.1.0")
|
||||||
|
log.info("TFM aInventory API process started.")
|
||||||
|
|
||||||
# Setup Cross-Origin for Client interaction (PWA)
|
# [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()]
|
||||||
|
|
||||||
|
# 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(
|
app.add_middleware(
|
||||||
CORSMiddleware,
|
CORSMiddleware,
|
||||||
allow_origins=["*"],
|
allow_origins=ALLOWED_ORIGINS,
|
||||||
allow_credentials=True,
|
allow_credentials=True,
|
||||||
allow_methods=["*"],
|
allow_methods=["GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"],
|
||||||
allow_headers=["*"],
|
allow_headers=["*"],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# [H-02] Rate limiting on API
|
||||||
|
limiter = Limiter(key_func=get_remote_address)
|
||||||
|
app.state.limiter = limiter
|
||||||
|
|
||||||
app.include_router(items.router)
|
app.include_router(items.router)
|
||||||
app.include_router(operations.router)
|
app.include_router(operations.router)
|
||||||
|
app.include_router(users.router)
|
||||||
|
app.include_router(categories.router)
|
||||||
|
app.include_router(admin_db.router)
|
||||||
|
|
||||||
|
@app.on_event("startup")
|
||||||
|
def startup_event():
|
||||||
|
log.info("[STARTUP] Starting background scheduler...")
|
||||||
|
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("/")
|
@app.get("/")
|
||||||
def read_root():
|
def read_root():
|
||||||
|
|||||||