feat: Add API key authentication for backup tool

- Add get_api_key() and require_api_key() to auth.py
- Add generic key-value settings storage to SettingsStore
- Add test_api_key_auth to verify the implementation
- Uses secrets.compare_digest for timing-safe comparison
- Auto-generates API key on first use and stores in settings DB
This commit is contained in:
2026-05-11 21:25:46 +02:00
parent f912623677
commit 836f733a2e
31 changed files with 2014 additions and 1 deletions
+195
View File
@@ -0,0 +1,195 @@
---
name: sift-backlog
description: Triage and organize backlog tasks into actionable plans. Use when asked to review the backlog, prioritize tasks, create plans from backlog items, or move tasks from backlog to open status. Handles the full workflow of listing backlog tasks, grouping related tasks into plans, setting priorities and dependencies, activating plans, and changing task status from backlog to open.
---
# Sift Backlog
Triage backlog tasks: prioritize, group into plans, set dependencies, and activate.
## Overview
1. List backlog tasks (`sf task backlog`)
2. Clarify and enrich each task (titles, descriptions)
3. Identify groupings and create draft plans
4. Add tasks to plans and set dependencies
5. Activate plans
6. Set task status to open
## Workflow
### Step 1: List Backlog Tasks
```bash
sf task backlog
```
### Step 2: Clarify and Enrich Tasks
Backlog tasks often have only a brief title with no description. Before organizing, ensure each task is well-defined.
**For each task, evaluate:**
- Is the title clear and actionable?
- Is there a description? Check with `sf task describe <task-id> --show`
- Is the scope unambiguous?
**If the title is unclear**, update it:
```bash
sf update <task-id> --title "Clear, actionable title"
```
**Add a description** with context, scope, and acceptance criteria:
```bash
sf task describe <task-id> --content "Description with:
- What needs to be done
- Why it matters
- Acceptance criteria
- Any relevant context"
```
**Use your best judgment** to interpret tasks and make reasonable decisions about scope, grouping, and priority. You have context about the codebase, project patterns, and typical development practices—leverage this knowledge rather than deferring to the user for routine decisions.
**Only ask the user for clarity when absolutely necessary:**
- The task is fundamentally ambiguous (multiple mutually exclusive interpretations)
- Critical business logic or user-facing behavior that could go wrong in meaningful ways
- External dependencies or integrations you cannot verify
**Do NOT ask about:**
- Implementation details you can reasonably infer
- Priority or grouping decisions—use your judgment
- Standard development practices (testing, code style, etc.)
- Tasks where a reasonable interpretation exists
### Step 3: Create Draft Plans
Group related tasks into plans using your best judgment. Plans start as drafts (tasks won't be dispatched until activated).
**Grouping guidance:**
- Group tasks that share a common theme, feature area, or goal
- Consider technical dependencies when grouping (tasks that touch the same files/modules)
- Separate unrelated work into distinct plans for parallel execution
- Don't over-group—if tasks are truly independent, separate plans enable better parallelism
- Don't under-group—related tasks benefit from shared context and coordinated execution
```bash
sf plan create --title "Plan Name"
```
**Example:**
```bash
sf plan create --title "Authentication Improvements"
# Output: Created plan el-abc123
```
### Step 4: Add Tasks to Plans
```bash
sf plan add-task <plan-id> <task-id>
```
**Example:**
```bash
sf plan add-task el-abc123 el-task1
sf plan add-task el-abc123 el-task2
```
### Step 5: Set Dependencies Between Tasks
Use `blocks` dependency when one task must complete before another can start.
```bash
sf dependency add <blocked-id> <blocker-id> --type blocks
```
**Semantics:** The first ID is blocked BY the second ID. The blocker must complete first.
**Example:** Task 2 can't start until Task 1 completes:
```bash
sf dependency add el-task2 el-task1 --type blocks
```
### Step 6: Update Priorities
Set priorities based on your assessment of impact, urgency, and dependencies. Use your judgment—you don't need user confirmation for routine prioritization.
**Priority guidance:**
- **Critical (1):** Blocking issues, security vulnerabilities, production bugs
- **High (2):** Important features with deadlines, significant user impact
- **Medium (3):** Standard feature work, most tasks default here
- **Low (4):** Nice-to-haves, minor improvements, tech debt
- **Minimal (5):** Backlog cleanup, documentation, exploratory work
```bash
sf update <task-id> --priority <1-5>
```
| Value | Level |
| ----- | -------- |
| 1 | Critical |
| 2 | High |
| 3 | Medium |
| 4 | Low |
| 5 | Minimal |
### Step 7: Activate Plans
Once tasks are organized with dependencies set, activate plans to enable dispatch.
```bash
sf plan activate <plan-id>
```
### Step 8: Set Task Status to Open
Move tasks from backlog to open so they become ready for work.
```bash
sf update <id> --status open
```
## Other Actions
**Close obsolete tasks:**
```bash
sf task close <id> --reason "Won't do: <reason>"
```
**Defer tasks:**
```bash
sf task defer <id> --until <date>
```
**View existing plans:**
```bash
sf plan list
```
**View tasks in a plan:**
```bash
sf plan tasks <plan-id>
```
## Tips
- **Use your best judgment** for grouping, prioritization, and task interpretation—don't defer routine decisions to the user
- **Only escalate to the user** when ambiguity is fundamental and could lead to wasted work (mutually exclusive interpretations, critical business decisions)
- Make reasonable inferences about implementation details, scope, and priority based on codebase context
- Create plans before setting dependencies to avoid dispatch race conditions
- Always activate plans after dependencies are set
- Focus on oldest backlog items first (sorted by creation date)
- Every task should have a clear title and description before activation
- When uncertain about a minor detail, make a reasonable choice and document it in the task description—workers can ask if needed
Binary file not shown.
Binary file not shown.
Binary file not shown.
+127
View File
@@ -0,0 +1,127 @@
{
"nextId": 1,
"settings": {
"globalPause": false,
"enginePaused": false,
"maxConcurrent": 2,
"maxTriageConcurrent": 2,
"globalMaxConcurrent": 4,
"maxWorktrees": 4,
"pollIntervalMs": 15000,
"heartbeatMultiplier": 1,
"groupOverlappingFiles": true,
"overlapIgnorePaths": [],
"autoMerge": true,
"mergeStrategy": "direct",
"requirePrApproval": false,
"pushAfterMerge": false,
"pushRemote": "origin",
"unavailableNodePolicy": "block",
"recycleWorktrees": false,
"worktreeNaming": "random",
"taskPrefix": "FN",
"includeTaskIdInCommit": true,
"commitAuthorEnabled": true,
"commitAuthorName": "Fusion",
"commitAuthorEmail": "noreply@runfusion.ai",
"modelPresets": [],
"autoSelectModelPreset": false,
"completionDocumentationMode": "off",
"defaultPresetBySize": {},
"autoResolveConflicts": true,
"smartConflictResolution": true,
"mergerAutostashMaxAgeHours": 24,
"worktreeRebaseBeforeMerge": true,
"worktreeRebaseRemote": "",
"worktreeRebaseLocalBase": true,
"mergeConflictStrategy": "smart-prefer-main",
"workflowStepTimeoutMs": 360000,
"strictScopeEnforcement": false,
"buildRetryCount": 0,
"verificationFixRetries": 3,
"buildTimeoutMs": 300000,
"requirePlanApproval": false,
"specStalenessEnabled": false,
"specStalenessMaxAgeMs": 21600000,
"aiSessionTtlMs": 604800000,
"aiSessionCleanupIntervalMs": 3600000,
"autoUnpauseEnabled": true,
"autoUnpauseBaseDelayMs": 300000,
"autoUnpauseMaxDelayMs": 3600000,
"maxStuckKills": 6,
"preserveProgressOnStuckRequeue": true,
"maxPostReviewFixes": 1,
"maxSpawnedAgentsPerParent": 5,
"maxSpawnedAgentsGlobal": 20,
"maintenanceIntervalMs": 300000,
"autoArchiveDoneTasksEnabled": true,
"autoArchiveDoneAfterMs": 172800000,
"archiveAgentLogMode": "compact",
"autoUpdatePrStatus": false,
"githubCommentOnDone": false,
"githubTrackingEnabledByDefault": false,
"githubAuthMode": "gh-cli",
"autoBackupEnabled": false,
"autoBackupSchedule": "0 2 * * *",
"autoBackupRetention": 7,
"autoBackupDir": ".fusion/backups",
"memoryBackupEnabled": false,
"memoryBackupSchedule": "0 3 * * *",
"memoryBackupRetention": 14,
"memoryBackupDir": ".fusion/backups/memory",
"memoryBackupScope": "all",
"autoSummarizeTitles": false,
"useAiMergeCommitSummary": true,
"insightExtractionEnabled": false,
"insightExtractionSchedule": "0 2 * * *",
"insightExtractionMinIntervalMs": 86400000,
"taskEvaluationEnabled": false,
"taskEvaluationSchedule": "0 5 * * *",
"taskEvaluationFollowUpPolicy": "off",
"memoryEnabled": true,
"memoryBackendType": "qmd",
"memoryAutoSummarizeEnabled": false,
"memoryAutoSummarizeThresholdChars": 50000,
"memoryAutoSummarizeSchedule": "0 3 * * *",
"memoryDreamsEnabled": false,
"memoryDreamsSchedule": "0 4 * * *",
"runStepsInNewSessions": false,
"maxParallelSteps": 2,
"missionStaleThresholdMs": 600000,
"missionMaxTaskRetries": 3,
"missionHealthCheckIntervalMs": 300000,
"reflectionEnabled": false,
"reflectionIntervalMs": 3600000,
"reflectionAfterTask": true,
"reviewHandoffPolicy": "disabled",
"showQuickChatFAB": false,
"researchSettings": {
"enabled": true,
"enabledSources": {
"webSearch": true,
"pageFetch": true,
"github": false,
"localDocs": true,
"llmSynthesis": true
},
"limits": {
"maxConcurrentRuns": 3,
"maxSourcesPerRun": 20,
"maxDurationMs": 300000,
"requestTimeoutMs": 30000
}
},
"evalSettings": {
"enabled": false,
"intervalMs": 86400000,
"followUpPolicy": "suggest-only",
"retentionDays": 30
},
"researchEnabled": true,
"researchMaxConcurrentRuns": 3,
"researchDefaultTimeout": 300000,
"researchMaxSourcesPerRun": 20,
"researchMaxSynthesisRounds": 2,
"globalPauseReason": "manual"
}
}
BIN
View File
Binary file not shown.
Binary file not shown.
Binary file not shown.
+3
View File
@@ -0,0 +1,3 @@
# Daily Memory 2026-05-11
<!-- Append running observations, open loops, and day-to-day notes here. Promote evergreen facts to MEMORY.md. -->
+3
View File
@@ -0,0 +1,3 @@
# Memory Dreams
<!-- Periodic synthesized patterns from daily notes. Promote durable lessons to MEMORY.md. -->
+19
View File
@@ -0,0 +1,19 @@
# Project Memory
<!-- This file stores durable project learnings. Agents consult and update it during triage and execution. -->
## Architecture
<!-- Key architectural patterns, module boundaries, and design decisions -->
## Conventions
<!-- Project-specific coding standards, naming patterns, file organization -->
## Pitfalls
<!-- Known issues, common mistakes, and things to avoid -->
## Context
<!-- Important background information, dependency constraints, deployment notes -->
+1
View File
@@ -47,3 +47,4 @@ frontend/dist/
# Pi internal
.pi-lens/
.pi/
/.stoneforge/.worktrees/
+149
View File
@@ -0,0 +1,149 @@
---
description: Implement tasks from an OpenSpec change (Experimental)
---
Implement tasks from an OpenSpec change.
**Input**: Optionally specify a change name (e.g., `/opsx-apply add-auth`). If omitted, check if it can be inferred from conversation context. If vague or ambiguous you MUST prompt for available changes.
**Steps**
1. **Select the change**
If a name is provided, use it. Otherwise:
- Infer from conversation context if the user mentioned a change
- Auto-select if only one active change exists
- If ambiguous, run `openspec list --json` to get available changes and use the **AskUserQuestion tool** to let the user select
Always announce: "Using change: <name>" and how to override (e.g., `/opsx-apply <other>`).
2. **Check status to understand the schema**
```bash
openspec status --change "<name>" --json
```
Parse the JSON to understand:
- `schemaName`: The workflow being used (e.g., "spec-driven")
- Which artifact contains the tasks (typically "tasks" for spec-driven, check status for others)
3. **Get apply instructions**
```bash
openspec instructions apply --change "<name>" --json
```
This returns:
- `contextFiles`: artifact ID -> array of concrete file paths (varies by schema)
- Progress (total, complete, remaining)
- Task list with status
- Dynamic instruction based on current state
**Handle states:**
- If `state: "blocked"` (missing artifacts): show message, suggest using `/opsx-continue`
- If `state: "all_done"`: congratulate, suggest archive
- Otherwise: proceed to implementation
4. **Read context files**
Read every file path listed under `contextFiles` from the apply instructions output.
The files depend on the schema being used:
- **spec-driven**: proposal, specs, design, tasks
- Other schemas: follow the contextFiles from CLI output
5. **Show current progress**
Display:
- Schema being used
- Progress: "N/M tasks complete"
- Remaining tasks overview
- Dynamic instruction from CLI
6. **Implement tasks (loop until done or blocked)**
For each pending task:
- Show which task is being worked on
- Make the code changes required
- Keep changes minimal and focused
- Mark task complete in the tasks file: `- [ ]` → `- [x]`
- Continue to next task
**Pause if:**
- Task is unclear → ask for clarification
- Implementation reveals a design issue → suggest updating artifacts
- Error or blocker encountered → report and wait for guidance
- User interrupts
7. **On completion or pause, show status**
Display:
- Tasks completed this session
- Overall progress: "N/M tasks complete"
- If all done: suggest archive
- If paused: explain why and wait for guidance
**Output During Implementation**
```
## Implementing: <change-name> (schema: <schema-name>)
Working on task 3/7: <task description>
[...implementation happening...]
✓ Task complete
Working on task 4/7: <task description>
[...implementation happening...]
✓ Task complete
```
**Output On Completion**
```
## Implementation Complete
**Change:** <change-name>
**Schema:** <schema-name>
**Progress:** 7/7 tasks complete ✓
### Completed This Session
- [x] Task 1
- [x] Task 2
...
All tasks complete! You can archive this change with `/opsx-archive`.
```
**Output On Pause (Issue Encountered)**
```
## Implementation Paused
**Change:** <change-name>
**Schema:** <schema-name>
**Progress:** 4/7 tasks complete
### Issue Encountered
<description of the issue>
**Options:**
1. <option 1>
2. <option 2>
3. Other approach
What would you like to do?
```
**Guardrails**
- Keep going through tasks until done or blocked
- Always read context files before starting (from the apply instructions output)
- If task is ambiguous, pause and ask before implementing
- If implementation reveals issues, pause and suggest artifact updates
- Keep code changes minimal and scoped to each task
- Update task checkbox immediately after completing each task
- Pause on errors, blockers, or unclear requirements - don't guess
- Use contextFiles from CLI output, don't assume specific file names
**Fluid Workflow Integration**
This skill supports the "actions on a change" model:
- **Can be invoked anytime**: Before all artifacts are done (if tasks exist), after partial implementation, interleaved with other actions
- **Allows artifact updates**: If implementation reveals design issues, suggest updating artifacts - not phase-locked, work fluidly
+154
View File
@@ -0,0 +1,154 @@
---
description: Archive a completed change in the experimental workflow
---
Archive a completed change in the experimental workflow.
**Input**: Optionally specify a change name after `/opsx-archive` (e.g., `/opsx-archive add-auth`). If omitted, check if it can be inferred from conversation context. If vague or ambiguous you MUST prompt for available changes.
**Steps**
1. **If no change name provided, prompt for selection**
Run `openspec list --json` to get available changes. Use the **AskUserQuestion tool** to let the user select.
Show only active changes (not already archived).
Include the schema used for each change if available.
**IMPORTANT**: Do NOT guess or auto-select a change. Always let the user choose.
2. **Check artifact completion status**
Run `openspec status --change "<name>" --json` to check artifact completion.
Parse the JSON to understand:
- `schemaName`: The workflow being used
- `artifacts`: List of artifacts with their status (`done` or other)
**If any artifacts are not `done`:**
- Display warning listing incomplete artifacts
- Prompt user for confirmation to continue
- Proceed if user confirms
3. **Check task completion status**
Read the tasks file (typically `tasks.md`) to check for incomplete tasks.
Count tasks marked with `- [ ]` (incomplete) vs `- [x]` (complete).
**If incomplete tasks found:**
- Display warning showing count of incomplete tasks
- Prompt user for confirmation to continue
- Proceed if user confirms
**If no tasks file exists:** Proceed without task-related warning.
4. **Assess delta spec sync state**
Check for delta specs at `openspec/changes/<name>/specs/`. If none exist, proceed without sync prompt.
**If delta specs exist:**
- Compare each delta spec with its corresponding main spec at `openspec/specs/<capability>/spec.md`
- Determine what changes would be applied (adds, modifications, removals, renames)
- Show a combined summary before prompting
**Prompt options:**
- If changes needed: "Sync now (recommended)", "Archive without syncing"
- If already synced: "Archive now", "Sync anyway", "Cancel"
If user chooses sync, use Task tool (subagent_type: "general-purpose", prompt: "Use Skill tool to invoke openspec-sync-specs for change '<name>'. Delta spec analysis: <include the analyzed delta spec summary>"). Proceed to archive regardless of choice.
5. **Perform the archive**
Create the archive directory if it doesn't exist:
```bash
mkdir -p openspec/changes/archive
```
Generate target name using current date: `YYYY-MM-DD-<change-name>`
**Check if target already exists:**
- If yes: Fail with error, suggest renaming existing archive or using different date
- If no: Move the change directory to archive
```bash
mv openspec/changes/<name> openspec/changes/archive/YYYY-MM-DD-<name>
```
6. **Display summary**
Show archive completion summary including:
- Change name
- Schema that was used
- Archive location
- Spec sync status (synced / sync skipped / no delta specs)
- Note about any warnings (incomplete artifacts/tasks)
**Output On Success**
```
## Archive Complete
**Change:** <change-name>
**Schema:** <schema-name>
**Archived to:** openspec/changes/archive/YYYY-MM-DD-<name>/
**Specs:** ✓ Synced to main specs
All artifacts complete. All tasks complete.
```
**Output On Success (No Delta Specs)**
```
## Archive Complete
**Change:** <change-name>
**Schema:** <schema-name>
**Archived to:** openspec/changes/archive/YYYY-MM-DD-<name>/
**Specs:** No delta specs
All artifacts complete. All tasks complete.
```
**Output On Success With Warnings**
```
## Archive Complete (with warnings)
**Change:** <change-name>
**Schema:** <schema-name>
**Archived to:** openspec/changes/archive/YYYY-MM-DD-<name>/
**Specs:** Sync skipped (user chose to skip)
**Warnings:**
- Archived with 2 incomplete artifacts
- Archived with 3 incomplete tasks
- Delta spec sync was skipped (user chose to skip)
Review the archive if this was not intentional.
```
**Output On Error (Archive Exists)**
```
## Archive Failed
**Change:** <change-name>
**Target:** openspec/changes/archive/YYYY-MM-DD-<name>/
Target archive directory already exists.
**Options:**
1. Rename the existing archive
2. Delete the existing archive if it's a duplicate
3. Wait until a different date to archive
```
**Guardrails**
- Always prompt for change selection if not provided
- Use artifact graph (openspec status --json) for completion checking
- Don't block archive on warnings - just inform and confirm
- Preserve .openspec.yaml when moving to archive (it moves with the directory)
- Show clear summary of what happened
- If sync is requested, use the Skill tool to invoke `openspec-sync-specs` (agent-driven)
- If delta specs exist, always run the sync assessment and show the combined summary before prompting
+170
View File
@@ -0,0 +1,170 @@
---
description: Enter explore mode - think through ideas, investigate problems, clarify requirements
---
Enter explore mode. Think deeply. Visualize freely. Follow the conversation wherever it goes.
**IMPORTANT: Explore mode is for thinking, not implementing.** You may read files, search code, and investigate the codebase, but you must NEVER write code or implement features. If the user asks you to implement something, remind them to exit explore mode first and create a change proposal. You MAY create OpenSpec artifacts (proposals, designs, specs) if the user asks—that's capturing thinking, not implementing.
**This is a stance, not a workflow.** There are no fixed steps, no required sequence, no mandatory outputs. You're a thinking partner helping the user explore.
**Input**: The argument after `/opsx-explore` is whatever the user wants to think about. Could be:
- A vague idea: "real-time collaboration"
- A specific problem: "the auth system is getting unwieldy"
- A change name: "add-dark-mode" (to explore in context of that change)
- A comparison: "postgres vs sqlite for this"
- Nothing (just enter explore mode)
---
## The Stance
- **Curious, not prescriptive** - Ask questions that emerge naturally, don't follow a script
- **Open threads, not interrogations** - Surface multiple interesting directions and let the user follow what resonates. Don't funnel them through a single path of questions.
- **Visual** - Use ASCII diagrams liberally when they'd help clarify thinking
- **Adaptive** - Follow interesting threads, pivot when new information emerges
- **Patient** - Don't rush to conclusions, let the shape of the problem emerge
- **Grounded** - Explore the actual codebase when relevant, don't just theorize
---
## What You Might Do
Depending on what the user brings, you might:
**Explore the problem space**
- Ask clarifying questions that emerge from what they said
- Challenge assumptions
- Reframe the problem
- Find analogies
**Investigate the codebase**
- Map existing architecture relevant to the discussion
- Find integration points
- Identify patterns already in use
- Surface hidden complexity
**Compare options**
- Brainstorm multiple approaches
- Build comparison tables
- Sketch tradeoffs
- Recommend a path (if asked)
**Visualize**
```
┌─────────────────────────────────────────┐
│ Use ASCII diagrams liberally │
├─────────────────────────────────────────┤
│ │
│ ┌────────┐ ┌────────┐ │
│ │ State │────────▶│ State │ │
│ │ A │ │ B │ │
│ └────────┘ └────────┘ │
│ │
│ System diagrams, state machines, │
│ data flows, architecture sketches, │
│ dependency graphs, comparison tables │
│ │
└─────────────────────────────────────────┘
```
**Surface risks and unknowns**
- Identify what could go wrong
- Find gaps in understanding
- Suggest spikes or investigations
---
## OpenSpec Awareness
You have full context of the OpenSpec system. Use it naturally, don't force it.
### Check for context
At the start, quickly check what exists:
```bash
openspec list --json
```
This tells you:
- If there are active changes
- Their names, schemas, and status
- What the user might be working on
If the user mentioned a specific change name, read its artifacts for context.
### When no change exists
Think freely. When insights crystallize, you might offer:
- "This feels solid enough to start a change. Want me to create a proposal?"
- Or keep exploring - no pressure to formalize
### When a change exists
If the user mentions a change or you detect one is relevant:
1. **Read existing artifacts for context**
- `openspec/changes/<name>/proposal.md`
- `openspec/changes/<name>/design.md`
- `openspec/changes/<name>/tasks.md`
- etc.
2. **Reference them naturally in conversation**
- "Your design mentions using Redis, but we just realized SQLite fits better..."
- "The proposal scopes this to premium users, but we're now thinking everyone..."
3. **Offer to capture when decisions are made**
| Insight Type | Where to Capture |
|----------------------------|--------------------------------|
| New requirement discovered | `specs/<capability>/spec.md` |
| Requirement changed | `specs/<capability>/spec.md` |
| Design decision made | `design.md` |
| Scope changed | `proposal.md` |
| New work identified | `tasks.md` |
| Assumption invalidated | Relevant artifact |
Example offers:
- "That's a design decision. Capture it in design.md?"
- "This is a new requirement. Add it to specs?"
- "This changes scope. Update the proposal?"
4. **The user decides** - Offer and move on. Don't pressure. Don't auto-capture.
---
## What You Don't Have To Do
- Follow a script
- Ask the same questions every time
- Produce a specific artifact
- Reach a conclusion
- Stay on topic if a tangent is valuable
- Be brief (this is thinking time)
---
## Ending Discovery
There's no required ending. Discovery might:
- **Flow into a proposal**: "Ready to start? I can create a change proposal."
- **Result in artifact updates**: "Updated design.md with these decisions"
- **Just provide clarity**: User has what they need, moves on
- **Continue later**: "We can pick this up anytime"
When things crystallize, you might offer a summary - but it's optional. Sometimes the thinking IS the value.
---
## Guardrails
- **Don't implement** - Never write code or implement features. Creating OpenSpec artifacts is fine, writing application code is not.
- **Don't fake understanding** - If something is unclear, dig deeper
- **Don't rush** - Discovery is thinking time, not task time
- **Don't force structure** - Let patterns emerge naturally
- **Don't auto-capture** - Offer to save insights, don't just do it
- **Do visualize** - A good diagram is worth many paragraphs
- **Do explore the codebase** - Ground discussions in reality
- **Do question assumptions** - Including the user's and your own
+103
View File
@@ -0,0 +1,103 @@
---
description: Propose a new change - create it and generate all artifacts in one step
---
Propose a new change - create the change and generate all artifacts in one step.
I'll create a change with artifacts:
- proposal.md (what & why)
- design.md (how)
- tasks.md (implementation steps)
When ready to implement, run /opsx-apply
---
**Input**: The argument after `/opsx-propose` is the change name (kebab-case), OR a description of what the user wants to build.
**Steps**
1. **If no input provided, ask what they want to build**
Use the **AskUserQuestion tool** (open-ended, no preset options) to ask:
> "What change do you want to work on? Describe what you want to build or fix."
From their description, derive a kebab-case name (e.g., "add user authentication" → `add-user-auth`).
**IMPORTANT**: Do NOT proceed without understanding what the user wants to build.
2. **Create the change directory**
```bash
openspec new change "<name>"
```
This creates a scaffolded change at `openspec/changes/<name>/` with `.openspec.yaml`.
3. **Get the artifact build order**
```bash
openspec status --change "<name>" --json
```
Parse the JSON to get:
- `applyRequires`: array of artifact IDs needed before implementation (e.g., `["tasks"]`)
- `artifacts`: list of all artifacts with their status and dependencies
4. **Create artifacts in sequence until apply-ready**
Use the **TodoWrite tool** to track progress through the artifacts.
Loop through artifacts in dependency order (artifacts with no pending dependencies first):
a. **For each artifact that is `ready` (dependencies satisfied)**:
- Get instructions:
```bash
openspec instructions <artifact-id> --change "<name>" --json
```
- The instructions JSON includes:
- `context`: Project background (constraints for you - do NOT include in output)
- `rules`: Artifact-specific rules (constraints for you - do NOT include in output)
- `template`: The structure to use for your output file
- `instruction`: Schema-specific guidance for this artifact type
- `outputPath`: Where to write the artifact
- `dependencies`: Completed artifacts to read for context
- Read any completed dependency files for context
- Create the artifact file using `template` as the structure
- Apply `context` and `rules` as constraints - but do NOT copy them into the file
- Show brief progress: "Created <artifact-id>"
b. **Continue until all `applyRequires` artifacts are complete**
- After creating each artifact, re-run `openspec status --change "<name>" --json`
- Check if every artifact ID in `applyRequires` has `status: "done"` in the artifacts array
- Stop when all `applyRequires` artifacts are done
c. **If an artifact requires user input** (unclear context):
- Use **AskUserQuestion tool** to clarify
- Then continue with creation
5. **Show final status**
```bash
openspec status --change "<name>"
```
**Output**
After completing all artifacts, summarize:
- Change name and location
- List of artifacts created with brief descriptions
- What's ready: "All artifacts created! Ready for implementation."
- Prompt: "Run `/opsx-apply` to start implementing."
**Artifact Creation Guidelines**
- Follow the `instruction` field from `openspec instructions` for each artifact type
- The schema defines what each artifact should contain - follow it
- Read dependency artifacts for context before creating new ones
- Use `template` as the structure for your output file - fill in its sections
- **IMPORTANT**: `context` and `rules` are constraints for YOU, not content for the file
- Do NOT copy `<context>`, `<rules>`, `<project_context>` blocks into the artifact
- These guide what you write, but should never appear in the output
**Guardrails**
- Create ALL artifacts needed for implementation (as defined by schema's `apply.requires`)
- Always read dependency artifacts before creating a new one
- If context is critically unclear, ask the user - but prefer making reasonable decisions to keep momentum
- If a change with that name already exists, ask if user wants to continue it or create a new one
- Verify each artifact file exists after writing before proceeding to next
@@ -0,0 +1,156 @@
---
name: openspec-apply-change
description: Implement tasks from an OpenSpec change. Use when the user wants to start implementing, continue implementation, or work through tasks.
license: MIT
compatibility: Requires openspec CLI.
metadata:
author: openspec
version: "1.0"
generatedBy: "1.3.1"
---
Implement tasks from an OpenSpec change.
**Input**: Optionally specify a change name. If omitted, check if it can be inferred from conversation context. If vague or ambiguous you MUST prompt for available changes.
**Steps**
1. **Select the change**
If a name is provided, use it. Otherwise:
- Infer from conversation context if the user mentioned a change
- Auto-select if only one active change exists
- If ambiguous, run `openspec list --json` to get available changes and use the **AskUserQuestion tool** to let the user select
Always announce: "Using change: <name>" and how to override (e.g., `/opsx-apply <other>`).
2. **Check status to understand the schema**
```bash
openspec status --change "<name>" --json
```
Parse the JSON to understand:
- `schemaName`: The workflow being used (e.g., "spec-driven")
- Which artifact contains the tasks (typically "tasks" for spec-driven, check status for others)
3. **Get apply instructions**
```bash
openspec instructions apply --change "<name>" --json
```
This returns:
- `contextFiles`: artifact ID -> array of concrete file paths (varies by schema - could be proposal/specs/design/tasks or spec/tests/implementation/docs)
- Progress (total, complete, remaining)
- Task list with status
- Dynamic instruction based on current state
**Handle states:**
- If `state: "blocked"` (missing artifacts): show message, suggest using openspec-continue-change
- If `state: "all_done"`: congratulate, suggest archive
- Otherwise: proceed to implementation
4. **Read context files**
Read every file path listed under `contextFiles` from the apply instructions output.
The files depend on the schema being used:
- **spec-driven**: proposal, specs, design, tasks
- Other schemas: follow the contextFiles from CLI output
5. **Show current progress**
Display:
- Schema being used
- Progress: "N/M tasks complete"
- Remaining tasks overview
- Dynamic instruction from CLI
6. **Implement tasks (loop until done or blocked)**
For each pending task:
- Show which task is being worked on
- Make the code changes required
- Keep changes minimal and focused
- Mark task complete in the tasks file: `- [ ]` → `- [x]`
- Continue to next task
**Pause if:**
- Task is unclear → ask for clarification
- Implementation reveals a design issue → suggest updating artifacts
- Error or blocker encountered → report and wait for guidance
- User interrupts
7. **On completion or pause, show status**
Display:
- Tasks completed this session
- Overall progress: "N/M tasks complete"
- If all done: suggest archive
- If paused: explain why and wait for guidance
**Output During Implementation**
```
## Implementing: <change-name> (schema: <schema-name>)
Working on task 3/7: <task description>
[...implementation happening...]
✓ Task complete
Working on task 4/7: <task description>
[...implementation happening...]
✓ Task complete
```
**Output On Completion**
```
## Implementation Complete
**Change:** <change-name>
**Schema:** <schema-name>
**Progress:** 7/7 tasks complete ✓
### Completed This Session
- [x] Task 1
- [x] Task 2
...
All tasks complete! Ready to archive this change.
```
**Output On Pause (Issue Encountered)**
```
## Implementation Paused
**Change:** <change-name>
**Schema:** <schema-name>
**Progress:** 4/7 tasks complete
### Issue Encountered
<description of the issue>
**Options:**
1. <option 1>
2. <option 2>
3. Other approach
What would you like to do?
```
**Guardrails**
- Keep going through tasks until done or blocked
- Always read context files before starting (from the apply instructions output)
- If task is ambiguous, pause and ask before implementing
- If implementation reveals issues, pause and suggest artifact updates
- Keep code changes minimal and scoped to each task
- Update task checkbox immediately after completing each task
- Pause on errors, blockers, or unclear requirements - don't guess
- Use contextFiles from CLI output, don't assume specific file names
**Fluid Workflow Integration**
This skill supports the "actions on a change" model:
- **Can be invoked anytime**: Before all artifacts are done (if tasks exist), after partial implementation, interleaved with other actions
- **Allows artifact updates**: If implementation reveals design issues, suggest updating artifacts - not phase-locked, work fluidly
@@ -0,0 +1,114 @@
---
name: openspec-archive-change
description: Archive a completed change in the experimental workflow. Use when the user wants to finalize and archive a change after implementation is complete.
license: MIT
compatibility: Requires openspec CLI.
metadata:
author: openspec
version: "1.0"
generatedBy: "1.3.1"
---
Archive a completed change in the experimental workflow.
**Input**: Optionally specify a change name. If omitted, check if it can be inferred from conversation context. If vague or ambiguous you MUST prompt for available changes.
**Steps**
1. **If no change name provided, prompt for selection**
Run `openspec list --json` to get available changes. Use the **AskUserQuestion tool** to let the user select.
Show only active changes (not already archived).
Include the schema used for each change if available.
**IMPORTANT**: Do NOT guess or auto-select a change. Always let the user choose.
2. **Check artifact completion status**
Run `openspec status --change "<name>" --json` to check artifact completion.
Parse the JSON to understand:
- `schemaName`: The workflow being used
- `artifacts`: List of artifacts with their status (`done` or other)
**If any artifacts are not `done`:**
- Display warning listing incomplete artifacts
- Use **AskUserQuestion tool** to confirm user wants to proceed
- Proceed if user confirms
3. **Check task completion status**
Read the tasks file (typically `tasks.md`) to check for incomplete tasks.
Count tasks marked with `- [ ]` (incomplete) vs `- [x]` (complete).
**If incomplete tasks found:**
- Display warning showing count of incomplete tasks
- Use **AskUserQuestion tool** to confirm user wants to proceed
- Proceed if user confirms
**If no tasks file exists:** Proceed without task-related warning.
4. **Assess delta spec sync state**
Check for delta specs at `openspec/changes/<name>/specs/`. If none exist, proceed without sync prompt.
**If delta specs exist:**
- Compare each delta spec with its corresponding main spec at `openspec/specs/<capability>/spec.md`
- Determine what changes would be applied (adds, modifications, removals, renames)
- Show a combined summary before prompting
**Prompt options:**
- If changes needed: "Sync now (recommended)", "Archive without syncing"
- If already synced: "Archive now", "Sync anyway", "Cancel"
If user chooses sync, use Task tool (subagent_type: "general-purpose", prompt: "Use Skill tool to invoke openspec-sync-specs for change '<name>'. Delta spec analysis: <include the analyzed delta spec summary>"). Proceed to archive regardless of choice.
5. **Perform the archive**
Create the archive directory if it doesn't exist:
```bash
mkdir -p openspec/changes/archive
```
Generate target name using current date: `YYYY-MM-DD-<change-name>`
**Check if target already exists:**
- If yes: Fail with error, suggest renaming existing archive or using different date
- If no: Move the change directory to archive
```bash
mv openspec/changes/<name> openspec/changes/archive/YYYY-MM-DD-<name>
```
6. **Display summary**
Show archive completion summary including:
- Change name
- Schema that was used
- Archive location
- Whether specs were synced (if applicable)
- Note about any warnings (incomplete artifacts/tasks)
**Output On Success**
```
## Archive Complete
**Change:** <change-name>
**Schema:** <schema-name>
**Archived to:** openspec/changes/archive/YYYY-MM-DD-<name>/
**Specs:** ✓ Synced to main specs (or "No delta specs" or "Sync skipped")
All artifacts complete. All tasks complete.
```
**Guardrails**
- Always prompt for change selection if not provided
- Use artifact graph (openspec status --json) for completion checking
- Don't block archive on warnings - just inform and confirm
- Preserve .openspec.yaml when moving to archive (it moves with the directory)
- Show clear summary of what happened
- If sync is requested, use openspec-sync-specs approach (agent-driven)
- If delta specs exist, always run the sync assessment and show the combined summary before prompting
+288
View File
@@ -0,0 +1,288 @@
---
name: openspec-explore
description: Enter explore mode - a thinking partner for exploring ideas, investigating problems, and clarifying requirements. Use when the user wants to think through something before or during a change.
license: MIT
compatibility: Requires openspec CLI.
metadata:
author: openspec
version: "1.0"
generatedBy: "1.3.1"
---
Enter explore mode. Think deeply. Visualize freely. Follow the conversation wherever it goes.
**IMPORTANT: Explore mode is for thinking, not implementing.** You may read files, search code, and investigate the codebase, but you must NEVER write code or implement features. If the user asks you to implement something, remind them to exit explore mode first and create a change proposal. You MAY create OpenSpec artifacts (proposals, designs, specs) if the user asks—that's capturing thinking, not implementing.
**This is a stance, not a workflow.** There are no fixed steps, no required sequence, no mandatory outputs. You're a thinking partner helping the user explore.
---
## The Stance
- **Curious, not prescriptive** - Ask questions that emerge naturally, don't follow a script
- **Open threads, not interrogations** - Surface multiple interesting directions and let the user follow what resonates. Don't funnel them through a single path of questions.
- **Visual** - Use ASCII diagrams liberally when they'd help clarify thinking
- **Adaptive** - Follow interesting threads, pivot when new information emerges
- **Patient** - Don't rush to conclusions, let the shape of the problem emerge
- **Grounded** - Explore the actual codebase when relevant, don't just theorize
---
## What You Might Do
Depending on what the user brings, you might:
**Explore the problem space**
- Ask clarifying questions that emerge from what they said
- Challenge assumptions
- Reframe the problem
- Find analogies
**Investigate the codebase**
- Map existing architecture relevant to the discussion
- Find integration points
- Identify patterns already in use
- Surface hidden complexity
**Compare options**
- Brainstorm multiple approaches
- Build comparison tables
- Sketch tradeoffs
- Recommend a path (if asked)
**Visualize**
```
┌─────────────────────────────────────────┐
│ Use ASCII diagrams liberally │
├─────────────────────────────────────────┤
│ │
│ ┌────────┐ ┌────────┐ │
│ │ State │────────▶│ State │ │
│ │ A │ │ B │ │
│ └────────┘ └────────┘ │
│ │
│ System diagrams, state machines, │
│ data flows, architecture sketches, │
│ dependency graphs, comparison tables │
│ │
└─────────────────────────────────────────┘
```
**Surface risks and unknowns**
- Identify what could go wrong
- Find gaps in understanding
- Suggest spikes or investigations
---
## OpenSpec Awareness
You have full context of the OpenSpec system. Use it naturally, don't force it.
### Check for context
At the start, quickly check what exists:
```bash
openspec list --json
```
This tells you:
- If there are active changes
- Their names, schemas, and status
- What the user might be working on
### When no change exists
Think freely. When insights crystallize, you might offer:
- "This feels solid enough to start a change. Want me to create a proposal?"
- Or keep exploring - no pressure to formalize
### When a change exists
If the user mentions a change or you detect one is relevant:
1. **Read existing artifacts for context**
- `openspec/changes/<name>/proposal.md`
- `openspec/changes/<name>/design.md`
- `openspec/changes/<name>/tasks.md`
- etc.
2. **Reference them naturally in conversation**
- "Your design mentions using Redis, but we just realized SQLite fits better..."
- "The proposal scopes this to premium users, but we're now thinking everyone..."
3. **Offer to capture when decisions are made**
| Insight Type | Where to Capture |
|----------------------------|--------------------------------|
| New requirement discovered | `specs/<capability>/spec.md` |
| Requirement changed | `specs/<capability>/spec.md` |
| Design decision made | `design.md` |
| Scope changed | `proposal.md` |
| New work identified | `tasks.md` |
| Assumption invalidated | Relevant artifact |
Example offers:
- "That's a design decision. Capture it in design.md?"
- "This is a new requirement. Add it to specs?"
- "This changes scope. Update the proposal?"
4. **The user decides** - Offer and move on. Don't pressure. Don't auto-capture.
---
## What You Don't Have To Do
- Follow a script
- Ask the same questions every time
- Produce a specific artifact
- Reach a conclusion
- Stay on topic if a tangent is valuable
- Be brief (this is thinking time)
---
## Handling Different Entry Points
**User brings a vague idea:**
```
User: I'm thinking about adding real-time collaboration
You: Real-time collab is a big space. Let me think about this...
COLLABORATION SPECTRUM
════════════════════════════════════════════
Awareness Coordination Sync
│ │ │
▼ ▼ ▼
┌────────┐ ┌────────┐ ┌────────┐
│Presence│ │Cursors │ │ CRDT │
│ "3 │ │ Multi │ │Conflict│
│online" │ │ select │ │ free │
└────────┘ └────────┘ └────────┘
│ │ │
trivial moderate complex
Where's your head at?
```
**User brings a specific problem:**
```
User: The auth system is a mess
You: [reads codebase]
┌─────────────────────────────────────────────┐
│ CURRENT AUTH FLOW │
└─────────────────────────────────────────────┘
┌─────────────┼─────────────┐
▼ ▼ ▼
┌─────────┐ ┌─────────┐ ┌─────────┐
│ Google │ │ GitHub │ │ Email │
│ OAuth │ │ OAuth │ │ Magic │
└────┬────┘ └────┬────┘ └────┬────┘
│ │ │
└─────────────┼─────────────┘
┌───────────┐
│ Session │
└─────┬─────┘
┌───────────┐
│ Perms │
└───────────┘
I see three tangles. Which one's burning?
```
**User is stuck mid-implementation:**
```
User: /opsx-explore add-auth-system
The OAuth integration is more complex than expected
You: [reads change artifacts]
You're on task 4: "Implement OAuth flow"
Let me trace what's involved...
[draws diagram, explores options, suggests paths]
Want to update the design to reflect this?
Or add a spike task to investigate?
```
**User wants to compare options:**
```
User: Should we use Postgres or SQLite?
You: Generic answer is boring. What's the context?
User: A CLI tool that tracks local dev environments
You: That changes everything.
┌─────────────────────────────────────────────────┐
│ CLI TOOL DATA STORAGE │
└─────────────────────────────────────────────────┘
Key constraints:
• No daemon running
• Must work offline
• Single user
SQLite Postgres
Deployment embedded ✓ needs server ✗
Offline yes ✓ no ✗
Single file yes ✓ no ✗
SQLite. Not even close.
Unless... is there a sync component?
```
---
## Ending Discovery
There's no required ending. Discovery might:
- **Flow into a proposal**: "Ready to start? I can create a change proposal."
- **Result in artifact updates**: "Updated design.md with these decisions"
- **Just provide clarity**: User has what they need, moves on
- **Continue later**: "We can pick this up anytime"
When it feels like things are crystallizing, you might summarize:
```
## What We Figured Out
**The problem**: [crystallized understanding]
**The approach**: [if one emerged]
**Open questions**: [if any remain]
**Next steps** (if ready):
- Create a change proposal
- Keep exploring: just keep talking
```
But this summary is optional. Sometimes the thinking IS the value.
---
## Guardrails
- **Don't implement** - Never write code or implement features. Creating OpenSpec artifacts is fine, writing application code is not.
- **Don't fake understanding** - If something is unclear, dig deeper
- **Don't rush** - Discovery is thinking time, not task time
- **Don't force structure** - Let patterns emerge naturally
- **Don't auto-capture** - Offer to save insights, don't just do it
- **Do visualize** - A good diagram is worth many paragraphs
- **Do explore the codebase** - Ground discussions in reality
- **Do question assumptions** - Including the user's and your own
+110
View File
@@ -0,0 +1,110 @@
---
name: openspec-propose
description: Propose a new change with all artifacts generated in one step. Use when the user wants to quickly describe what they want to build and get a complete proposal with design, specs, and tasks ready for implementation.
license: MIT
compatibility: Requires openspec CLI.
metadata:
author: openspec
version: "1.0"
generatedBy: "1.3.1"
---
Propose a new change - create the change and generate all artifacts in one step.
I'll create a change with artifacts:
- proposal.md (what & why)
- design.md (how)
- tasks.md (implementation steps)
When ready to implement, run /opsx-apply
---
**Input**: The user's request should include a change name (kebab-case) OR a description of what they want to build.
**Steps**
1. **If no clear input provided, ask what they want to build**
Use the **AskUserQuestion tool** (open-ended, no preset options) to ask:
> "What change do you want to work on? Describe what you want to build or fix."
From their description, derive a kebab-case name (e.g., "add user authentication" → `add-user-auth`).
**IMPORTANT**: Do NOT proceed without understanding what the user wants to build.
2. **Create the change directory**
```bash
openspec new change "<name>"
```
This creates a scaffolded change at `openspec/changes/<name>/` with `.openspec.yaml`.
3. **Get the artifact build order**
```bash
openspec status --change "<name>" --json
```
Parse the JSON to get:
- `applyRequires`: array of artifact IDs needed before implementation (e.g., `["tasks"]`)
- `artifacts`: list of all artifacts with their status and dependencies
4. **Create artifacts in sequence until apply-ready**
Use the **TodoWrite tool** to track progress through the artifacts.
Loop through artifacts in dependency order (artifacts with no pending dependencies first):
a. **For each artifact that is `ready` (dependencies satisfied)**:
- Get instructions:
```bash
openspec instructions <artifact-id> --change "<name>" --json
```
- The instructions JSON includes:
- `context`: Project background (constraints for you - do NOT include in output)
- `rules`: Artifact-specific rules (constraints for you - do NOT include in output)
- `template`: The structure to use for your output file
- `instruction`: Schema-specific guidance for this artifact type
- `outputPath`: Where to write the artifact
- `dependencies`: Completed artifacts to read for context
- Read any completed dependency files for context
- Create the artifact file using `template` as the structure
- Apply `context` and `rules` as constraints - but do NOT copy them into the file
- Show brief progress: "Created <artifact-id>"
b. **Continue until all `applyRequires` artifacts are complete**
- After creating each artifact, re-run `openspec status --change "<name>" --json`
- Check if every artifact ID in `applyRequires` has `status: "done"` in the artifacts array
- Stop when all `applyRequires` artifacts are done
c. **If an artifact requires user input** (unclear context):
- Use **AskUserQuestion tool** to clarify
- Then continue with creation
5. **Show final status**
```bash
openspec status --change "<name>"
```
**Output**
After completing all artifacts, summarize:
- Change name and location
- List of artifacts created with brief descriptions
- What's ready: "All artifacts created! Ready for implementation."
- Prompt: "Run `/opsx-apply` or ask me to implement to start working on the tasks."
**Artifact Creation Guidelines**
- Follow the `instruction` field from `openspec instructions` for each artifact type
- The schema defines what each artifact should contain - follow it
- Read dependency artifacts for context before creating new ones
- Use `template` as the structure for your output file - fill in its sections
- **IMPORTANT**: `context` and `rules` are constraints for YOU, not content for the file
- Do NOT copy `<context>`, `<rules>`, `<project_context>` blocks into the artifact
- These guide what you write, but should never appear in the output
**Guardrails**
- Create ALL artifacts needed for implementation (as defined by schema's `apply.requires`)
- Always read dependency artifacts before creating a new one
- If context is critically unclear, ask the user - but prefer making reasonable decisions to keep momentum
- If a change with that name already exists, ask if user wants to continue it or create a new one
- Verify each artifact file exists after writing before proceeding to next
+2
View File
@@ -0,0 +1,2 @@
380698
1778526681407
+6
View File
@@ -0,0 +1,6 @@
# Runtime data
*.db
*.db-journal
*.db-wal
*.db-shm
daemon-state.json
+20
View File
@@ -0,0 +1,20 @@
# Stoneforge Configuration
database: stoneforge.db
sync:
auto_export: true
elements_file: elements.jsonl
dependencies_file: dependencies.jsonl
playbooks:
paths:
- playbooks
identity:
mode: soft
merge:
auto_merge: true
target_branch: stoneforge/review
require_approval: false
workflow:
preset: review
agents:
permission_model: unrestricted
View File
+1
View File
@@ -0,0 +1 @@
{"id":"el-2rt3","type":"entity","createdAt":"2026-05-11T19:11:16.359Z","updatedAt":"2026-05-11T19:20:17.978Z","createdBy":"el-0000","tags":[],"metadata":{"agent":{"agentRole":"director","sessionStatus":"idle","maxConcurrentTasks":1,"channelId":"el-4cua","provider":"opencode","model":"kimi-for-coding/k2p6"}},"name":"director","entityType":"agent"}
+24 -1
View File
@@ -3,21 +3,25 @@
from __future__ import annotations
import logging
import secrets
from functools import lru_cache
from typing import Any
from urllib.parse import urljoin
import jwt
import requests
from fastapi import Request
from fastapi import Header, HTTPException, Request
from fastapi.responses import JSONResponse
from jwt import PyJWKClient
from jwt.exceptions import InvalidTokenError
from media_library_viewer_api.config import Settings, get_settings
from media_library_viewer_api.dependencies import get_settings_store
logger = logging.getLogger(__name__)
_API_KEY: str | None = None
EXEMPT_PATHS = {
"/api/health",
"/docs",
@@ -115,3 +119,22 @@ async def require_jwt_auth(request: Request, call_next):
request.state.jwt_claims = claims
request.state.jwt_subject = claims.get("sub") if isinstance(claims, dict) else None
return await call_next(request)
def get_api_key() -> str:
global _API_KEY
if _API_KEY is None:
store = get_settings_store()
settings = store.get_settings()
_API_KEY = settings.get("backup_api_key")
if not _API_KEY:
_API_KEY = secrets.token_urlsafe(32)
store.update_setting("backup_api_key", _API_KEY)
return _API_KEY
def require_api_key(authorization: str = Header("", alias="Authorization")) -> str:
expected = f"Bearer {get_api_key()}"
if not authorization or not secrets.compare_digest(authorization, expected):
raise HTTPException(status_code=401, detail="Invalid or missing API key")
return authorization
@@ -1205,6 +1205,69 @@ class SettingsStore:
)
return int(cur.rowcount or 0)
# ------------------------------------------------------------------
# Generic key-value settings
# ------------------------------------------------------------------
def get_settings(self) -> dict[str, Any]:
"""Return all generic settings as a dict."""
self.init_schema()
with self.connect() as conn:
conn.execute(
"""
CREATE TABLE IF NOT EXISTS app_settings (
key TEXT PRIMARY KEY,
value TEXT NOT NULL,
updated_at INTEGER NOT NULL
)
"""
)
rows = conn.execute("SELECT key, value FROM app_settings").fetchall()
return {row["key"]: row["value"] for row in rows}
def get_setting(self, key: str, default: Any = None) -> Any:
"""Return a single setting value or default."""
self.init_schema()
with self.connect() as conn:
conn.execute(
"""
CREATE TABLE IF NOT EXISTS app_settings (
key TEXT PRIMARY KEY,
value TEXT NOT NULL,
updated_at INTEGER NOT NULL
)
"""
)
row = conn.execute(
"SELECT value FROM app_settings WHERE key = ?", (key,)
).fetchone()
return row["value"] if row else default
def update_setting(self, key: str, value: str) -> None:
"""Set a generic key-value setting."""
self.init_schema()
now = int(time.time())
with self.connect() as conn:
conn.execute(
"""
CREATE TABLE IF NOT EXISTS app_settings (
key TEXT PRIMARY KEY,
value TEXT NOT NULL,
updated_at INTEGER NOT NULL
)
"""
)
conn.execute(
"""
INSERT INTO app_settings (key, value, updated_at)
VALUES (?, ?, ?)
ON CONFLICT(key) DO UPDATE SET
value = excluded.value,
updated_at = excluded.updated_at
""",
(key, value, now),
)
_store: SettingsStore | None = None
+47
View File
@@ -60,6 +60,53 @@ def test_create_job_and_run():
assert run["status"] == "success"
def test_api_key_auth():
with tempfile.TemporaryDirectory() as tmpdir:
db_path = Path(tmpdir) / "test_settings.sqlite"
store = SettingsStore(db_path)
store.init_schema()
from fastapi import HTTPException
from media_library_viewer_api.auth import get_api_key, require_api_key
from media_library_viewer_api.services import settings_store
# Monkey-patch the global store for this test
original_store = settings_store._store
settings_store._store = store
# Reset the cached API key
import media_library_viewer_api.auth as auth_module
auth_module._API_KEY = None
try:
# Should generate and return an API key
api_key = get_api_key()
assert api_key
assert len(api_key) > 0
# Should raise 401 without key
try:
require_api_key("")
assert False, "Should have raised"
except HTTPException as e:
assert e.status_code == 401
# Should raise 401 with wrong key
try:
require_api_key("Bearer wrong-key")
assert False, "Should have raised"
except HTTPException as e:
assert e.status_code == 401
# Should succeed with correct key
result = require_api_key(f"Bearer {api_key}")
assert result == f"Bearer {api_key}"
finally:
settings_store._store = original_store
auth_module._API_KEY = None
def test_alert_failed_status():
from media_library_viewer_api.services.backup_alert_engine import generate_alerts_for_run
@@ -0,0 +1,53 @@
# Obsidian Documentation Structure for Manage (Media Library Viewer)
**Date:** 2026-05-08
**Status:** Approved
## Purpose
Create a comprehensive, interconnected Obsidian documentation vault for the Manage project (media library viewer application). The documentation targets all audiences: future developers, contributors, operators, and deployers.
## Structure
### Map of Content (MOC)
- `manage/Overview.md` — Central hub with wikilinks to all documentation areas
### Architecture & Overview
- `manage/Architecture.md` — System design, data flow, tech stack
- `manage/Directory Structure.md` — Annotated codebase layout
### Backend
- `manage/Backend/API Endpoints.md` — Complete REST endpoint map
- `manage/Backend/Configuration.md` — Settings, env vars, OIDC
- `manage/Backend/Services.md` — Core services: settings store, media index, poller, mail
- `manage/Backend/Clients.md` — External integrations: Jellyfin, Jellyseerr, SSH
### Frontend
- `manage/Frontend/Pages & Routing.md` — Routes, page components
- `manage/Frontend/Components.md` — Reusable components
- `manage/Frontend/State & Data.md` — Hooks, QueryClient, data fetching
- `manage/Frontend/Auth & Theme.md` — OIDC auth, MUI theme
### Deployment
- `manage/Deployment/Production.md` — Docker Compose, Traefik, TLS
- `manage/Deployment/Development.md` — Dev workflow, hot reload
### Operations
- `manage/Operations/Machine Management.md` — SSH keys, collectors
- `manage/Operations/Monitoring.md` — Poller, metrics
- `manage/Operations/Tasks & Jobs.md` — Saved tasks, job templates
### Development
- `manage/Development/Setup.md` — Getting started for backend + frontend
- `manage/Development/Testing.md` — Test structure and commands
- `manage/Development/Contributing.md` — Conventions, PR workflow
## Cross-Linking Conventions
- Every note uses YAML frontmatter with `tags` and `aliases`
- Related docs linked via `[[wikilinks]]`
- Callouts for warnings, tips, and notes
## Technologies Referenced
- Backend: Python 3.11+, FastAPI, SQLite, Paramiko, PyJWT
- Frontend: React 19, TypeScript 6, Vite 8, MUI 9, D3 7, AG Grid
- Infra: Docker, Traefik, Authentik/OIDC
+20
View File
@@ -0,0 +1,20 @@
schema: spec-driven
# Project context (optional)
# This is shown to AI when creating artifacts.
# Add your tech stack, conventions, style guides, domain knowledge, etc.
# Example:
# context: |
# Tech stack: TypeScript, React, Node.js
# We use conventional commits
# Domain: e-commerce platform
# Per-artifact rules (optional)
# Add custom rules for specific artifacts.
# Example:
# rules:
# proposal:
# - Keep proposals under 500 words
# - Always include a "Non-goals" section
# tasks:
# - Break tasks into chunks of max 2 hours
+186
View File
@@ -0,0 +1,186 @@
═══════════════════════════════════════════════════════════════════════════
Token Analysis: Session ses_1fbc61a9effehz5Mmv2vbgjhMt
Model: deepseek-v4-flash
═══════════════════════════════════════════════════════════════════════════
WARNINGS
───────────────────────────────────────────────────
- Pricing for 'openrouter/deepseek/deepseek-v4-flash' was not found in models.json. Cost estimates use the default fallback rates ($1/M input, $3/M output, no cache pricing).
TOKEN BREAKDOWN BY CATEGORY
─────────────────────────────────────────────────────────────────────────
Estimated using tokenizer analysis of message content:
Input Categories:
SYSTEM ████████████████████████░░░░░░ 79.0% (15,386)
USER ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 1.1% (210)
TOOLS ██████░░░░░░░░░░░░░░░░░░░░░░░░ 19.9% (3,877)
Subtotal: 19,473 estimated input tokens
Note: inferred system/overhead values are heuristic estimates from API telemetry.
Output Categories:
ASSISTANT ██████████████░░░░░░░░░░░░░░░░ 47.9% (114)
REASONING ████████████████░░░░░░░░░░░░░░ 52.1% (124)
Subtotal: 238 estimated output tokens
Local Total: 19,711 tokens (estimated)
TOOL USAGE BREAKDOWN
─────────────────────────────────────────────────────────────────────────
task █████████████████████████████░ 97.7% (3,786) 1x
mystatus █░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 2.3% (91) 1x
TOP CONTRIBUTORS
─────────────────────────────────────────────────────────────────────────
• System (inferred from API telemetry) 15,386 tokens (78.1%)
• task 3,786 tokens (19.2%)
• Assistant#1 93 tokens (0.5%)
• mystatus 91 tokens (0.5%)
• Reasoning#3 81 tokens (0.4%)
═══════════════════════════════════════════════════════════════════════════
MOST RECENT API CALL
─────────────────────────────────────────────────────────────────────────
Raw telemetry from last API response:
Input (fresh): 19,473 tokens
Cache read: 0 tokens
Output: 363 tokens
Reasoning: 81 tokens
Provider total: 19,917 tokens
Cost: $0.0029
─────────────────────────────────────
Total: 19,917 tokens
═══════════════════════════════════════════════════════════════════════════
SESSION TOTALS (All 3 API calls)
─────────────────────────────────────────────────────────────────────────
Assistant messages observed: 4 (structural count)
Total tokens processed across the entire session (for cost calculation):
Input tokens: 52,509 (fresh tokens across all calls)
Cache read: 4,736 (cached tokens across all calls)
Cache write: 0 (tokens written to cache)
Output tokens: 488 (all model responses)
Reasoning tokens: 124 (thinking/reasoning)
─────────────────────────────────────
Session Total: 57,857 tokens (for billing)
Cache read calls: 1 / 3
Cache write calls: 0 / 3
═══════════════════════════════════════════════════════════════════════════
SESSION COST
─────────────────────────────────────────────────────────────────────────
Token usage breakdown:
Input tokens: 52,509
Output tokens: 488
Reasoning tokens: 124
Cache read: 4,736
─────────────────────────────────────────────────────────────────────────
ACTUAL COST (from API): $0.0077
Estimated cost: $0.0543 (+609.9% diff)
Note: Actual cost from OpenCode includes provider-specific pricing
and 200K+ context adjustments.
══════════════════════════════════════════════════════════════════════════
AVAILABLE SKILLS (always-available context)
─────────────────────────────────────────────────────────────────────────
These skills were recovered from the skill tool metadata available to this session.
Skill Description Tokens
───────────────────────────────────────────────────────────────────────
verification-before-c… Use when about to claim work is complete, fi… ~47
finishing-a-developme… Use when implementation is complete, all tes… ~46
receiving-code-review Use when receiving code review feedback, bef… ~40
using-git-worktrees Use when starting feature work that needs is… ~40
brainstorming You MUST use this before any creative work -… ~39
using-superpowers Use when starting any conversation - establi… ~31
dispatching-parallel-… Use when facing 2+ independent tasks that ca… ~28
auto-commit Use when you are making multiple edits or co… ~25
executing-plans Use when you have a written implementation p… ~25
requesting-code-review Use when completing tasks, implementing majo… ~24
systematic-debugging Use when encountering any bug, test failure,… ~24
writing-plans Use when you have a spec or requirements for… ~23
writing-skills Use when creating new skills, editing existi… ~22
subagent-driven-devel… Use when executing implementation plans with… ~19
test-driven-developme… Use when implementing any feature or bugfix,… ~19
───────────────────────────────────────────────────────────────────────
Total: ~452 tokens (15 skills available)
Note: Full skill tool description is ~657 tokens (includes boilerplate).
═══════════════════════════════════════════════════════════════════════════
AVAILABLE SUBAGENTS (in task tool definition)
─────────────────────────────────────────────────────────────────────────
These subagents are embedded in the task tool description and consume tokens on every API call.
Subagent Description Tokens
───────────────────────────────────────────────────────────────────────
explore Fast agent specialized for exploring codebases. U… ~105
general General-purpose agent for researching complex que… ~28
───────────────────────────────────────────────────────────────────────
Total: ~133 tokens (2 subagents available)
Note: Full task tool description is ~983 tokens (includes instructions/examples).
═══════════════════════════════════════════════════════════════════════════
TOOL DEFINITION COSTS (Estimated from argument analysis)
─────────────────────────────────────────────────────────────────────────
Tool Est. Tokens Args Complexity
───────────────────────────────────────────────────────────────────
task ~370 3 simple
tokenscope ~310 1 simple
mystatus ~280 0 simple
───────────────────────────────────────────────────────────────────
Total: ~ 960 tokens (3 enabled tools)
Note: Estimates inferred from tool call arguments in this session.
Actual schema tokens may vary +/-20%.
═══════════════════════════════════════════════════════════════════════════
CACHE EFFICIENCY
─────────────────────────────────────────────────────────────────────────
Token Distribution:
Cache Read: 4,736 tokens ██░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 8.3%
Fresh Input: 52,509 tokens ████████████████████████████░░ 91.7%
───────────────────────────────────────────────────────────────────
Cache Hit Rate: 8.3% (cache read / (cache read + fresh input))
Cost Analysis (deepseek-v4-flash @ $1.00/M input, $0.00/M cache read, $0.00/M cache write):
Without caching: $0.0572 (57,245 tokens x $1.00/M)
With caching: $0.0525 (fresh x $1.00/M + cache read x $0.00/M + cache write x $0.00/M)
───────────────────────────────────────────────────────────────────
Cost Savings: $0.0047 (8.3% reduction)
Effective Rate: $0.92/M tokens (vs. $1.00/M standard)
═══════════════════════════════════════════════════════════════════════════
SUBAGENT COSTS (2 child sessions, 16 API calls)
─────────────────────────────────────────────────────────────────────────
explore $0.0359 (372,189 tokens, 8 calls)
explore $0.0422 (359,195 tokens, 8 calls)
─────────────────────────────────────────────────────────────────────────
Subagent Total: $0.0781 (731,384 tokens, 16 calls)
═══════════════════════════════════════════════════════════════════════════
SUMMARY
─────────────────────────────────────────────────────────────────────────
Cost Tokens API Calls
Main session: $ 0.0077 57,857 3
Subagents: $ 0.0781 731,384 16
─────────────────────────────────────────────────────────────────────────
TOTAL: $ 0.0858 789,241 19
═══════════════════════════════════════════════════════════════════════════