fifony 0.1.25-next.227d159 → 0.1.26-next.f3fade3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -4,7 +4,7 @@
4
4
 
5
5
  **AI agents that actually ship code. You just watch.**
6
6
 
7
- Point at a repo. Open the dashboard. AI plans, builds, and reviews — you approve.
7
+ Point at a repo. Open the dashboard. AI plans, builds, and reviews — you approve and merge.
8
8
 
9
9
  [![License](https://img.shields.io/badge/license-Apache%202.0-blue.svg)](LICENSE)
10
10
  [![Node](https://img.shields.io/badge/node-%3E%3D23-brightgreen.svg)]()
@@ -16,42 +16,60 @@ Point at a repo. Open the dashboard. AI plans, builds, and reviews — you appro
16
16
  ## Quick Start
17
17
 
18
18
  ```bash
19
- npx -y fifony --port 4040
19
+ npx -y fifony
20
20
  ```
21
21
 
22
- Open **http://localhost:4040**. The first run launches the onboarding wizard — it detects your CLIs, scans your project, and configures everything in six steps. State lives in `.fifony/`. No accounts, no cloud, no external database.
22
+ Open **http://localhost:4000**. The first run launches the onboarding wizard — it detects your CLIs, scans your project, and configures everything in six steps. State lives in `.fifony/`. No accounts, no cloud, no external database.
23
23
 
24
24
  ---
25
25
 
26
26
  ## How It Works
27
27
 
28
- fifony breaks every task into three stages, each independently configurable:
29
-
30
- ```
31
- Plan Execute Review
32
- ┌──────────────┐ ┌──────────────┐ ┌──────────────┐
33
- │ Claude │─▶│ Codex │─▶│ Claude │
34
- │ Opus 4.5 │ │ │ │ Sonnet 4.5 │
35
- │ effort: high │ │ effort: med │ │ effort: med │
36
- └──────────────┘ └──────────────┘ └──────────────┘
37
- ```
38
-
39
- You set the provider, model, and reasoning effort for each stage. Claude plans, Codex executes, Claude reviews — or any combination you prefer. Configure it in the Settings UI or drop a `WORKFLOW.md` in your project root.
28
+ fifony auto-detects your installed CLI tools (Claude, Codex, Gemini) and routes each pipeline stage to the best available provider. Configure per-stage provider, model, and reasoning effort in the Settings UI or drop a `WORKFLOW.md` in your project root.
40
29
 
41
30
  ### Issue Lifecycle
42
31
 
43
- ```
44
- Planning → Todo → Queued → Running → In Review → Done
45
- ↓ ↓
46
- Interrupted Blocked (retry with backoff)
32
+ ```mermaid
33
+ stateDiagram-v2
34
+ [*] --> Planning
35
+ Planning --> Planned: PLANNED
36
+ Planned --> Queued: QUEUE
37
+ Queued --> Running: RUN
38
+ Running --> Reviewing: REVIEW
39
+ Reviewing --> Reviewed: REVIEWED
40
+ Reviewed --> Done: DONE
41
+ Done --> Merged: MERGE
42
+
43
+ Running --> Blocked: BLOCK
44
+ Reviewing --> Blocked: BLOCK
45
+ Blocked --> Queued: UNBLOCK
46
+ Blocked --> Planning: REPLAN
47
+
48
+ Reviewed --> Queued: REQUEUE (rework)
49
+ Reviewed --> Planning: REPLAN
50
+
51
+ Done --> Planning: REOPEN
52
+ Merged --> Planning: REOPEN
53
+
54
+ Planning --> Cancelled: CANCEL
55
+ Planned --> Cancelled: CANCEL
56
+ Reviewed --> Cancelled: CANCEL
57
+ Blocked --> Cancelled: CANCEL
58
+ Cancelled --> Planning: REOPEN
59
+
60
+ Merged --> [*]
61
+ Cancelled --> [*]
47
62
  ```
48
63
 
49
- 1. **Create** Describe what you want done. fifony AI-enhances the title and description before planning.
50
- 2. **Plan** — The planner agent generates a structured execution plan: phases, steps, target files, complexity, risks.
51
- 3. **Approve** You review the plan. Optionally chat with the AI to refine it before approving.
52
- 4. **Execute** Agents run in an isolated workspace (a copy of your project). Live output streams to the dashboard.
53
- 5. **Review** The reviewer agent inspects the diff and either approves, requests rework, or blocks.
54
- 6. **Merge** You review the diff and merge the workspace back to your project root.
64
+ | Step | What happens |
65
+ |------|-------------|
66
+ | **Create** | Describe what you want done. |
67
+ | **Plan** | The planner agent generates a structured execution plan: phases, steps, target files, complexity, risks. |
68
+ | **Approve** | You review the plan. Optionally refine it with AI chat before approving. |
69
+ | **Execute** | Agents run in an isolated git worktree. Live output streams to the dashboard. |
70
+ | **Review** | The reviewer agent inspects the diff and either approves, requests rework, or blocks. |
71
+ | **Done** | Approved and waiting for merge. You review the diff in the dashboard. |
72
+ | **Merge** | You merge the worktree into your project. Analytics capture lines added/removed. |
55
73
 
56
74
  Agents run as detached child processes, tracked by PID. If the server restarts mid-run, fifony recovers on the next boot.
57
75
 
@@ -63,7 +81,7 @@ The first run walks you through six steps:
63
81
 
64
82
  | Step | What happens |
65
83
  |------|-------------|
66
- | CLI Detection | Finds `claude`, `codex`, `git`, `node`, `docker`, and other tools on your system |
84
+ | CLI Detection | Finds `claude`, `codex`, `gemini`, `git`, `node`, `docker`, and other tools on your system |
67
85
  | Project Scan | Detects language, stack, and build system — 18+ ecosystems supported |
68
86
  | AI Analysis | Uses the detected CLI to extract domain context from your codebase |
69
87
  | Domains | 21 options across Technical / Industry / Role, pre-selected by the AI |
@@ -72,16 +90,14 @@ The first run walks you through six steps:
72
90
 
73
91
  Settings are saved progressively and can be re-run from Settings at any time.
74
92
 
75
- Supported build files include: `package.json`, `Cargo.toml`, `pyproject.toml`, `go.mod`, `build.gradle`, `Gemfile`, `mix.exs`, `pubspec.yaml`, `CMakeLists.txt`, `composer.json`, `Package.swift`, `deno.json`, `pom.xml`, `Dockerfile`, and more.
76
-
77
93
  ---
78
94
 
79
95
  ## Dashboard
80
96
 
81
97
  | Route | What you see |
82
98
  |-------|-------------|
83
- | `/kanban` | Drag-and-drop board. Cards flow through pipeline stages. Desktop click+drag, mobile long-press. |
84
- | `/issues` | Searchable list with multi-state filters, sort options, and capability filters. Shows token usage and duration per issue. |
99
+ | `/kanban` | Drag-and-drop board with 5 columns: Planning, In Progress, Reviewing, Blocked, Done. |
100
+ | `/issues` | Searchable list with multi-state filters, sort options, and capability filters. |
85
101
  | `/agents` | Live cockpit: worker slots, queue depth, real-time log tail, token sparklines per agent. |
86
102
  | `/analytics` | Token usage trends, daily and weekly rollups, top issues by cost, per-model breakdown. |
87
103
  | `/settings` | General, Workflow pipeline config, Notifications, Providers. |
@@ -125,20 +141,20 @@ Agents install to `.claude/agents/` and `.codex/agents/` during onboarding. Skil
125
141
  ## CLI Reference
126
142
 
127
143
  ```bash
128
- # Dashboard + API + scheduler
129
- npx -y fifony --port 4040
144
+ # Dashboard + API (default port 4000)
145
+ npx -y fifony
130
146
 
131
- # With Vite HMR for frontend development
132
- npx -y fifony --port 4040 --dev
147
+ # Custom port
148
+ npx -y fifony --port 8080
133
149
 
134
- # Headless scheduler only, no UI
135
- npx -y fifony
150
+ # With Vite HMR for frontend development
151
+ npx -y fifony --dev
136
152
 
137
153
  # MCP server (stdio)
138
154
  npx -y fifony mcp
139
155
 
140
156
  # Different workspace
141
- npx -y fifony --workspace /path/to/repo --port 4040
157
+ npx -y fifony --workspace /path/to/repo
142
158
 
143
159
  # Run one scheduler cycle and exit
144
160
  npx -y fifony --once
@@ -187,7 +203,7 @@ Add to `claude_desktop_config.json` or VS Code settings:
187
203
 
188
204
  ## REST API
189
205
 
190
- Interactive docs at `http://localhost:4040/docs`.
206
+ Interactive docs at `http://localhost:4000/docs`.
191
207
 
192
208
  | Endpoint | Description |
193
209
  |----------|-------------|
@@ -223,6 +239,7 @@ FIFONY_AGENT_PROVIDER=codex # codex | claude
223
239
  FIFONY_WORKER_CONCURRENCY=2
224
240
  FIFONY_MAX_ATTEMPTS=3
225
241
  FIFONY_AGENT_MAX_TURNS=4
242
+ FIFONY_LOG_FILE=0 # set to 1 to also write .fifony/fifony-local.log
226
243
  ```
227
244
 
228
245
  ---
@@ -233,49 +250,23 @@ FIFONY_AGENT_MAX_TURNS=4
233
250
  .fifony/
234
251
  s3db/ ← durable database (issues, events, sessions, settings)
235
252
  source/ ← project snapshot used for workspace seeding
236
- workspaces/ ← isolated per-issue execution directories
253
+ workspaces/ ← isolated per-issue execution directories (git worktrees)
237
254
  ```
238
255
 
239
- fifony is split into an explicit runtime contract:
240
-
241
- - **Persistence layer (s3db.js + resource config):**
242
- - Issues, events, sessions, states, and settings are first-class resources under `.fifony/s3db/`.
243
- - No external DB is required in normal mode.
244
- - **Eventual consistency analytics:**
245
- - `EventualConsistencyPlugin` is enabled for issue metrics and supports counters, incremental updates, and analytics rollups.
246
- - Metrics tracked include:
247
- - `usage.tokens` and `tokenUsage` by phase/model,
248
- - `eventsCount`,
249
- - `linesAdded`, `linesRemoved`, `filesChanged`.
250
- - We query this through `getLastNDays/getLastNHours` to feed `/api/analytics/lines` and `/api/analytics/tokens`.
251
- - **Issue lifecycle in StateMachinePlugin (single source of truth):**
252
- - States, guarded transitions, actions, and triggers are centralized in `issue-state-machine.ts`.
253
- - Public commands (`approve`, `execute`, `queue`, `retry`, `merge`) only emit transitions; the workflow behavior is not duplicated in route handlers.
254
- - The machine also owns interruption recovery, retry delays, terminal transitions, and rollback edges.
255
- - **Queue and execution pool:**
256
- - `s3queue` is the runtime queue adapter with polling workers.
257
- - Workers consume planning/execution/review jobs and run concurrently respecting configured concurrency.
258
- - **Agent abstraction:**
259
- - fifony wraps local CLIs, not proprietary model logic.
260
- - Per stage we configure `cli`, `model`, and `reasoningEffort` independently (`planning`, `execute`, `review`).
261
- - This lets you swap providers by issue without changing core orchestration.
262
- - **Per-issue isolation:**
263
- - Each issue has its own execution workspace and Git worktree branch.
264
- - That avoids file conflicts and enables parallel work on the same repo safely.
265
- - **Review and execution observability:**
266
- - Diff stats are computed from git `--stat` output (`linesAdded`, `linesRemoved`, `filesChanged`).
267
- - Runtime events are emitted for state transitions, review path decisions, and merge outcomes.
268
- - CLI output and audit trails are stored to make mid-run interruptions recoverable.
269
- - **Capability routing:**
270
- - Routing derives issue labels (`capability:<category>`, `overlay:<name>`) from issue text and inferred file paths.
271
- - This improves queue triage and worker assignment.
256
+ - **State machine (single source of truth):** States, guarded transitions, actions, and triggers are centralized in `issue-state-machine.ts`. All side effects (events, field mutations, EC tracking) happen in FSM entry actions.
257
+ - **Persistence (s3db.js):** Issues, events, sessions, and settings are first-class resources. No external DB required.
258
+ - **Eventual consistency analytics:** `EventualConsistencyPlugin` tracks `linesAdded`, `linesRemoved`, `filesChanged`, token usage, and event counts with daily cohort rollups.
259
+ - **Queue workers:** `S3QueuePlugin` dispatches planning/execution/review jobs to concurrent workers.
260
+ - **Agent abstraction:** Wraps local CLIs (Claude, Codex, Gemini), not proprietary model logic. Per-stage provider/model/effort configuration.
261
+ - **Per-issue isolation:** Each issue gets its own git worktree branch. No file conflicts, safe parallel work.
262
+ - **Capability routing:** Issue labels derived from text and file paths drive automatic provider/agent selection.
272
263
 
273
264
  ---
274
265
 
275
266
  ## Requirements
276
267
 
277
268
  - Node.js 23 or newer
278
- - At least one of: `claude` CLI, `codex` CLI
269
+ - At least one of: `claude` CLI, `codex` CLI, `gemini` CLI
279
270
 
280
271
  ---
281
272
 
@@ -1,4 +1,4 @@
1
- const CACHE_VERSION = "1774027280112";
1
+ const CACHE_VERSION = "1774027862578";
2
2
  const CORE_CACHE = `fifony-core-${CACHE_VERSION}`;
3
3
  const ASSET_CACHE = `fifony-assets-${CACHE_VERSION}`;
4
4
  const APP_SHELL_ROUTES = ["/kanban", "/issues", "/agents", "/settings", "/onboarding"];
package/package.json CHANGED
@@ -1,21 +1,31 @@
1
1
  {
2
2
  "name": "fifony",
3
- "version": "0.1.25-next.227d159",
3
+ "version": "0.1.26-next.f3fade3",
4
4
  "private": false,
5
5
  "type": "module",
6
- "description": "Filesystem-backed local fifony orchestrator with a TypeScript CLI, MCP mode, and multi-agent Codex or Claude workflows.",
6
+ "license": "Apache-2.0",
7
+ "author": "Filipe Forattini <filipe@forattini.com.br>",
8
+ "description": "Filesystem-backed local orchestrator with a TypeScript CLI, MCP mode, and multi-agent Codex or Claude workflows.",
9
+ "keywords": [
10
+ "ai",
11
+ "agent",
12
+ "orchestrator",
13
+ "codex",
14
+ "claude",
15
+ "gemini",
16
+ "mcp",
17
+ "cli",
18
+ "automation"
19
+ ],
7
20
  "bin": {
8
21
  "fifony": "./bin/fifony.js",
9
22
  "fifony-wrap": "./bin/fifony-wrap.js"
10
23
  },
11
24
  "files": [
12
25
  "bin/",
13
- "dist/",
26
+ "dist/**/*.js",
14
27
  "app/dist/",
15
- "app/public/",
16
- "src/fixtures/",
17
28
  "README.md",
18
- "FIFONY.md",
19
29
  "LICENSE",
20
30
  "NOTICE"
21
31
  ],
@@ -34,25 +44,25 @@
34
44
  "node": ">=23"
35
45
  },
36
46
  "dependencies": {
37
- "@tanstack/react-query": "^5.91.2",
38
- "@tanstack/react-router": "^1.167.5",
39
47
  "cli-args-parser": "^1.0.6",
40
- "lucide-react": "^0.577.0",
41
48
  "node-cron": "^4.2.1",
42
49
  "pino": "^10.3.1",
43
50
  "pino-pretty": "^13.1.3",
44
51
  "raffel": "^1.0.18",
45
- "react": "^19.2.4",
46
- "react-dom": "^19.2.4",
47
52
  "recker": "^1.0.86",
48
53
  "s3db.js": "21.3.2",
49
54
  "yaml": "^2.8.2"
50
55
  },
51
56
  "devDependencies": {
52
57
  "@tailwindcss/vite": "^4.2.2",
58
+ "@tanstack/react-query": "^5.91.2",
59
+ "@tanstack/react-router": "^1.167.5",
53
60
  "@tanstack/router-plugin": "^1.166.14",
54
61
  "@vitejs/plugin-react": "^6.0.1",
55
62
  "daisyui": "^5.5.19",
63
+ "lucide-react": "^0.577.0",
64
+ "react": "^19.2.4",
65
+ "react-dom": "^19.2.4",
56
66
  "tailwindcss": "^4.2.2",
57
67
  "tsup": "^8.5.1",
58
68
  "tsx": "^4.21.0",
Binary file
@@ -1,8 +0,0 @@
1
- <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" role="img" aria-labelledby="title">
2
- <title id="title">Fifony</title>
3
- <rect width="512" height="512" fill="#0f172a"/>
4
- <rect x="88" y="88" width="336" height="336" rx="72" fill="#13223d"/>
5
- <path fill="#4ade80" d="M154 160h204c11 0 20 9 20 20v26H134v-26c0-11 9-20 20-20z"/>
6
- <path fill="#22d3ee" d="M130 212h252l-24 138c-3 15-16 26-31 26H185c-15 0-28-11-31-26L130 212z"/>
7
- <circle cx="256" cy="326" r="24" fill="#0f172a"/>
8
- </svg>
@@ -1,7 +0,0 @@
1
- <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" role="img" aria-labelledby="title">
2
- <title id="title">Fifony</title>
3
- <rect width="512" height="512" rx="96" fill="#0f172a"/>
4
- <path fill="#4ade80" d="M130 132h252c13 0 24 11 24 24v32H106v-32c0-13 11-24 24-24z"/>
5
- <path fill="#22d3ee" d="M102 192h308l-30 170c-3 18-19 32-38 32H170c-19 0-35-14-38-32L102 192z"/>
6
- <circle cx="256" cy="332" r="26" fill="#0f172a"/>
7
- </svg>
@@ -1,49 +0,0 @@
1
- {
2
- "id": "/",
3
- "name": "fifony Dashboard",
4
- "short_name": "fifony",
5
- "description": "fifony local orchestrator dashboard (React + PWA)",
6
- "lang": "en-US",
7
- "start_url": "/kanban",
8
- "scope": "/",
9
- "display": "standalone",
10
- "display_override": ["window-controls-overlay", "standalone", "browser"],
11
- "background_color": "#020617",
12
- "theme_color": "#0f172a",
13
- "orientation": "portrait-primary",
14
- "prefer_related_applications": false,
15
- "categories": ["productivity", "developer", "business"],
16
- "shortcuts": [
17
- {
18
- "name": "Kanban",
19
- "short_name": "Kanban",
20
- "url": "/kanban",
21
- "icons": [{ "src": "/icon.svg", "sizes": "any", "type": "image/svg+xml" }]
22
- },
23
- {
24
- "name": "Issues",
25
- "short_name": "Issues",
26
- "url": "/issues",
27
- "icons": [{ "src": "/icon.svg", "sizes": "any", "type": "image/svg+xml" }]
28
- },
29
- {
30
- "name": "Agents",
31
- "short_name": "Agents",
32
- "url": "/agents",
33
- "icons": [{ "src": "/icon.svg", "sizes": "any", "type": "image/svg+xml" }]
34
- }
35
- ],
36
- "icons": [
37
- {
38
- "src": "/icon.svg",
39
- "type": "image/svg+xml",
40
- "sizes": "any"
41
- },
42
- {
43
- "src": "/icon-maskable.svg",
44
- "type": "image/svg+xml",
45
- "sizes": "any",
46
- "purpose": "any maskable"
47
- }
48
- ]
49
- }
@@ -1,86 +0,0 @@
1
- <!doctype html>
2
- <html lang="en">
3
- <head>
4
- <meta charset="UTF-8" />
5
- <meta name="viewport" content="width=device-width, initial-scale=1" />
6
- <meta name="theme-color" content="#0f172a" />
7
- <title>Offline · fifony</title>
8
- <style>
9
- :root {
10
- color-scheme: dark;
11
- --bg: #020617;
12
- --panel: #0f172a;
13
- --line: rgba(148, 163, 184, 0.18);
14
- --text: #e2e8f0;
15
- --muted: #94a3b8;
16
- --accent: #22d3ee;
17
- --accent-2: #4ade80;
18
- }
19
- * { box-sizing: border-box; }
20
- body {
21
- margin: 0;
22
- min-height: 100vh;
23
- display: grid;
24
- place-items: center;
25
- background:
26
- radial-gradient(circle at top, rgba(34, 211, 238, 0.14), transparent 42%),
27
- radial-gradient(circle at bottom, rgba(74, 222, 128, 0.12), transparent 36%),
28
- var(--bg);
29
- color: var(--text);
30
- font: 16px/1.5 ui-sans-serif, system-ui, sans-serif;
31
- padding: 24px;
32
- }
33
- main {
34
- width: min(100%, 520px);
35
- padding: 28px;
36
- border-radius: 24px;
37
- border: 1px solid var(--line);
38
- background: linear-gradient(180deg, rgba(15, 23, 42, 0.96), rgba(15, 23, 42, 0.84));
39
- box-shadow: 0 20px 60px rgba(2, 6, 23, 0.45);
40
- }
41
- .eyebrow {
42
- display: inline-flex;
43
- padding: 6px 10px;
44
- border-radius: 999px;
45
- border: 1px solid var(--line);
46
- color: var(--muted);
47
- font-size: 12px;
48
- letter-spacing: 0.08em;
49
- text-transform: uppercase;
50
- }
51
- h1 { margin: 16px 0 8px; font-size: clamp(28px, 5vw, 40px); line-height: 1.05; }
52
- p { margin: 0; color: var(--muted); }
53
- .actions { display: flex; gap: 12px; margin-top: 24px; flex-wrap: wrap; }
54
- a, button {
55
- appearance: none;
56
- border: 0;
57
- border-radius: 999px;
58
- padding: 12px 18px;
59
- font: inherit;
60
- text-decoration: none;
61
- cursor: pointer;
62
- }
63
- a {
64
- color: #082f49;
65
- background: linear-gradient(135deg, var(--accent), var(--accent-2));
66
- font-weight: 700;
67
- }
68
- button {
69
- color: var(--text);
70
- background: rgba(148, 163, 184, 0.12);
71
- border: 1px solid var(--line);
72
- }
73
- </style>
74
- </head>
75
- <body>
76
- <main>
77
- <div class="eyebrow">Offline</div>
78
- <h1>fifony is temporarily disconnected.</h1>
79
- <p>The dashboard shell is available, but live runtime data needs the local server connection to come back.</p>
80
- <div class="actions">
81
- <a href="/kanban">Try again</a>
82
- <button type="button" onclick="location.reload()">Reload</button>
83
- </div>
84
- </main>
85
- </body>
86
- </html>
@@ -1,145 +0,0 @@
1
- const CACHE_VERSION = "__BUILD_TIMESTAMP__";
2
- const CORE_CACHE = `fifony-core-${CACHE_VERSION}`;
3
- const ASSET_CACHE = `fifony-assets-${CACHE_VERSION}`;
4
- const APP_SHELL_ROUTES = ["/kanban", "/issues", "/agents", "/settings", "/onboarding"];
5
- const APP_SHELL_FILES = ["/offline.html", "/manifest.webmanifest", "/icon.svg", "/icon-maskable.svg"];
6
- const API_PREFIXES = ["/api/", "/docs", "/ws"];
7
-
8
- self.addEventListener("install", (event) => {
9
- event.waitUntil((async () => {
10
- const cache = await caches.open(CORE_CACHE);
11
- // Cache each route individually so one failure doesn't block the rest
12
- await Promise.allSettled(
13
- [...APP_SHELL_ROUTES, ...APP_SHELL_FILES].map(async (url) => {
14
- try {
15
- const response = await fetch(url);
16
- if (response.ok) await cache.put(url, response);
17
- } catch {}
18
- }),
19
- );
20
- await self.skipWaiting();
21
- })());
22
- });
23
-
24
- self.addEventListener("activate", (event) => {
25
- event.waitUntil((async () => {
26
- const names = await caches.keys();
27
- await Promise.all(
28
- names
29
- .filter((name) => name !== CORE_CACHE && name !== ASSET_CACHE)
30
- .map((name) => caches.delete(name)),
31
- );
32
- await self.clients.claim();
33
-
34
- // Notify all clients that a new version activated (they may show a refresh banner)
35
- const clients = await self.clients.matchAll({ type: "window" });
36
- for (const client of clients) {
37
- client.postMessage({ type: "SW_UPDATED", version: CACHE_VERSION });
38
- }
39
- })());
40
- });
41
-
42
- self.addEventListener("message", (event) => {
43
- if (event.data?.type === "SKIP_WAITING") {
44
- self.skipWaiting();
45
- }
46
-
47
- if (event.data?.type === "GET_OFFLINE_STATUS") {
48
- const port = event.ports?.[0];
49
- if (port) {
50
- // Probe the API to determine online/offline state
51
- fetch("/health", { signal: AbortSignal.timeout(3000) })
52
- .then((res) => port.postMessage({ offline: !res.ok }))
53
- .catch(() => port.postMessage({ offline: true }));
54
- }
55
- }
56
-
57
- if (event.data?.type === "GET_VERSION") {
58
- const port = event.ports?.[0];
59
- if (port) {
60
- port.postMessage({ version: CACHE_VERSION });
61
- }
62
- }
63
- });
64
-
65
- self.addEventListener("fetch", (event) => {
66
- const request = event.request;
67
- if (request.method !== "GET") return;
68
-
69
- const url = new URL(request.url);
70
- if (url.origin !== self.location.origin) return;
71
- if (API_PREFIXES.some((prefix) => url.pathname.startsWith(prefix))) return;
72
-
73
- if (request.mode === "navigate") {
74
- event.respondWith((async () => {
75
- try {
76
- const response = await fetch(request);
77
- const cache = await caches.open(CORE_CACHE);
78
- await cache.put(request, response.clone());
79
- return response;
80
- } catch {
81
- // Offline: notify clients
82
- notifyClientsOffline();
83
-
84
- const cache = await caches.open(CORE_CACHE);
85
- return (
86
- await cache.match(request) ||
87
- await cache.match("/kanban") ||
88
- await cache.match("/offline.html")
89
- );
90
- }
91
- })());
92
- return;
93
- }
94
-
95
- const isStaticAsset =
96
- url.pathname.startsWith("/assets/") ||
97
- url.pathname.endsWith(".css") ||
98
- url.pathname.endsWith(".js") ||
99
- url.pathname.endsWith(".svg") ||
100
- url.pathname.endsWith(".webmanifest");
101
-
102
- if (!isStaticAsset) return;
103
-
104
- event.respondWith((async () => {
105
- const cache = await caches.open(ASSET_CACHE);
106
- const cached = await cache.match(request);
107
-
108
- if (cached) {
109
- event.waitUntil((async () => {
110
- try {
111
- const fresh = await fetch(request);
112
- if (fresh?.ok) {
113
- await cache.put(request, fresh.clone());
114
- }
115
- } catch {}
116
- })());
117
- return cached;
118
- }
119
-
120
- try {
121
- const response = await fetch(request);
122
- if (response?.ok) {
123
- await cache.put(request, response.clone());
124
- }
125
- return response;
126
- } catch {
127
- if (cached) return cached;
128
- return new Response("Offline", {
129
- status: 503,
130
- statusText: "Service Unavailable",
131
- headers: { "content-type": "text/plain; charset=utf-8" },
132
- });
133
- }
134
- })());
135
- });
136
-
137
- // Broadcast offline status to all window clients
138
- async function notifyClientsOffline() {
139
- try {
140
- const clients = await self.clients.matchAll({ type: "window" });
141
- for (const client of clients) {
142
- client.postMessage({ type: "OFFLINE" });
143
- }
144
- } catch {}
145
- }
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../../src/agents/cli-wrapper.ts"],"sourcesContent":["#!/usr/bin/env node\n/**\n * fifony-wrap — man-in-the-middle wrapper for CLI agents\n *\n * Protocol:\n * FIFONY_WRAP_COMMAND (required) real CLI command to run, e.g. \"claude -p $FIFONY_TURN_PROMPT_FILE\"\n * FIFONY_WRAP_BEFORE_HOOK (optional) shell script run before the CLI; can modify $FIFONY_TURN_PROMPT_FILE in place\n * FIFONY_WRAP_AFTER_HOOK (optional) shell script run after the CLI; receives FIFONY_WRAP_OUTPUT_FILE pointing\n * to a temp file with the captured output — modify it in place to change what fifony sees\n *\n * Configure via the API, MCP, or s3db — set agentCommand on the workflow config to:\n * FIFONY_WRAP_COMMAND=\"claude -p $FIFONY_TURN_PROMPT_FILE\" FIFONY_WRAP_BEFORE_HOOK=./hooks/before.sh fifony-wrap\n */\nimport { readFileSync, writeFileSync, rmSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { env, exit } from \"node:process\";\nimport { spawn } from \"node:child_process\";\n\nfunction runShell(command: string, extraEnv: Record<string, string> = {}): Promise<{ code: number }> {\n return new Promise((resolve) => {\n const child = spawn(command, {\n shell: true,\n stdio: \"inherit\",\n env: { ...env, ...extraEnv },\n });\n child.on(\"close\", (code) => resolve({ code: code ?? 1 }));\n child.on(\"error\", () => resolve({ code: 1 }));\n });\n}\n\nfunction runAndCapture(command: string): Promise<{ code: number | null; output: string }> {\n return new Promise((resolve) => {\n const child = spawn(command, { shell: true, stdio: [\"inherit\", \"pipe\", \"pipe\"] });\n let output = \"\";\n child.stdout?.on(\"data\", (chunk: Buffer) => { output += String(chunk); });\n child.stderr?.on(\"data\", (chunk: Buffer) => { output += String(chunk); });\n child.on(\"close\", (code) => resolve({ code, output }));\n child.on(\"error\", () => resolve({ code: null, output: `Command execution error: ${command}` }));\n });\n}\n\nasync function main() {\n const wrapCommand = env.FIFONY_WRAP_COMMAND?.trim();\n if (!wrapCommand) {\n process.stderr.write(\"fifony-wrap: FIFONY_WRAP_COMMAND is required\\n\");\n exit(1);\n }\n\n const workspacePath = env.FIFONY_WORKSPACE_PATH ?? process.cwd();\n const beforeHook = env.FIFONY_WRAP_BEFORE_HOOK?.trim();\n const afterHook = env.FIFONY_WRAP_AFTER_HOOK?.trim();\n const outputFile = join(workspacePath, \".wrap-output.txt\");\n\n if (beforeHook) {\n const { code } = await runShell(beforeHook);\n if (code !== 0) {\n process.stderr.write(`fifony-wrap: before hook exited ${code}\\n`);\n exit(code);\n }\n }\n\n const { code, output } = await runAndCapture(wrapCommand);\n\n writeFileSync(outputFile, output, \"utf8\");\n\n if (afterHook) {\n const { code: hookCode } = await runShell(afterHook, { FIFONY_WRAP_OUTPUT_FILE: outputFile });\n if (hookCode !== 0) {\n process.stderr.write(`fifony-wrap: after hook exited ${hookCode} — using original output\\n`);\n writeFileSync(outputFile, output, \"utf8\");\n }\n }\n\n let finalOutput = output;\n try {\n finalOutput = readFileSync(outputFile, \"utf8\");\n } catch {}\n try { rmSync(outputFile, { force: true }); } catch {}\n\n process.stdout.write(finalOutput);\n exit(code ?? 1);\n}\n\nmain().catch((err) => {\n process.stderr.write(`fifony-wrap: fatal: ${String(err)}\\n`);\n exit(1);\n});\n"],"mappings":";;;AAaA,SAAS,cAAc,eAAe,cAAc;AACpD,SAAS,YAAY;AACrB,SAAS,KAAK,YAAY;AAC1B,SAAS,aAAa;AAEtB,SAAS,SAAS,SAAiB,WAAmC,CAAC,GAA8B;AACnG,SAAO,IAAI,QAAQ,CAAC,YAAY;AAC9B,UAAM,QAAQ,MAAM,SAAS;AAAA,MAC3B,OAAO;AAAA,MACP,OAAO;AAAA,MACP,KAAK,EAAE,GAAG,KAAK,GAAG,SAAS;AAAA,IAC7B,CAAC;AACD,UAAM,GAAG,SAAS,CAAC,SAAS,QAAQ,EAAE,MAAM,QAAQ,EAAE,CAAC,CAAC;AACxD,UAAM,GAAG,SAAS,MAAM,QAAQ,EAAE,MAAM,EAAE,CAAC,CAAC;AAAA,EAC9C,CAAC;AACH;AAEA,SAAS,cAAc,SAAmE;AACxF,SAAO,IAAI,QAAQ,CAAC,YAAY;AAC9B,UAAM,QAAQ,MAAM,SAAS,EAAE,OAAO,MAAM,OAAO,CAAC,WAAW,QAAQ,MAAM,EAAE,CAAC;AAChF,QAAI,SAAS;AACb,UAAM,QAAQ,GAAG,QAAQ,CAAC,UAAkB;AAAE,gBAAU,OAAO,KAAK;AAAA,IAAG,CAAC;AACxE,UAAM,QAAQ,GAAG,QAAQ,CAAC,UAAkB;AAAE,gBAAU,OAAO,KAAK;AAAA,IAAG,CAAC;AACxE,UAAM,GAAG,SAAS,CAAC,SAAS,QAAQ,EAAE,MAAM,OAAO,CAAC,CAAC;AACrD,UAAM,GAAG,SAAS,MAAM,QAAQ,EAAE,MAAM,MAAM,QAAQ,4BAA4B,OAAO,GAAG,CAAC,CAAC;AAAA,EAChG,CAAC;AACH;AAEA,eAAe,OAAO;AACpB,QAAM,cAAc,IAAI,qBAAqB,KAAK;AAClD,MAAI,CAAC,aAAa;AAChB,YAAQ,OAAO,MAAM,gDAAgD;AACrE,SAAK,CAAC;AAAA,EACR;AAEA,QAAM,gBAAgB,IAAI,yBAAyB,QAAQ,IAAI;AAC/D,QAAM,aAAa,IAAI,yBAAyB,KAAK;AACrD,QAAM,YAAY,IAAI,wBAAwB,KAAK;AACnD,QAAM,aAAa,KAAK,eAAe,kBAAkB;AAEzD,MAAI,YAAY;AACd,UAAM,EAAE,MAAAA,MAAK,IAAI,MAAM,SAAS,UAAU;AAC1C,QAAIA,UAAS,GAAG;AACd,cAAQ,OAAO,MAAM,mCAAmCA,KAAI;AAAA,CAAI;AAChE,WAAKA,KAAI;AAAA,IACX;AAAA,EACF;AAEA,QAAM,EAAE,MAAM,OAAO,IAAI,MAAM,cAAc,WAAW;AAExD,gBAAc,YAAY,QAAQ,MAAM;AAExC,MAAI,WAAW;AACb,UAAM,EAAE,MAAM,SAAS,IAAI,MAAM,SAAS,WAAW,EAAE,yBAAyB,WAAW,CAAC;AAC5F,QAAI,aAAa,GAAG;AAClB,cAAQ,OAAO,MAAM,kCAAkC,QAAQ;AAAA,CAA4B;AAC3F,oBAAc,YAAY,QAAQ,MAAM;AAAA,IAC1C;AAAA,EACF;AAEA,MAAI,cAAc;AAClB,MAAI;AACF,kBAAc,aAAa,YAAY,MAAM;AAAA,EAC/C,QAAQ;AAAA,EAAC;AACT,MAAI;AAAE,WAAO,YAAY,EAAE,OAAO,KAAK,CAAC;AAAA,EAAG,QAAQ;AAAA,EAAC;AAEpD,UAAQ,OAAO,MAAM,WAAW;AAChC,OAAK,QAAQ,CAAC;AAChB;AAEA,KAAK,EAAE,MAAM,CAAC,QAAQ;AACpB,UAAQ,OAAO,MAAM,uBAAuB,OAAO,GAAG,CAAC;AAAA,CAAI;AAC3D,OAAK,CAAC;AACR,CAAC;","names":["code"]}